summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Emanuele Stoppa <my.burning@gmail.com> 2023-07-20 09:04:53 +0100
committerGravatar GitHub <noreply@github.com> 2023-07-20 09:04:53 +0100
commit3043f9872356e3869477e3235f1c514ebf4c6766 (patch)
treeb93d7bf0aecf9f8dae4562148195f54caddc4a84
parent30cdc2805700b8a9bf2e20c67b01e96fb58b95f4 (diff)
downloadastro-3043f9872356e3869477e3235f1c514ebf4c6766.tar.gz
astro-3043f9872356e3869477e3235f1c514ebf4c6766.tar.zst
astro-3043f9872356e3869477e3235f1c514ebf4c6766.zip
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
-rw-r--r--packages/astro/src/core/app/index.ts241
-rw-r--r--packages/astro/src/core/build/generate.ts46
-rw-r--r--packages/astro/src/core/endpoint/index.ts2
-rw-r--r--packages/astro/src/core/render/core.ts68
-rw-r--r--packages/astro/src/core/render/dev/index.ts119
-rw-r--r--packages/astro/src/core/render/index.ts2
-rw-r--r--packages/astro/src/vite-plugin-astro-server/route.ts126
-rw-r--r--packages/astro/test/units/render/head.test.js8
-rw-r--r--packages/astro/test/units/render/jsx.test.js8
9 files changed, 323 insertions, 297 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,60 +249,50 @@ export class App {
return getSetCookiesFromResponse(response);
}
- async #getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule> {
- if (route.type === 'redirect') {
- return RedirectSinglePageBuiltModule;
- } else {
- if (this.#manifest.pageMap) {
- const importComponentInstance = this.#manifest.pageMap.get(route.component);
- if (!importComponentInstance) {
- throw new Error(
- `Unexpectedly unable to find a component instance for route ${route.route}`
- );
- }
- const pageModule = await importComponentInstance();
- return pageModule;
- } else if (this.#manifest.pageModule) {
- const importComponentInstance = this.#manifest.pageModule;
- return importComponentInstance;
- } else {
- throw new Error(
- "Astro couldn't find the correct page to render, probably because it wasn't correctly mapped for SSR usage. This is an internal error, please file an issue."
- );
- }
- }
- }
-
- async #renderPage(
+ /**
+ * Creates the render context of the current route
+ */
+ async #createRenderContext(
+ url: URL,
request: Request,
routeData: RouteData,
page: SinglePageBuiltModule,
status = 200
- ): Promise<Response> {
- 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<never>();
- const styles = createStylesheetElementSet(info.styles);
+ ): Promise<RenderContext> {
+ 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<never>();
+ const styles = createStylesheetElementSet(info.styles);
- let scripts = new Set<SSRElement>();
- for (const script of info.scripts) {
- if ('stage' in script) {
- if (script.stage === 'head-inline') {
- scripts.add({
- props: {},
- children: script.children,
- });
+ let scripts = new Set<SSRElement>();
+ 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));
}
- } else {
- scripts.add(createModuleScriptElement(script));
}
- }
-
- try {
- const mod = (await page.page()) as any;
- const renderContext = await createRenderContext({
+ const mod = await page.page();
+ return await createRenderContext({
request,
pathname,
componentMetadata: this.#manifest.componentMetadata,
@@ -253,70 +304,30 @@ export class App {
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<Response> {
- 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;
+ async #getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule> {
+ if (route.type === 'redirect') {
+ return RedirectSinglePageBuiltModule;
} 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`);
+ if (this.#manifest.pageMap) {
+ const importComponentInstance = this.#manifest.pageMap.get(route.component);
+ if (!importComponentInstance) {
+ throw new Error(
+ `Unexpectedly unable to find a component instance for route ${route.route}`
+ );
+ }
+ const pageModule = await importComponentInstance();
+ return pageModule;
+ } else if (this.#manifest.pageModule) {
+ const importComponentInstance = this.#manifest.pageModule;
+ return importComponentInstance;
} else {
- headers.set('Content-Type', 'text/plain;charset=utf-8');
+ throw new Error(
+ "Astro couldn't find the correct page to render, probably because it wasn't correctly mapped for SSR usage. This is an internal error, please file an issue."
+ );
}
- 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<Response | EndpointOutput>
- );
- if (result.type === 'response') {
+ 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 (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<MiddlewareReturnType = Response>(
+export async function tryRenderRoute<MiddlewareReturnType = Response>(
+ routeType: RouteType,
renderContext: Readonly<RenderContext>,
env: Readonly<Environment>,
mod: Readonly<ComponentInstance>,
onRequest?: MiddlewareHandler<MiddlewareReturnType>
-): Promise<Response> {
+): Promise<Response | EndpointCallResult> {
const apiContext = createAPIContext({
request: renderContext.request,
params: renderContext.params,
@@ -101,26 +107,50 @@ export async function tryRenderPage<MiddlewareReturnType = Response>(
adapterName: env.adapterName,
});
- if (onRequest) {
- return await callMiddleware<Response>(
- env.logging,
- onRequest as MiddlewareResponseHandler,
- apiContext,
- () => {
- return renderPage({
+ switch (routeType) {
+ 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,
});
}
- );
- } 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<SSRElement>();
- [...styleUrls].forEach((href) => {
- links.add({
- props: {
- rel: 'stylesheet',
- href,
- },
- children: '',
- });
- });
-
- let styles = new Set<SSRElement>();
- [...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<Response> {
- 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<SSRElement>();
+ [...styleUrls].forEach((href) => {
+ links.add({
+ props: {
+ rel: 'stylesheet',
+ href,
+ },
+ children: '',
+ });
+ });
+
+ let styles = new Set<SSRElement>();
+ [...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();