refactor: add pipeline concept (#8020)

This commit is contained in:
Emanuele Stoppa 2023-08-10 14:56:13 +01:00 committed by GitHub
parent 924bef998e
commit a39ff7ed6b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 279 additions and 78 deletions

View file

@ -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. 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. [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`.

View file

@ -1,13 +1,13 @@
import mime from 'mime';
import type { import type {
EndpointHandler, EndpointHandler,
ManifestData, ManifestData,
MiddlewareEndpointHandler,
RouteData, RouteData,
SSRElement, SSRElement,
SSRManifest, SSRManifest,
} from '../../@types/astro'; } from '../../@types/astro';
import type { SinglePageBuiltModule } from '../build/types'; 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 { consoleLogDestination } from '../logger/console.js';
import { error, type LogOptions } from '../logger/core.js'; import { error, type LogOptions } from '../logger/core.js';
import { import {
@ -16,12 +16,10 @@ import {
removeTrailingForwardSlash, removeTrailingForwardSlash,
} from '../path.js'; } from '../path.js';
import { RedirectSinglePageBuiltModule } from '../redirects/index.js'; import { RedirectSinglePageBuiltModule } from '../redirects/index.js';
import { isResponse } from '../render/core.js';
import { import {
createEnvironment, createEnvironment,
createRenderContext, createRenderContext,
tryRenderRoute, tryRenderRoute,
type Environment,
type RenderContext, type RenderContext,
} from '../render/index.js'; } from '../render/index.js';
import { RouteCache } from '../render/route-cache.js'; import { RouteCache } from '../render/route-cache.js';
@ -32,6 +30,7 @@ import {
} from '../render/ssr-element.js'; } from '../render/ssr-element.js';
import { matchRoute } from '../routing/match.js'; import { matchRoute } from '../routing/match.js';
import type { RouteInfo } from './types'; import type { RouteInfo } from './types';
import { EndpointNotFoundError, SSRRoutePipeline } from './ssrPipeline.js';
export { deserializeManifest } from './common.js'; export { deserializeManifest } from './common.js';
const clientLocalsSymbol = Symbol.for('astro.locals'); const clientLocalsSymbol = Symbol.for('astro.locals');
@ -53,16 +52,15 @@ export class App {
/** /**
* The current environment of the application * The current environment of the application
*/ */
#env: Environment;
#manifest: SSRManifest; #manifest: SSRManifest;
#manifestData: ManifestData; #manifestData: ManifestData;
#routeDataToRouteInfo: Map<RouteData, RouteInfo>; #routeDataToRouteInfo: Map<RouteData, RouteInfo>;
#encoder = new TextEncoder();
#logging: LogOptions = { #logging: LogOptions = {
dest: consoleLogDestination, dest: consoleLogDestination,
level: 'info', level: 'info',
}; };
#baseWithoutTrailingSlash: string; #baseWithoutTrailingSlash: string;
#pipeline: SSRRoutePipeline;
constructor(manifest: SSRManifest, streaming = true) { constructor(manifest: SSRManifest, streaming = true) {
this.#manifest = manifest; this.#manifest = manifest;
@ -71,7 +69,7 @@ export class App {
}; };
this.#routeDataToRouteInfo = new Map(manifest.routes.map((route) => [route.routeData, route])); this.#routeDataToRouteInfo = new Map(manifest.routes.map((route) => [route.routeData, route]));
this.#baseWithoutTrailingSlash = removeTrailingForwardSlash(this.#manifest.base); this.#baseWithoutTrailingSlash = removeTrailingForwardSlash(this.#manifest.base);
this.#env = this.#createEnvironment(streaming); this.#pipeline = new SSRRoutePipeline(this.#createEnvironment(streaming));
} }
set setManifest(newManifest: SSRManifest) { set setManifest(newManifest: SSRManifest) {
@ -163,19 +161,21 @@ export class App {
); );
let response; let response;
try { try {
response = await tryRenderRoute( // NOTE: ideally we could set the middleware function just once, but we don't have the infrastructure to that yet
routeData.type, if (mod.onRequest) {
renderContext, this.#pipeline.setMiddlewareFunction(mod.onRequest as MiddlewareEndpointHandler);
this.#env, }
pageModule, response = await this.#pipeline.renderRoute(renderContext, pageModule);
mod.onRequest
);
} catch (err: any) { } catch (err: any) {
error(this.#logging, 'ssr', err.stack || err.message || String(err)); if (err instanceof EndpointNotFoundError) {
return this.#renderError(request, { status: 500 }); 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)) { if (STATUS_CODES.has(response.status)) {
return this.#renderError(request, { return this.#renderError(request, {
response, response,
@ -184,35 +184,8 @@ export class App {
} }
Reflect.set(response, responseSentSymbol, true); Reflect.set(response, responseSentSymbol, true);
return response; 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) { setCookieHeaders(response: Response) {
@ -238,7 +211,7 @@ export class App {
pathname, pathname,
route: routeData, route: routeData,
status, status,
env: this.#env, env: this.#pipeline.env,
mod: handler as any, mod: handler as any,
}); });
} else { } else {
@ -272,7 +245,7 @@ export class App {
route: routeData, route: routeData,
status, status,
mod, mod,
env: this.#env, env: this.#pipeline.env,
}); });
} }
} }
@ -301,9 +274,8 @@ export class App {
); );
const page = (await mod.page()) as any; const page = (await mod.page()) as any;
const response = (await tryRenderRoute( const response = (await tryRenderRoute(
'page', // this is hardcoded to ensure proper behavior for missing endpoints
newRenderContext, newRenderContext,
this.#env, this.#pipeline.env,
page page
)) as Response; )) as Response;
return this.#mergeResponses(response, originalResponse); return this.#mergeResponses(response, originalResponse);

View file

@ -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<Response> {
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;
}
}
}

View file

@ -547,7 +547,7 @@ async function generatePath(
let response; let response;
try { try {
response = await tryRenderRoute(pageData.route.type, renderContext, env, mod, onRequest); response = await tryRenderRoute(renderContext, env, mod, onRequest);
} catch (err) { } catch (err) {
if (!AstroError.is(err) && !(err as SSRError).id && typeof err === 'object') { if (!AstroError.is(err) && !(err as SSRError).id && typeof err === 'object') {
(err as SSRError).id = pageData.component; (err as SSRError).id = pageData.component;

View file

@ -1,4 +1,3 @@
import type { Node as ESTreeNode } from 'estree-walker';
import type { ModuleInfo, PluginContext } from 'rollup'; import type { ModuleInfo, PluginContext } from 'rollup';
import type { Plugin as VitePlugin } from 'vite'; import type { Plugin as VitePlugin } from 'vite';
import type { PluginMetadata as AstroPluginMetadata } from '../../../vite-plugin-astro/types'; import type { PluginMetadata as AstroPluginMetadata } from '../../../vite-plugin-astro/types';

View file

@ -1,2 +1,2 @@
export { AstroCookies } from './cookies.js'; export { AstroCookies } from './cookies.js';
export { attachToResponse, getSetCookiesFromResponse } from './response.js'; export { attachCookiesToResponse, getSetCookiesFromResponse } from './response.js';

View file

@ -2,7 +2,7 @@ import type { AstroCookies } from './cookies';
const astroCookiesSymbol = Symbol.for('astro.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); Reflect.set(response, astroCookiesSymbol, cookies);
} }

View file

@ -9,7 +9,7 @@ import type {
import type { Environment, RenderContext } from '../render/index'; import type { Environment, RenderContext } from '../render/index';
import { renderEndpoint } from '../../runtime/server/index.js'; import { renderEndpoint } from '../../runtime/server/index.js';
import { ASTRO_VERSION } from '../constants.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 { AstroError, AstroErrorData } from '../errors/index.js';
import { warn } from '../logger/core.js'; import { warn } from '../logger/core.js';
import { callMiddleware } from '../middleware/callMiddleware.js'; import { callMiddleware } from '../middleware/callMiddleware.js';
@ -125,7 +125,7 @@ export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>
} }
if (response instanceof Response) { if (response instanceof Response) {
attachToResponse(response, context.cookies); attachCookiesToResponse(response, context.cookies);
return { return {
type: 'response', type: 'response',
response, response,

View file

@ -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> | 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<Response> {
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<MiddlewareReturnType = Response>(
renderContext: Readonly<RenderContext>,
env: Readonly<Environment>,
mod: Readonly<ComponentInstance>,
onRequest?: MiddlewareHandler<MiddlewareReturnType>
): Promise<Response | EndpointCallResult> {
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<Response>(
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');
}
}

View file

@ -22,7 +22,7 @@ export interface RenderContext {
links?: Set<SSRElement>; links?: Set<SSRElement>;
styles?: Set<SSRElement>; styles?: Set<SSRElement>;
componentMetadata?: SSRResult['componentMetadata']; componentMetadata?: SSRResult['componentMetadata'];
route?: RouteData; route: RouteData;
status?: number; status?: number;
params: Params; params: Params;
props: Props; props: Props;
@ -32,6 +32,7 @@ export interface RenderContext {
export type CreateRenderContextArgs = Partial< export type CreateRenderContextArgs = Partial<
Omit<RenderContext, 'params' | 'props' | 'locals'> Omit<RenderContext, 'params' | 'props' | 'locals'>
> & { > & {
route: RouteData;
request: RenderContext['request']; request: RenderContext['request'];
mod: ComponentInstance; mod: ComponentInstance;
env: Environment; env: Environment;

View file

@ -7,7 +7,7 @@ import type {
RouteType, RouteType,
} from '../../@types/astro'; } from '../../@types/astro';
import { renderPage as runtimeRenderPage } from '../../runtime/server/index.js'; 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 { callEndpoint, createAPIContext, type EndpointCallResult } from '../endpoint/index.js';
import { callMiddleware } from '../middleware/callMiddleware.js'; import { callMiddleware } from '../middleware/callMiddleware.js';
import { redirectRouteGenerate, redirectRouteStatus, routeIsRedirect } from '../redirects/index.js'; import { redirectRouteGenerate, redirectRouteStatus, routeIsRedirect } from '../redirects/index.js';
@ -22,7 +22,7 @@ export type RenderPage = {
cookies: AstroCookies; cookies: AstroCookies;
}; };
async function renderPage({ mod, renderContext, env, cookies }: RenderPage) { export async function renderPage({ mod, renderContext, env, cookies }: RenderPage) {
if (routeIsRedirect(renderContext.route)) { if (routeIsRedirect(renderContext.route)) {
return new Response(null, { return new Response(null, {
status: redirectRouteStatus(renderContext.route, renderContext.request.method), 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 // If there is an Astro.cookies instance, attach it to the response so that
// adapters can grab the Set-Cookie headers. // adapters can grab the Set-Cookie headers.
if (result.cookies) { if (result.cookies) {
attachToResponse(response, result.cookies); attachCookiesToResponse(response, result.cookies);
} }
return response; return response;
@ -85,9 +85,9 @@ async function renderPage({ mod, renderContext, env, cookies }: RenderPage) {
* ## Errors * ## Errors
* *
* It throws an error if the page can't be rendered. * It throws an error if the page can't be rendered.
* @deprecated Use the pipeline instead
*/ */
export async function tryRenderRoute<MiddlewareReturnType = Response>( export async function tryRenderRoute<MiddlewareReturnType = Response>(
routeType: RouteType,
renderContext: Readonly<RenderContext>, renderContext: Readonly<RenderContext>,
env: Readonly<Environment>, env: Readonly<Environment>,
mod: Readonly<ComponentInstance>, mod: Readonly<ComponentInstance>,
@ -101,7 +101,7 @@ export async function tryRenderRoute<MiddlewareReturnType = Response>(
adapterName: env.adapterName, adapterName: env.adapterName,
}); });
switch (routeType) { switch (renderContext.route.type) {
case 'page': case 'page':
case 'redirect': { case 'redirect': {
if (onRequest) { if (onRequest) {
@ -137,7 +137,7 @@ export async function tryRenderRoute<MiddlewareReturnType = Response>(
return result; return result;
} }
default: default:
throw new Error(`Couldn't find route of type [${routeType}]`); throw new Error(`Couldn't find route of type [${renderContext.route.type}]`);
} }
} }

View file

@ -22,7 +22,7 @@ export interface SSROptions {
/** Request */ /** Request */
request: Request; request: Request;
/** optional, in case we need to render something outside of a dev server */ /** optional, in case we need to render something outside of a dev server */
route?: RouteData; route: RouteData;
/** /**
* Optional middlewares * Optional middlewares
*/ */

View file

@ -8,7 +8,7 @@ import type {
SSRElement, SSRElement,
SSRManifest, SSRManifest,
} from '../@types/astro'; } 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 { AstroErrorData, isAstroError } from '../core/errors/index.js';
import { warn } from '../core/logger/core.js'; import { warn } from '../core/logger/core.js';
import { loadMiddleware } from '../core/middleware/loadMiddleware.js'; import { loadMiddleware } from '../core/middleware/loadMiddleware.js';
@ -49,18 +49,18 @@ export interface MatchedRoute {
mod: ComponentInstance; mod: ComponentInstance;
} }
function getCustom404Route(manifest: ManifestData): RouteData | undefined { function getCustom404Route(manifestData: ManifestData): RouteData | undefined {
const route404 = /^\/404\/?$/; 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( export async function matchRoute(
pathname: string, pathname: string,
env: DevelopmentEnvironment, env: DevelopmentEnvironment,
manifest: ManifestData manifestData: ManifestData
): Promise<MatchedRoute | undefined> { ): Promise<MatchedRoute | undefined> {
const { logging, settings, routeCache } = env; const { logging, settings, routeCache } = env;
const matches = matchAllRoutes(pathname, manifest); const matches = matchAllRoutes(pathname, manifestData);
const preloadedMatches = await getSortedPreloadedMatches({ env, matches, settings }); const preloadedMatches = await getSortedPreloadedMatches({ env, matches, settings });
for await (const { preloadedComponent, route: maybeRoute, filePath } of preloadedMatches) { 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. // build formats, and is necessary based on how the manifest tracks build targets.
const altPathname = pathname.replace(/(index)?\.html$/, ''); const altPathname = pathname.replace(/(index)?\.html$/, '');
if (altPathname !== pathname) { if (altPathname !== pathname) {
return await matchRoute(altPathname, env, manifest); return await matchRoute(altPathname, env, manifestData);
} }
if (matches.length) { if (matches.length) {
@ -112,7 +112,7 @@ export async function matchRoute(
} }
log404(logging, pathname); log404(logging, pathname);
const custom404 = getCustom404Route(manifest); const custom404 = getCustom404Route(manifestData);
if (custom404) { if (custom404) {
const filePath = new URL(`./${custom404.component}`, settings.config.root); 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 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 (isEndpointResult(result, route.type)) {
if (result.type === 'response') { if (result.type === 'response') {
if (result.response.headers.get('X-Astro-Response') === 'Not-Found') { 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); await writeWebResponse(incomingResponse, response);
} }
} else { } else {

View file

@ -90,13 +90,14 @@ describe('core/render', () => {
const PageModule = createAstroModule(Page); const PageModule = createAstroModule(Page);
const ctx = await createRenderContext({ const ctx = await createRenderContext({
route: { type: 'page', pathname: '/index' },
request: new Request('http://example.com/'), request: new Request('http://example.com/'),
links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }], links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }],
mod: PageModule, mod: PageModule,
env, env,
}); });
const response = await tryRenderRoute('page', ctx, env, PageModule); const response = await tryRenderRoute(ctx, env, PageModule);
const html = await response.text(); const html = await response.text();
const $ = cheerio.load(html); const $ = cheerio.load(html);
@ -170,13 +171,14 @@ describe('core/render', () => {
const PageModule = createAstroModule(Page); const PageModule = createAstroModule(Page);
const ctx = await createRenderContext({ const ctx = await createRenderContext({
route: { type: 'page', pathname: '/index' },
request: new Request('http://example.com/'), request: new Request('http://example.com/'),
links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }], links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }],
env, env,
mod: PageModule, mod: PageModule,
}); });
const response = await tryRenderRoute('page', ctx, env, PageModule); const response = await tryRenderRoute(ctx, env, PageModule);
const html = await response.text(); const html = await response.text();
const $ = cheerio.load(html); const $ = cheerio.load(html);
@ -216,13 +218,14 @@ describe('core/render', () => {
const PageModule = createAstroModule(Page); const PageModule = createAstroModule(Page);
const ctx = await createRenderContext({ const ctx = await createRenderContext({
route: { type: 'page', pathname: '/index' },
request: new Request('http://example.com/'), request: new Request('http://example.com/'),
links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }], links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }],
env, env,
mod: PageModule, mod: PageModule,
}); });
const response = await tryRenderRoute('page', ctx, env, PageModule); const response = await tryRenderRoute(ctx, env, PageModule);
const html = await response.text(); const html = await response.text();
const $ = cheerio.load(html); const $ = cheerio.load(html);

View file

@ -45,12 +45,13 @@ describe('core/render', () => {
const mod = createAstroModule(Page); const mod = createAstroModule(Page);
const ctx = await createRenderContext({ const ctx = await createRenderContext({
route: { type: 'page', pathname: '/index' },
request: new Request('http://example.com/'), request: new Request('http://example.com/'),
env, env,
mod, mod,
}); });
const response = await tryRenderRoute('page', ctx, env, mod); const response = await tryRenderRoute(ctx, env, mod);
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
@ -90,11 +91,12 @@ describe('core/render', () => {
const mod = createAstroModule(Page); const mod = createAstroModule(Page);
const ctx = await createRenderContext({ const ctx = await createRenderContext({
route: { type: 'page', pathname: '/index' },
request: new Request('http://example.com/'), request: new Request('http://example.com/'),
env, env,
mod, mod,
}); });
const response = await tryRenderRoute('page', ctx, env, mod); const response = await tryRenderRoute(ctx, env, mod);
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
@ -115,12 +117,13 @@ describe('core/render', () => {
const mod = createAstroModule(Page); const mod = createAstroModule(Page);
const ctx = await createRenderContext({ const ctx = await createRenderContext({
route: { type: 'page', pathname: '/index' },
request: new Request('http://example.com/'), request: new Request('http://example.com/'),
env, env,
mod, mod,
}); });
const response = await tryRenderRoute('page', ctx, env, mod); const response = await tryRenderRoute(ctx, env, mod);
try { try {
await response.text(); await response.text();