From a39ff7ed6bc1e01246bd58eed2b532bd81fc3f02 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 10 Aug 2023 14:56:13 +0100 Subject: [PATCH] refactor: add pipeline concept (#8020) --- packages/astro/src/core/README.md | 13 ++ packages/astro/src/core/app/index.ts | 70 +++----- packages/astro/src/core/app/ssrPipeline.ts | 54 ++++++ packages/astro/src/core/build/generate.ts | 2 +- .../src/core/build/plugins/plugin-analyzer.ts | 1 - packages/astro/src/core/cookies/index.ts | 2 +- packages/astro/src/core/cookies/response.ts | 2 +- packages/astro/src/core/endpoint/index.ts | 4 +- packages/astro/src/core/pipeline.ts | 156 ++++++++++++++++++ packages/astro/src/core/render/context.ts | 3 +- packages/astro/src/core/render/core.ts | 12 +- packages/astro/src/core/render/index.ts | 2 +- .../src/vite-plugin-astro-server/route.ts | 18 +- packages/astro/test/units/render/head.test.js | 9 +- packages/astro/test/units/render/jsx.test.js | 9 +- 15 files changed, 279 insertions(+), 78 deletions(-) create mode 100644 packages/astro/src/core/app/ssrPipeline.ts create mode 100644 packages/astro/src/core/pipeline.ts diff --git a/packages/astro/src/core/README.md b/packages/astro/src/core/README.md index 7f5e4f89c..74f55a0bb 100644 --- a/packages/astro/src/core/README.md +++ b/packages/astro/src/core/README.md @@ -3,3 +3,16 @@ Code that executes within the top-level Node context. Contains the main Astro logic for the `build` and `dev` commands, and also manages the Vite server and SSR. [See CONTRIBUTING.md](../../../../CONTRIBUTING.md) for a code overview. + +## Pipeline + +The pipeline is an internal concept that describes how Astro pages are eventually created and rendered to the user. + +Each pipeline has different requirements, criteria and quirks. Although, each pipeline must use the same underline functions, because +the core of the pipeline is the same. + +The core of the pipeline is rendering a generic route (page, endpoint or redirect) and returning a `Response`. +When rendering a route, a pipeline must pass a `RenderContext` and `ComponentInstance`. The way these two information are +computed doesn't concern the core of a pipeline. In fact, these types will be computed in different manner based on the type of pipeline. + +Each consumer will decide how to handle a `Response`. diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index 43f7a3926..c3b687d01 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -1,13 +1,13 @@ -import mime from 'mime'; import type { EndpointHandler, ManifestData, + MiddlewareEndpointHandler, RouteData, SSRElement, SSRManifest, } from '../../@types/astro'; import type { SinglePageBuiltModule } from '../build/types'; -import { attachToResponse, getSetCookiesFromResponse } from '../cookies/index.js'; +import { getSetCookiesFromResponse } from '../cookies/index.js'; import { consoleLogDestination } from '../logger/console.js'; import { error, type LogOptions } from '../logger/core.js'; import { @@ -16,12 +16,10 @@ import { removeTrailingForwardSlash, } from '../path.js'; import { RedirectSinglePageBuiltModule } from '../redirects/index.js'; -import { isResponse } from '../render/core.js'; import { createEnvironment, createRenderContext, tryRenderRoute, - type Environment, type RenderContext, } from '../render/index.js'; import { RouteCache } from '../render/route-cache.js'; @@ -32,6 +30,7 @@ import { } from '../render/ssr-element.js'; import { matchRoute } from '../routing/match.js'; import type { RouteInfo } from './types'; +import { EndpointNotFoundError, SSRRoutePipeline } from './ssrPipeline.js'; export { deserializeManifest } from './common.js'; const clientLocalsSymbol = Symbol.for('astro.locals'); @@ -53,16 +52,15 @@ export class App { /** * The current environment of the application */ - #env: Environment; #manifest: SSRManifest; #manifestData: ManifestData; #routeDataToRouteInfo: Map; - #encoder = new TextEncoder(); #logging: LogOptions = { dest: consoleLogDestination, level: 'info', }; #baseWithoutTrailingSlash: string; + #pipeline: SSRRoutePipeline; constructor(manifest: SSRManifest, streaming = true) { this.#manifest = manifest; @@ -71,7 +69,7 @@ export class App { }; this.#routeDataToRouteInfo = new Map(manifest.routes.map((route) => [route.routeData, route])); this.#baseWithoutTrailingSlash = removeTrailingForwardSlash(this.#manifest.base); - this.#env = this.#createEnvironment(streaming); + this.#pipeline = new SSRRoutePipeline(this.#createEnvironment(streaming)); } set setManifest(newManifest: SSRManifest) { @@ -163,19 +161,21 @@ export class App { ); let response; try { - response = await tryRenderRoute( - routeData.type, - renderContext, - this.#env, - pageModule, - mod.onRequest - ); + // NOTE: ideally we could set the middleware function just once, but we don't have the infrastructure to that yet + if (mod.onRequest) { + this.#pipeline.setMiddlewareFunction(mod.onRequest as MiddlewareEndpointHandler); + } + response = await this.#pipeline.renderRoute(renderContext, pageModule); } catch (err: any) { - error(this.#logging, 'ssr', err.stack || err.message || String(err)); - return this.#renderError(request, { status: 500 }); + if (err instanceof EndpointNotFoundError) { + return this.#renderError(request, { status: 404, response: err.originalResponse }); + } else { + error(this.#logging, 'ssr', err.stack || err.message || String(err)); + return this.#renderError(request, { status: 500 }); + } } - if (isResponse(response, routeData.type)) { + if (SSRRoutePipeline.isResponse(response, routeData.type)) { if (STATUS_CODES.has(response.status)) { return this.#renderError(request, { response, @@ -184,35 +184,8 @@ export class App { } Reflect.set(response, responseSentSymbol, true); return response; - } else { - if (response.type === 'response') { - if (response.response.headers.get('X-Astro-Response') === 'Not-Found') { - return this.#renderError(request, { - response: response.response, - status: 404, - }); - } - return response.response; - } else { - const headers = new Headers(); - const mimeType = mime.getType(url.pathname); - if (mimeType) { - headers.set('Content-Type', `${mimeType};charset=utf-8`); - } else { - headers.set('Content-Type', 'text/plain;charset=utf-8'); - } - const bytes = - response.encoding !== 'binary' ? this.#encoder.encode(response.body) : response.body; - headers.set('Content-Length', bytes.byteLength.toString()); - - const newResponse = new Response(bytes, { - status: 200, - headers, - }); - attachToResponse(newResponse, response.cookies); - return newResponse; - } } + return response; } setCookieHeaders(response: Response) { @@ -238,7 +211,7 @@ export class App { pathname, route: routeData, status, - env: this.#env, + env: this.#pipeline.env, mod: handler as any, }); } else { @@ -272,7 +245,7 @@ export class App { route: routeData, status, mod, - env: this.#env, + env: this.#pipeline.env, }); } } @@ -301,9 +274,8 @@ export class App { ); const page = (await mod.page()) as any; const response = (await tryRenderRoute( - 'page', // this is hardcoded to ensure proper behavior for missing endpoints newRenderContext, - this.#env, + this.#pipeline.env, page )) as Response; return this.#mergeResponses(response, originalResponse); diff --git a/packages/astro/src/core/app/ssrPipeline.ts b/packages/astro/src/core/app/ssrPipeline.ts new file mode 100644 index 000000000..cdb95ff7c --- /dev/null +++ b/packages/astro/src/core/app/ssrPipeline.ts @@ -0,0 +1,54 @@ +import type { Environment } from '../render'; +import type { EndpointCallResult } from '../endpoint/index.js'; +import mime from 'mime'; +import { attachCookiesToResponse } from '../cookies/index.js'; +import { Pipeline } from '../pipeline.js'; + +/** + * Thrown when an endpoint contains a response with the header "X-Astro-Response" === 'Not-Found' + */ +export class EndpointNotFoundError extends Error { + originalResponse: Response; + constructor(originalResponse: Response) { + super(); + this.originalResponse = originalResponse; + } +} + +export class SSRRoutePipeline extends Pipeline { + encoder = new TextEncoder(); + + constructor(env: Environment) { + super(env); + this.setEndpointHandler(this.#ssrEndpointHandler); + } + + // This function is responsible for handling the result coming from an endpoint. + async #ssrEndpointHandler(request: Request, response: EndpointCallResult): Promise { + if (response.type === 'response') { + if (response.response.headers.get('X-Astro-Response') === 'Not-Found') { + throw new EndpointNotFoundError(response.response); + } + return response.response; + } else { + const url = new URL(request.url); + const headers = new Headers(); + const mimeType = mime.getType(url.pathname); + if (mimeType) { + headers.set('Content-Type', `${mimeType};charset=utf-8`); + } else { + headers.set('Content-Type', 'text/plain;charset=utf-8'); + } + const bytes = + response.encoding !== 'binary' ? this.encoder.encode(response.body) : response.body; + headers.set('Content-Length', bytes.byteLength.toString()); + + const newResponse = new Response(bytes, { + status: 200, + headers, + }); + attachCookiesToResponse(newResponse, response.cookies); + return newResponse; + } + } +} diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index 3e3a44ce0..f8d1bf6b4 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -547,7 +547,7 @@ async function generatePath( let response; try { - response = await tryRenderRoute(pageData.route.type, renderContext, env, mod, onRequest); + response = await tryRenderRoute(renderContext, env, mod, onRequest); } catch (err) { if (!AstroError.is(err) && !(err as SSRError).id && typeof err === 'object') { (err as SSRError).id = pageData.component; diff --git a/packages/astro/src/core/build/plugins/plugin-analyzer.ts b/packages/astro/src/core/build/plugins/plugin-analyzer.ts index bb632eb49..8483748bc 100644 --- a/packages/astro/src/core/build/plugins/plugin-analyzer.ts +++ b/packages/astro/src/core/build/plugins/plugin-analyzer.ts @@ -1,4 +1,3 @@ -import type { Node as ESTreeNode } from 'estree-walker'; import type { ModuleInfo, PluginContext } from 'rollup'; import type { Plugin as VitePlugin } from 'vite'; import type { PluginMetadata as AstroPluginMetadata } from '../../../vite-plugin-astro/types'; diff --git a/packages/astro/src/core/cookies/index.ts b/packages/astro/src/core/cookies/index.ts index 1b0c6b7a0..f3c7b6d61 100644 --- a/packages/astro/src/core/cookies/index.ts +++ b/packages/astro/src/core/cookies/index.ts @@ -1,2 +1,2 @@ export { AstroCookies } from './cookies.js'; -export { attachToResponse, getSetCookiesFromResponse } from './response.js'; +export { attachCookiesToResponse, getSetCookiesFromResponse } from './response.js'; diff --git a/packages/astro/src/core/cookies/response.ts b/packages/astro/src/core/cookies/response.ts index 18d72ab1c..668bd265f 100644 --- a/packages/astro/src/core/cookies/response.ts +++ b/packages/astro/src/core/cookies/response.ts @@ -2,7 +2,7 @@ import type { AstroCookies } from './cookies'; const astroCookiesSymbol = Symbol.for('astro.cookies'); -export function attachToResponse(response: Response, cookies: AstroCookies) { +export function attachCookiesToResponse(response: Response, cookies: AstroCookies) { Reflect.set(response, astroCookiesSymbol, cookies); } diff --git a/packages/astro/src/core/endpoint/index.ts b/packages/astro/src/core/endpoint/index.ts index 97b26e380..9298e7cbe 100644 --- a/packages/astro/src/core/endpoint/index.ts +++ b/packages/astro/src/core/endpoint/index.ts @@ -9,7 +9,7 @@ import type { import type { Environment, RenderContext } from '../render/index'; import { renderEndpoint } from '../../runtime/server/index.js'; import { ASTRO_VERSION } from '../constants.js'; -import { AstroCookies, attachToResponse } from '../cookies/index.js'; +import { AstroCookies, attachCookiesToResponse } from '../cookies/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; import { warn } from '../logger/core.js'; import { callMiddleware } from '../middleware/callMiddleware.js'; @@ -125,7 +125,7 @@ export async function callEndpoint } if (response instanceof Response) { - attachToResponse(response, context.cookies); + attachCookiesToResponse(response, context.cookies); return { type: 'response', response, diff --git a/packages/astro/src/core/pipeline.ts b/packages/astro/src/core/pipeline.ts new file mode 100644 index 000000000..66fa6bd07 --- /dev/null +++ b/packages/astro/src/core/pipeline.ts @@ -0,0 +1,156 @@ +import { type RenderContext, type Environment } from './render/index.js'; +import { type EndpointCallResult, callEndpoint, createAPIContext } from './endpoint/index.js'; +import type { + MiddlewareHandler, + MiddlewareResponseHandler, + ComponentInstance, + MiddlewareEndpointHandler, + RouteType, + EndpointHandler, +} from '../@types/astro'; +import { callMiddleware } from './middleware/callMiddleware.js'; +import { renderPage } from './render/core.js'; + +type EndpointResultHandler = ( + originalRequest: Request, + result: EndpointCallResult +) => Promise | Response; + +/** + * This is the basic class of a pipeline. + * + * Check the {@link ./README.md|README} for more information about the pipeline. + */ +export class Pipeline { + env: Environment; + onRequest?: MiddlewareEndpointHandler; + /** + * The handler accepts the *original* `Request` and result returned by the endpoint. + * It must return a `Response`. + */ + endpointHandler?: EndpointResultHandler; + + /** + * When creating a pipeline, an environment is mandatory. + * The environment won't change for the whole lifetime of the pipeline. + */ + constructor(env: Environment) { + this.env = env; + } + + /** + * When rendering a route, an "endpoint" will a type that needs to be handled and transformed into a `Response`. + * + * Each consumer might have different needs; use this function to set up the handler. + */ + setEndpointHandler(handler: EndpointResultHandler) { + this.endpointHandler = handler; + } + + /** + * A middleware function that will be called before each request. + */ + setMiddlewareFunction(onRequest: MiddlewareEndpointHandler) { + this.onRequest = onRequest; + } + + /** + * The main function of the pipeline. Use this function to render any route known to Astro; + */ + async renderRoute( + renderContext: RenderContext, + componentInstance: ComponentInstance + ): Promise { + const result = await this.#tryRenderRoute( + renderContext, + this.env, + componentInstance, + this.onRequest + ); + if (Pipeline.isEndpointResult(result, renderContext.route.type)) { + if (!this.endpointHandler) { + throw new Error( + 'You created a pipeline that does not know how to handle the result coming from an endpoint.' + ); + } + return this.endpointHandler(renderContext.request, result); + } else { + return result; + } + } + + /** + * It attempts to render a route. A route can be a: + * - page + * - redirect + * - endpoint + * + * ## Errors + * + * It throws an error if the page can't be rendered. + */ + async #tryRenderRoute( + renderContext: Readonly, + env: Readonly, + mod: Readonly, + onRequest?: MiddlewareHandler + ): Promise { + const apiContext = createAPIContext({ + request: renderContext.request, + params: renderContext.params, + props: renderContext.props, + site: env.site, + adapterName: env.adapterName, + }); + + switch (renderContext.route.type) { + case 'page': + case 'redirect': { + if (onRequest) { + return await callMiddleware( + env.logging, + onRequest as MiddlewareResponseHandler, + apiContext, + () => { + return renderPage({ + mod, + renderContext, + env, + cookies: apiContext.cookies, + }); + } + ); + } else { + return await renderPage({ + mod, + renderContext, + env, + cookies: apiContext.cookies, + }); + } + } + case 'endpoint': { + const result = await callEndpoint( + mod as any as EndpointHandler, + env, + renderContext, + onRequest + ); + return result; + } + default: + throw new Error(`Couldn't find route of type [${renderContext.route.type}]`); + } + } + + /** + * Use this function + */ + static isEndpointResult(result: any, routeType: RouteType): result is EndpointCallResult { + return !(result instanceof Response) && routeType === 'endpoint'; + } + + static isResponse(result: any, routeType: RouteType): result is Response { + return result instanceof Response && (routeType === 'page' || routeType === 'redirect'); + } +} diff --git a/packages/astro/src/core/render/context.ts b/packages/astro/src/core/render/context.ts index 5b26eda18..d767d7910 100644 --- a/packages/astro/src/core/render/context.ts +++ b/packages/astro/src/core/render/context.ts @@ -22,7 +22,7 @@ export interface RenderContext { links?: Set; styles?: Set; componentMetadata?: SSRResult['componentMetadata']; - route?: RouteData; + route: RouteData; status?: number; params: Params; props: Props; @@ -32,6 +32,7 @@ export interface RenderContext { export type CreateRenderContextArgs = Partial< Omit > & { + route: RouteData; request: RenderContext['request']; mod: ComponentInstance; env: Environment; diff --git a/packages/astro/src/core/render/core.ts b/packages/astro/src/core/render/core.ts index eca7552e6..9de046278 100644 --- a/packages/astro/src/core/render/core.ts +++ b/packages/astro/src/core/render/core.ts @@ -7,7 +7,7 @@ import type { RouteType, } from '../../@types/astro'; import { renderPage as runtimeRenderPage } from '../../runtime/server/index.js'; -import { attachToResponse } from '../cookies/index.js'; +import { attachCookiesToResponse } from '../cookies/index.js'; import { callEndpoint, createAPIContext, type EndpointCallResult } from '../endpoint/index.js'; import { callMiddleware } from '../middleware/callMiddleware.js'; import { redirectRouteGenerate, redirectRouteStatus, routeIsRedirect } from '../redirects/index.js'; @@ -22,7 +22,7 @@ export type RenderPage = { cookies: AstroCookies; }; -async function renderPage({ mod, renderContext, env, cookies }: RenderPage) { +export async function renderPage({ mod, renderContext, env, cookies }: RenderPage) { if (routeIsRedirect(renderContext.route)) { return new Response(null, { status: redirectRouteStatus(renderContext.route, renderContext.request.method), @@ -70,7 +70,7 @@ async function renderPage({ mod, renderContext, env, cookies }: RenderPage) { // If there is an Astro.cookies instance, attach it to the response so that // adapters can grab the Set-Cookie headers. if (result.cookies) { - attachToResponse(response, result.cookies); + attachCookiesToResponse(response, result.cookies); } return response; @@ -85,9 +85,9 @@ async function renderPage({ mod, renderContext, env, cookies }: RenderPage) { * ## Errors * * It throws an error if the page can't be rendered. + * @deprecated Use the pipeline instead */ export async function tryRenderRoute( - routeType: RouteType, renderContext: Readonly, env: Readonly, mod: Readonly, @@ -101,7 +101,7 @@ export async function tryRenderRoute( adapterName: env.adapterName, }); - switch (routeType) { + switch (renderContext.route.type) { case 'page': case 'redirect': { if (onRequest) { @@ -137,7 +137,7 @@ export async function tryRenderRoute( return result; } default: - throw new Error(`Couldn't find route of type [${routeType}]`); + throw new Error(`Couldn't find route of type [${renderContext.route.type}]`); } } diff --git a/packages/astro/src/core/render/index.ts b/packages/astro/src/core/render/index.ts index a82c5699e..20b964fa7 100644 --- a/packages/astro/src/core/render/index.ts +++ b/packages/astro/src/core/render/index.ts @@ -22,7 +22,7 @@ export interface SSROptions { /** Request */ request: Request; /** optional, in case we need to render something outside of a dev server */ - route?: RouteData; + route: RouteData; /** * Optional middlewares */ diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts index f58d248a3..0bbaacbe2 100644 --- a/packages/astro/src/vite-plugin-astro-server/route.ts +++ b/packages/astro/src/vite-plugin-astro-server/route.ts @@ -8,7 +8,7 @@ import type { SSRElement, SSRManifest, } from '../@types/astro'; -import { attachToResponse } from '../core/cookies/index.js'; +import { attachCookiesToResponse } from '../core/cookies/index.js'; import { AstroErrorData, isAstroError } from '../core/errors/index.js'; import { warn } from '../core/logger/core.js'; import { loadMiddleware } from '../core/middleware/loadMiddleware.js'; @@ -49,18 +49,18 @@ export interface MatchedRoute { mod: ComponentInstance; } -function getCustom404Route(manifest: ManifestData): RouteData | undefined { +function getCustom404Route(manifestData: ManifestData): RouteData | undefined { const route404 = /^\/404\/?$/; - return manifest.routes.find((r) => route404.test(r.route)); + return manifestData.routes.find((r) => route404.test(r.route)); } export async function matchRoute( pathname: string, env: DevelopmentEnvironment, - manifest: ManifestData + manifestData: ManifestData ): Promise { const { logging, settings, routeCache } = env; - const matches = matchAllRoutes(pathname, manifest); + const matches = matchAllRoutes(pathname, manifestData); const preloadedMatches = await getSortedPreloadedMatches({ env, matches, settings }); for await (const { preloadedComponent, route: maybeRoute, filePath } of preloadedMatches) { @@ -96,7 +96,7 @@ export async function matchRoute( // build formats, and is necessary based on how the manifest tracks build targets. const altPathname = pathname.replace(/(index)?\.html$/, ''); if (altPathname !== pathname) { - return await matchRoute(altPathname, env, manifest); + return await matchRoute(altPathname, env, manifestData); } if (matches.length) { @@ -112,7 +112,7 @@ export async function matchRoute( } log404(logging, pathname); - const custom404 = getCustom404Route(manifest); + const custom404 = getCustom404Route(manifestData); if (custom404) { const filePath = new URL(`./${custom404.component}`, settings.config.root); @@ -216,7 +216,7 @@ export async function handleRoute({ }); const onRequest = options.middleware?.onRequest as MiddlewareResponseHandler | undefined; - const result = await tryRenderRoute(route.type, renderContext, env, mod, onRequest); + const result = await tryRenderRoute(renderContext, env, mod, onRequest); if (isEndpointResult(result, route.type)) { if (result.type === 'response') { if (result.response.headers.get('X-Astro-Response') === 'Not-Found') { @@ -255,7 +255,7 @@ export async function handleRoute({ }, } ); - attachToResponse(response, result.cookies); + attachCookiesToResponse(response, result.cookies); await writeWebResponse(incomingResponse, response); } } else { diff --git a/packages/astro/test/units/render/head.test.js b/packages/astro/test/units/render/head.test.js index fbd16be31..d2580e30d 100644 --- a/packages/astro/test/units/render/head.test.js +++ b/packages/astro/test/units/render/head.test.js @@ -90,13 +90,14 @@ describe('core/render', () => { const PageModule = createAstroModule(Page); const ctx = await createRenderContext({ + route: { type: 'page', pathname: '/index' }, request: new Request('http://example.com/'), links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }], mod: PageModule, env, }); - const response = await tryRenderRoute('page', ctx, env, PageModule); + const response = await tryRenderRoute(ctx, env, PageModule); const html = await response.text(); const $ = cheerio.load(html); @@ -170,13 +171,14 @@ describe('core/render', () => { const PageModule = createAstroModule(Page); const ctx = await createRenderContext({ + route: { type: 'page', pathname: '/index' }, request: new Request('http://example.com/'), links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }], env, mod: PageModule, }); - const response = await tryRenderRoute('page', ctx, env, PageModule); + const response = await tryRenderRoute(ctx, env, PageModule); const html = await response.text(); const $ = cheerio.load(html); @@ -216,13 +218,14 @@ describe('core/render', () => { const PageModule = createAstroModule(Page); const ctx = await createRenderContext({ + route: { type: 'page', pathname: '/index' }, request: new Request('http://example.com/'), links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }], env, mod: PageModule, }); - const response = await tryRenderRoute('page', ctx, env, PageModule); + const response = await tryRenderRoute(ctx, env, PageModule); const html = await response.text(); const $ = cheerio.load(html); diff --git a/packages/astro/test/units/render/jsx.test.js b/packages/astro/test/units/render/jsx.test.js index 9be135fc0..1464b5b0c 100644 --- a/packages/astro/test/units/render/jsx.test.js +++ b/packages/astro/test/units/render/jsx.test.js @@ -45,12 +45,13 @@ describe('core/render', () => { const mod = createAstroModule(Page); const ctx = await createRenderContext({ + route: { type: 'page', pathname: '/index' }, request: new Request('http://example.com/'), env, mod, }); - const response = await tryRenderRoute('page', ctx, env, mod); + const response = await tryRenderRoute(ctx, env, mod); expect(response.status).to.equal(200); @@ -90,11 +91,12 @@ describe('core/render', () => { const mod = createAstroModule(Page); const ctx = await createRenderContext({ + route: { type: 'page', pathname: '/index' }, request: new Request('http://example.com/'), env, mod, }); - const response = await tryRenderRoute('page', ctx, env, mod); + const response = await tryRenderRoute(ctx, env, mod); expect(response.status).to.equal(200); @@ -115,12 +117,13 @@ describe('core/render', () => { const mod = createAstroModule(Page); const ctx = await createRenderContext({ + route: { type: 'page', pathname: '/index' }, request: new Request('http://example.com/'), env, mod, }); - const response = await tryRenderRoute('page', ctx, env, mod); + const response = await tryRenderRoute(ctx, env, mod); try { await response.text();