From 3043f9872356e3869477e3235f1c514ebf4c6766 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 20 Jul 2023 09:04:53 +0100 Subject: [PATCH] refactor: unify `renderPage` and `callEndpoint` in one single function (#7703) * refactor: unify `renderPage` and `callEndpoint` in one single function * chore: lint * don't return the response * chore: update error * rebase --- packages/astro/src/core/app/index.ts | 247 +++++++++--------- packages/astro/src/core/build/generate.ts | 44 ++-- packages/astro/src/core/endpoint/index.ts | 2 +- packages/astro/src/core/render/core.ts | 68 +++-- packages/astro/src/core/render/dev/index.ts | 119 +-------- packages/astro/src/core/render/index.ts | 2 +- .../src/vite-plugin-astro-server/route.ts | 126 ++++++++- packages/astro/test/units/render/head.test.js | 8 +- packages/astro/test/units/render/jsx.test.js | 8 +- 9 files changed, 325 insertions(+), 299 deletions(-) diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index 92f954bc5..02b38bfa6 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -8,7 +8,6 @@ import type { } from '../../@types/astro'; import type { SinglePageBuiltModule } from '../build/types'; import { attachToResponse, getSetCookiesFromResponse } from '../cookies/index.js'; -import { callEndpoint } from '../endpoint/index.js'; import { consoleLogDestination } from '../logger/console.js'; import { error, type LogOptions } from '../logger/core.js'; import { prependForwardSlash, removeTrailingForwardSlash } from '../path.js'; @@ -16,8 +15,9 @@ import { RedirectSinglePageBuiltModule } from '../redirects/index.js'; import { createEnvironment, createRenderContext, - tryRenderPage, + tryRenderRoute, type Environment, + type RenderContext, } from '../render/index.js'; import { RouteCache } from '../render/route-cache.js'; import { @@ -27,6 +27,7 @@ import { } from '../render/ssr-element.js'; import { matchRoute } from '../routing/match.js'; import type { RouteInfo } from './types'; +import { isResponse } from '../render/core'; export { deserializeManifest } from './common.js'; const clientLocalsSymbol = Symbol.for('astro.locals'); @@ -157,30 +158,90 @@ export class App { let mod = await this.#getModuleForRoute(routeData); - if (routeData.type === 'page' || routeData.type === 'redirect') { - let response = await this.#renderPage(request, routeData, mod, defaultStatus); + const pageModule = (await mod.page()) as any; + const url = new URL(request.url); + const renderContext = await this.#createRenderContext( + url, + request, + routeData, + mod, + defaultStatus + ); + let response; + try { + response = await tryRenderRoute( + routeData.type, + renderContext, + this.#env, + pageModule, + mod.onRequest + ); + } catch (err: any) { + error(this.#logging, 'ssr', err.stack || err.message || String(err)); + response = new Response(null, { + status: 500, + statusText: 'Internal server error', + }); + } + + if (isResponse(response, routeData.type)) { // If there was a known error code, try sending the according page (e.g. 404.astro / 500.astro). if (response.status === 500 || response.status === 404) { const errorRouteData = matchRoute('/' + response.status, this.#manifestData); if (errorRouteData && errorRouteData.route !== routeData.route) { mod = await this.#getModuleForRoute(errorRouteData); try { - let errorResponse = await this.#renderPage( + const newRenderContext = await this.#createRenderContext( + url, request, - errorRouteData, + routeData, mod, response.status ); - return errorResponse; + const page = (await mod.page()) as any; + const errorResponse = await tryRenderRoute( + routeData.type, + newRenderContext, + this.#env, + page + ); + return errorResponse as Response; } catch {} } } + Reflect.set(response, responseSentSymbol, true); return response; - } else if (routeData.type === 'endpoint') { - return this.#callEndpoint(request, routeData, mod, defaultStatus); } else { - throw new Error(`Unsupported route type [${routeData.type}].`); + if (response.type === 'response') { + if (response.response.headers.get('X-Astro-Response') === 'Not-Found') { + const fourOhFourRequest = new Request(new URL('/404', request.url)); + const fourOhFourRouteData = this.match(fourOhFourRequest); + if (fourOhFourRouteData) { + return this.render(fourOhFourRequest, fourOhFourRouteData); + } + } + return response.response; + } else { + const body = response.body; + 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 = this.#encoder.encode(body); + headers.set('Content-Length', bytes.byteLength.toString()); + + const newResponse = new Response(bytes, { + status: 200, + headers, + }); + + attachToResponse(newResponse, response.cookies); + return newResponse; + } } } @@ -188,6 +249,64 @@ export class App { return getSetCookiesFromResponse(response); } + /** + * Creates the render context of the current route + */ + async #createRenderContext( + url: URL, + request: Request, + routeData: RouteData, + page: SinglePageBuiltModule, + status = 200 + ): Promise { + if (routeData.type === 'endpoint') { + const pathname = '/' + this.removeBase(url.pathname); + const mod = await page.page(); + const handler = mod as unknown as EndpointHandler; + return await createRenderContext({ + request, + pathname, + route: routeData, + status, + env: this.#env, + mod: handler as any, + }); + } else { + const pathname = prependForwardSlash(this.removeBase(url.pathname)); + const info = this.#routeDataToRouteInfo.get(routeData)!; + // may be used in the future for handling rel=modulepreload, rel=icon, rel=manifest etc. + const links = new Set(); + const styles = createStylesheetElementSet(info.styles); + + let scripts = new Set(); + for (const script of info.scripts) { + if ('stage' in script) { + if (script.stage === 'head-inline') { + scripts.add({ + props: {}, + children: script.children, + }); + } + } else { + scripts.add(createModuleScriptElement(script)); + } + } + const mod = await page.page(); + return await createRenderContext({ + request, + pathname, + componentMetadata: this.#manifest.componentMetadata, + scripts, + styles, + links, + route: routeData, + status, + mod, + env: this.#env, + }); + } + } + async #getModuleForRoute(route: RouteData): Promise { if (route.type === 'redirect') { return RedirectSinglePageBuiltModule; @@ -211,112 +330,4 @@ export class App { } } } - - async #renderPage( - request: Request, - routeData: RouteData, - page: SinglePageBuiltModule, - status = 200 - ): Promise { - const url = new URL(request.url); - const pathname = prependForwardSlash(this.removeBase(url.pathname)); - const info = this.#routeDataToRouteInfo.get(routeData)!; - // may be used in the future for handling rel=modulepreload, rel=icon, rel=manifest etc. - const links = new Set(); - const styles = createStylesheetElementSet(info.styles); - - let scripts = new Set(); - for (const script of info.scripts) { - if ('stage' in script) { - if (script.stage === 'head-inline') { - scripts.add({ - props: {}, - children: script.children, - }); - } - } else { - scripts.add(createModuleScriptElement(script)); - } - } - - try { - const mod = (await page.page()) as any; - const renderContext = await createRenderContext({ - request, - pathname, - componentMetadata: this.#manifest.componentMetadata, - scripts, - styles, - links, - route: routeData, - status, - mod, - env: this.#env, - }); - - const response = await tryRenderPage(renderContext, this.#env, mod, page.onRequest); - - Reflect.set(request, responseSentSymbol, true); - return response; - } catch (err: any) { - error(this.#logging, 'ssr', err.stack || err.message || String(err)); - return new Response(null, { - status: 500, - statusText: 'Internal server error', - }); - } - } - - async #callEndpoint( - request: Request, - routeData: RouteData, - page: SinglePageBuiltModule, - status = 200 - ): Promise { - const url = new URL(request.url); - const pathname = '/' + this.removeBase(url.pathname); - const mod = await page.page(); - const handler = mod as unknown as EndpointHandler; - - const ctx = await createRenderContext({ - request, - pathname, - route: routeData, - status, - env: this.#env, - mod: handler as any, - }); - - const result = await callEndpoint(handler, this.#env, ctx, page.onRequest); - - if (result.type === 'response') { - if (result.response.headers.get('X-Astro-Response') === 'Not-Found') { - const fourOhFourRequest = new Request(new URL('/404', request.url)); - const fourOhFourRouteData = this.match(fourOhFourRequest); - if (fourOhFourRouteData) { - return this.render(fourOhFourRequest, fourOhFourRouteData); - } - } - return result.response; - } else { - const body = result.body; - 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 = this.#encoder.encode(body); - headers.set('Content-Length', bytes.byteLength.toString()); - - const response = new Response(bytes, { - status: 200, - headers, - }); - - attachToResponse(response, result.cookies); - return response; - } - } } diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index 49cc59cea..e4a8a0d2f 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -7,8 +7,6 @@ import type { AstroConfig, AstroSettings, ComponentInstance, - EndpointHandler, - EndpointOutput, GetStaticPathsItem, ImageTransform, MiddlewareHandler, @@ -37,11 +35,10 @@ import { import { runHookBuildGenerated } from '../../integrations/index.js'; import { isServerLikeOutput } from '../../prerender/utils.js'; import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js'; -import { callEndpoint } from '../endpoint/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; import { debug, info } from '../logger/core.js'; import { getRedirectLocationOrThrow, RedirectSinglePageBuiltModule } from '../redirects/index.js'; -import { createEnvironment, createRenderContext, tryRenderPage } from '../render/index.js'; +import { createEnvironment, createRenderContext, tryRenderRoute } from '../render/index.js'; import { callGetStaticPaths } from '../render/route-cache.js'; import { createAssetLink, @@ -65,6 +62,7 @@ import type { StylesheetAsset, } from './types'; import { getTimeStat } from './util.js'; +import { isEndpointResult } from '../render/core.js'; function createEntryURL(filePath: string, outFolder: URL) { return new URL('./' + filePath + `?time=${Date.now()}`, outFolder); @@ -542,36 +540,28 @@ async function generatePath( let body: string | Uint8Array; let encoding: BufferEncoding | undefined; - if (pageData.route.type === 'endpoint') { - const endpointHandler = mod as unknown as EndpointHandler; - const result = await callEndpoint( - endpointHandler, - env, - renderContext, - onRequest as MiddlewareHandler - ); + let response; + try { + response = await tryRenderRoute(pageData.route.type, renderContext, env, mod, onRequest); + } catch (err) { + if (!AstroError.is(err) && !(err as SSRError).id && typeof err === 'object') { + (err as SSRError).id = pageData.component; + } + throw err; + } - if (result.type === 'response') { + if (isEndpointResult(response, pageData.route.type)) { + if (response.type === 'response') { // If there's no body, do nothing - if (!result.response.body) return; - const ab = await result.response.arrayBuffer(); + if (!response.response.body) return; + const ab = await response.response.arrayBuffer(); body = new Uint8Array(ab); } else { - body = result.body; - encoding = result.encoding; + body = response.body; + encoding = response.encoding; } } else { - let response: Response; - try { - response = await tryRenderPage(renderContext, env, mod, onRequest); - } catch (err) { - if (!AstroError.is(err) && !(err as SSRError).id && typeof err === 'object') { - (err as SSRError).id = pageData.component; - } - throw err; - } - if (response.status >= 300 && response.status < 400) { // If redirects is set to false, don't output the HTML if (!opts.settings.config.build.redirects) { diff --git a/packages/astro/src/core/endpoint/index.ts b/packages/astro/src/core/endpoint/index.ts index 9fe3b42ab..392ffa291 100644 --- a/packages/astro/src/core/endpoint/index.ts +++ b/packages/astro/src/core/endpoint/index.ts @@ -17,7 +17,7 @@ import { callMiddleware } from '../middleware/callMiddleware.js'; const clientAddressSymbol = Symbol.for('astro.clientAddress'); const clientLocalsSymbol = Symbol.for('astro.locals'); -type EndpointCallResult = +export type EndpointCallResult = | { type: 'simple'; body: string; diff --git a/packages/astro/src/core/render/core.ts b/packages/astro/src/core/render/core.ts index a4934f277..efd546231 100644 --- a/packages/astro/src/core/render/core.ts +++ b/packages/astro/src/core/render/core.ts @@ -3,15 +3,17 @@ import type { ComponentInstance, MiddlewareHandler, MiddlewareResponseHandler, + RouteType, } from '../../@types/astro'; import { renderPage as runtimeRenderPage } from '../../runtime/server/index.js'; import { attachToResponse } from '../cookies/index.js'; -import { createAPIContext } from '../endpoint/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'; import type { RenderContext } from './context.js'; import type { Environment } from './environment.js'; import { createResult } from './result.js'; +import type { EndpointHandler } from '../../@types/astro'; export type RenderPage = { mod: ComponentInstance; @@ -81,18 +83,22 @@ async function renderPage({ mod, renderContext, env, cookies }: RenderPage) { } /** - * It attempts to render a page. + * 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. */ -export async function tryRenderPage( +export async function tryRenderRoute( + routeType: RouteType, renderContext: Readonly, env: Readonly, mod: Readonly, onRequest?: MiddlewareHandler -): Promise { +): Promise { const apiContext = createAPIContext({ request: renderContext.request, params: renderContext.params, @@ -101,26 +107,50 @@ export async function tryRenderPage( adapterName: env.adapterName, }); - if (onRequest) { - return await callMiddleware( - env.logging, - onRequest as MiddlewareResponseHandler, - apiContext, - () => { - return renderPage({ + switch (routeType) { + 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, }); } - ); - } 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 [${routeType}]`); } } + +export function isEndpointResult(result: any, routeType: RouteType): result is EndpointCallResult { + return !(result instanceof Response) && routeType === 'endpoint'; +} + +export function isResponse(result: any, routeType: RouteType): result is Response { + return result instanceof Response && (routeType === 'page' || routeType === 'redirect'); +} diff --git a/packages/astro/src/core/render/dev/index.ts b/packages/astro/src/core/render/dev/index.ts index 97b3a0c33..40bda5556 100644 --- a/packages/astro/src/core/render/dev/index.ts +++ b/packages/astro/src/core/render/dev/index.ts @@ -1,19 +1,9 @@ -import type { - AstroMiddlewareInstance, - ComponentInstance, - MiddlewareResponseHandler, - RouteData, - SSRElement, -} from '../../../@types/astro'; -import { PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js'; +import type { AstroMiddlewareInstance, ComponentInstance, RouteData } from '../../../@types/astro'; import { enhanceViteSSRError } from '../../errors/dev/index.js'; import { AggregateError, CSSError, MarkdownError } from '../../errors/index.js'; -import { isPage, resolveIdToUrl, viteID } from '../../util.js'; -import { createRenderContext, loadRenderers, tryRenderPage } from '../index.js'; -import { getStylesForURL } from './css.js'; +import { viteID } from '../../util.js'; +import { loadRenderers } from '../index.js'; import type { DevelopmentEnvironment } from './environment'; -import { getComponentMetadata } from './metadata.js'; -import { getScriptsForURL } from './scripts.js'; export { createDevelopmentEnvironment } from './environment.js'; export type { DevelopmentEnvironment }; @@ -63,106 +53,3 @@ export async function preload({ throw enhanceViteSSRError({ error, filePath, loader: env.loader }); } } - -interface GetScriptsAndStylesParams { - env: DevelopmentEnvironment; - filePath: URL; -} - -async function getScriptsAndStyles({ env, filePath }: GetScriptsAndStylesParams) { - // Add hoisted script tags - const scripts = await getScriptsForURL(filePath, env.settings.config.root, env.loader); - - // Inject HMR scripts - if (isPage(filePath, env.settings) && env.mode === 'development') { - scripts.add({ - props: { type: 'module', src: '/@vite/client' }, - children: '', - }); - scripts.add({ - props: { - type: 'module', - src: await resolveIdToUrl(env.loader, 'astro/runtime/client/hmr.js'), - }, - children: '', - }); - } - - // TODO: We should allow adding generic HTML elements to the head, not just scripts - for (const script of env.settings.scripts) { - if (script.stage === 'head-inline') { - scripts.add({ - props: {}, - children: script.content, - }); - } else if (script.stage === 'page' && isPage(filePath, env.settings)) { - scripts.add({ - props: { type: 'module', src: `/@id/${PAGE_SCRIPT_ID}` }, - children: '', - }); - } - } - - // Pass framework CSS in as style tags to be appended to the page. - const { urls: styleUrls, stylesMap } = await getStylesForURL(filePath, env.loader, env.mode); - let links = new Set(); - [...styleUrls].forEach((href) => { - links.add({ - props: { - rel: 'stylesheet', - href, - }, - children: '', - }); - }); - - let styles = new Set(); - [...stylesMap].forEach(([url, content]) => { - // Vite handles HMR for styles injected as scripts - scripts.add({ - props: { - type: 'module', - src: url, - }, - children: '', - }); - // But we still want to inject the styles to avoid FOUC - styles.add({ - props: { - type: 'text/css', - // Track the ID so we can match it to Vite's injected style later - 'data-astro-dev-id': viteID(new URL(`.${url}`, env.settings.config.root)), - }, - children: content, - }); - }); - - const metadata = await getComponentMetadata(filePath, env.loader); - - return { scripts, styles, links, metadata }; -} - -export async function renderPage(options: SSROptions): Promise { - const mod = options.preload; - - const { scripts, links, styles, metadata } = await getScriptsAndStyles({ - env: options.env, - filePath: options.filePath, - }); - const { env } = options; - - const renderContext = await createRenderContext({ - request: options.request, - pathname: options.pathname, - scripts, - links, - styles, - componentMetadata: metadata, - route: options.route, - mod, - env, - }); - const onRequest = options.middleware?.onRequest as MiddlewareResponseHandler | undefined; - - return tryRenderPage(renderContext, env, mod, onRequest); -} diff --git a/packages/astro/src/core/render/index.ts b/packages/astro/src/core/render/index.ts index 410998d01..cd0aedbb2 100644 --- a/packages/astro/src/core/render/index.ts +++ b/packages/astro/src/core/render/index.ts @@ -1,6 +1,6 @@ export { createRenderContext } from './context.js'; export type { RenderContext } from './context.js'; -export { tryRenderPage } from './core.js'; +export { tryRenderRoute } from './core.js'; export type { Environment } from './environment'; export { createEnvironment } from './environment.js'; export { getParamsAndProps } from './params-and-props.js'; diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts index 3571152d3..c4aff9d2b 100644 --- a/packages/astro/src/vite-plugin-astro-server/route.ts +++ b/packages/astro/src/vite-plugin-astro-server/route.ts @@ -1,20 +1,31 @@ import mime from 'mime'; import type http from 'node:http'; -import type { ComponentInstance, ManifestData, RouteData, SSRManifest } from '../@types/astro'; +import type { + ComponentInstance, + ManifestData, + MiddlewareResponseHandler, + RouteData, + SSRElement, + SSRManifest, +} from '../@types/astro'; import { attachToResponse } from '../core/cookies/index.js'; -import { call as callEndpoint } from '../core/endpoint/dev/index.js'; import { AstroErrorData, isAstroError } from '../core/errors/index.js'; import { warn } from '../core/logger/core.js'; import { loadMiddleware } from '../core/middleware/loadMiddleware.js'; import type { DevelopmentEnvironment, SSROptions } from '../core/render/dev/index'; -import { preload, renderPage } from '../core/render/dev/index.js'; -import { getParamsAndProps } from '../core/render/index.js'; +import { preload } from '../core/render/dev/index.js'; +import { createRenderContext, getParamsAndProps, tryRenderRoute } from '../core/render/index.js'; import { createRequest } from '../core/request.js'; import { matchAllRoutes } from '../core/routing/index.js'; import { getSortedPreloadedMatches } from '../prerender/routing.js'; import { isServerLikeOutput } from '../prerender/utils.js'; import { log404 } from './common.js'; import { handle404Response, writeSSRResult, writeWebResponse } from './response.js'; +import { getScriptsForURL } from '../core/render/dev/scripts.js'; +import { isPage, resolveIdToUrl, viteID } from '../core/util.js'; +import { PAGE_SCRIPT_ID } from '../vite-plugin-scripts/index.js'; +import { getStylesForURL } from '../core/render/dev/css.js'; +import { getComponentMetadata } from '../core/render/dev/metadata.js'; const clientLocalsSymbol = Symbol.for('astro.locals'); @@ -179,9 +190,28 @@ export async function handleRoute({ if (middleware) { options.middleware = middleware; } - // Route successfully matched! Render it. - if (route.type === 'endpoint') { - const result = await callEndpoint(options); + const mod = options.preload; + + const { scripts, links, styles, metadata } = await getScriptsAndStyles({ + env: options.env, + filePath: options.filePath, + }); + + const renderContext = await createRenderContext({ + request: options.request, + pathname: options.pathname, + scripts, + links, + styles, + componentMetadata: metadata, + route: options.route, + mod, + env, + }); + const onRequest = options.middleware?.onRequest as MiddlewareResponseHandler | undefined; + + const result = await tryRenderRoute(route.type, renderContext, env, mod, onRequest); + if (route.type === 'endpoint' && !(result instanceof Response)) { if (result.type === 'response') { if (result.response.headers.get('X-Astro-Response') === 'Not-Found') { const fourOhFourRoute = await matchRoute('/404', env, manifestData); @@ -219,8 +249,7 @@ export async function handleRoute({ attachToResponse(response, result.cookies); await writeWebResponse(incomingResponse, response); } - } else { - const result = await renderPage(options); + } else if (result instanceof Response) { if (result.status === 404) { const fourOhFourRoute = await matchRoute('/404', env, manifestData); return handleRoute({ @@ -245,6 +274,85 @@ export async function handleRoute({ } await writeSSRResult(request, response, incomingResponse); } + // unreachable +} + +interface GetScriptsAndStylesParams { + env: DevelopmentEnvironment; + filePath: URL; +} + +async function getScriptsAndStyles({ env, filePath }: GetScriptsAndStylesParams) { + // Add hoisted script tags + const scripts = await getScriptsForURL(filePath, env.settings.config.root, env.loader); + + // Inject HMR scripts + if (isPage(filePath, env.settings) && env.mode === 'development') { + scripts.add({ + props: { type: 'module', src: '/@vite/client' }, + children: '', + }); + scripts.add({ + props: { + type: 'module', + src: await resolveIdToUrl(env.loader, 'astro/runtime/client/hmr.js'), + }, + children: '', + }); + } + + // TODO: We should allow adding generic HTML elements to the head, not just scripts + for (const script of env.settings.scripts) { + if (script.stage === 'head-inline') { + scripts.add({ + props: {}, + children: script.content, + }); + } else if (script.stage === 'page' && isPage(filePath, env.settings)) { + scripts.add({ + props: { type: 'module', src: `/@id/${PAGE_SCRIPT_ID}` }, + children: '', + }); + } + } + + // Pass framework CSS in as style tags to be appended to the page. + const { urls: styleUrls, stylesMap } = await getStylesForURL(filePath, env.loader, env.mode); + let links = new Set(); + [...styleUrls].forEach((href) => { + links.add({ + props: { + rel: 'stylesheet', + href, + }, + children: '', + }); + }); + + let styles = new Set(); + [...stylesMap].forEach(([url, content]) => { + // Vite handles HMR for styles injected as scripts + scripts.add({ + props: { + type: 'module', + src: url, + }, + children: '', + }); + // But we still want to inject the styles to avoid FOUC + styles.add({ + props: { + type: 'text/css', + // Track the ID so we can match it to Vite's injected style later + 'data-astro-dev-id': viteID(new URL(`.${url}`, env.settings.config.root)), + }, + children: content, + }); + }); + + const metadata = await getComponentMetadata(filePath, env.loader); + + return { scripts, styles, links, metadata }; } function getStatus(matchedRoute?: MatchedRoute): number | undefined { diff --git a/packages/astro/test/units/render/head.test.js b/packages/astro/test/units/render/head.test.js index da4863fb1..fbd16be31 100644 --- a/packages/astro/test/units/render/head.test.js +++ b/packages/astro/test/units/render/head.test.js @@ -9,7 +9,7 @@ import { renderHead, Fragment, } from '../../../dist/runtime/server/index.js'; -import { createRenderContext, tryRenderPage } from '../../../dist/core/render/index.js'; +import { createRenderContext, tryRenderRoute } from '../../../dist/core/render/index.js'; import { createBasicEnvironment } from '../test-utils.js'; import * as cheerio from 'cheerio'; @@ -96,7 +96,7 @@ describe('core/render', () => { env, }); - const response = await tryRenderPage(ctx, env, PageModule); + const response = await tryRenderRoute('page', ctx, env, PageModule); const html = await response.text(); const $ = cheerio.load(html); @@ -176,7 +176,7 @@ describe('core/render', () => { mod: PageModule, }); - const response = await tryRenderPage(ctx, env, PageModule); + const response = await tryRenderRoute('page', ctx, env, PageModule); const html = await response.text(); const $ = cheerio.load(html); @@ -222,7 +222,7 @@ describe('core/render', () => { mod: PageModule, }); - const response = await tryRenderPage(ctx, env, PageModule); + const response = await tryRenderRoute('page', 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 61e5ee803..9be135fc0 100644 --- a/packages/astro/test/units/render/jsx.test.js +++ b/packages/astro/test/units/render/jsx.test.js @@ -8,7 +8,7 @@ import { import { jsx } from '../../../dist/jsx-runtime/index.js'; import { createRenderContext, - tryRenderPage, + tryRenderRoute, loadRenderer, } from '../../../dist/core/render/index.js'; import { createAstroJSXComponent, renderer as jsxRenderer } from '../../../dist/jsx/index.js'; @@ -50,7 +50,7 @@ describe('core/render', () => { mod, }); - const response = await tryRenderPage(ctx, env, mod); + const response = await tryRenderRoute('page', ctx, env, mod); expect(response.status).to.equal(200); @@ -94,7 +94,7 @@ describe('core/render', () => { env, mod, }); - const response = await tryRenderPage(ctx, env, mod); + const response = await tryRenderRoute('page', ctx, env, mod); expect(response.status).to.equal(200); @@ -120,7 +120,7 @@ describe('core/render', () => { mod, }); - const response = await tryRenderPage(ctx, env, mod); + const response = await tryRenderRoute('page', ctx, env, mod); try { await response.text();