Refactor component and renderers loading flow (#7518)
This commit is contained in:
parent
56f1b1229c
commit
baaeab0a67
7 changed files with 43 additions and 82 deletions
|
@ -5,12 +5,8 @@ import { createRenderContext } from '../../render/index.js';
|
||||||
import { callEndpoint } from '../index.js';
|
import { callEndpoint } from '../index.js';
|
||||||
|
|
||||||
export async function call(options: SSROptions, logging: LogOptions) {
|
export async function call(options: SSROptions, logging: LogOptions) {
|
||||||
const {
|
const { env, preload, middleware } = options;
|
||||||
env,
|
const endpointHandler = preload as unknown as EndpointHandler;
|
||||||
preload: [, mod],
|
|
||||||
middleware,
|
|
||||||
} = options;
|
|
||||||
const endpointHandler = mod as unknown as EndpointHandler;
|
|
||||||
|
|
||||||
const ctx = await createRenderContext({
|
const ctx = await createRenderContext({
|
||||||
request: options.request,
|
request: options.request,
|
||||||
|
@ -18,7 +14,7 @@ export async function call(options: SSROptions, logging: LogOptions) {
|
||||||
pathname: options.pathname,
|
pathname: options.pathname,
|
||||||
route: options.route,
|
route: options.route,
|
||||||
env,
|
env,
|
||||||
mod: endpointHandler as any,
|
mod: preload,
|
||||||
});
|
});
|
||||||
|
|
||||||
return await callEndpoint(endpointHandler, env, ctx, logging, middleware?.onRequest);
|
return await callEndpoint(endpointHandler, env, ctx, logging, middleware?.onRequest);
|
||||||
|
|
|
@ -5,17 +5,14 @@ import type {
|
||||||
MiddlewareResponseHandler,
|
MiddlewareResponseHandler,
|
||||||
RouteData,
|
RouteData,
|
||||||
SSRElement,
|
SSRElement,
|
||||||
SSRLoadedRenderer,
|
|
||||||
} from '../../../@types/astro';
|
} from '../../../@types/astro';
|
||||||
import { PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
|
import { PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
|
||||||
import { createAPIContext } from '../../endpoint/index.js';
|
import { createAPIContext } from '../../endpoint/index.js';
|
||||||
import { enhanceViteSSRError } from '../../errors/dev/index.js';
|
import { enhanceViteSSRError } from '../../errors/dev/index.js';
|
||||||
import { AggregateError, CSSError, MarkdownError } from '../../errors/index.js';
|
import { AggregateError, CSSError, MarkdownError } from '../../errors/index.js';
|
||||||
import { callMiddleware } from '../../middleware/callMiddleware.js';
|
import { callMiddleware } from '../../middleware/callMiddleware.js';
|
||||||
import type { ModuleLoader } from '../../module-loader/index';
|
|
||||||
import { isPage, resolveIdToUrl, viteID } from '../../util.js';
|
import { isPage, resolveIdToUrl, viteID } from '../../util.js';
|
||||||
import { createRenderContext, renderPage as coreRenderPage } from '../index.js';
|
import { createRenderContext, renderPage as coreRenderPage, loadRenderers } from '../index.js';
|
||||||
import { filterFoundRenderers, loadRenderer } from '../renderer.js';
|
|
||||||
import { getStylesForURL } from './css.js';
|
import { getStylesForURL } from './css.js';
|
||||||
import type { DevelopmentEnvironment } from './environment';
|
import type { DevelopmentEnvironment } from './environment';
|
||||||
import { getComponentMetadata } from './metadata.js';
|
import { getComponentMetadata } from './metadata.js';
|
||||||
|
@ -32,8 +29,8 @@ export interface SSROptions {
|
||||||
origin: string;
|
origin: string;
|
||||||
/** the web request (needed for dynamic routes) */
|
/** the web request (needed for dynamic routes) */
|
||||||
pathname: string;
|
pathname: string;
|
||||||
/** The renderers and instance */
|
/** The runtime component instance */
|
||||||
preload: ComponentPreload;
|
preload: ComponentInstance;
|
||||||
/** 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 */
|
||||||
|
@ -44,36 +41,31 @@ export interface SSROptions {
|
||||||
middleware?: AstroMiddlewareInstance<unknown>;
|
middleware?: AstroMiddlewareInstance<unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ComponentPreload = [SSRLoadedRenderer[], ComponentInstance];
|
|
||||||
|
|
||||||
export async function loadRenderers(
|
|
||||||
moduleLoader: ModuleLoader,
|
|
||||||
settings: AstroSettings
|
|
||||||
): Promise<SSRLoadedRenderer[]> {
|
|
||||||
const loader = (entry: string) => moduleLoader.import(entry);
|
|
||||||
const renderers = await Promise.all(settings.renderers.map((r) => loadRenderer(r, loader)));
|
|
||||||
return filterFoundRenderers(renderers);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function preload({
|
export async function preload({
|
||||||
env,
|
env,
|
||||||
filePath,
|
filePath,
|
||||||
}: Pick<SSROptions, 'env' | 'filePath'>): Promise<ComponentPreload> {
|
}: {
|
||||||
|
env: DevelopmentEnvironment;
|
||||||
|
filePath: URL;
|
||||||
|
}): Promise<ComponentInstance> {
|
||||||
// Important: This needs to happen first, in case a renderer provides polyfills.
|
// Important: This needs to happen first, in case a renderer provides polyfills.
|
||||||
const renderers = await loadRenderers(env.loader, env.settings);
|
const renderers = await loadRenderers(env.settings, env.loader);
|
||||||
|
// Override the environment's renderers. This ensures that if renderers change (HMR)
|
||||||
|
// The new instances are passed through.
|
||||||
|
env.renderers = renderers;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Load the module from the Vite SSR Runtime.
|
// Load the module from the Vite SSR Runtime.
|
||||||
const mod = (await env.loader.import(viteID(filePath))) as ComponentInstance;
|
const mod = (await env.loader.import(viteID(filePath))) as ComponentInstance;
|
||||||
|
|
||||||
return [renderers, mod];
|
return mod;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If the error came from Markdown or CSS, we already handled it and there's no need to enhance it
|
// If the error came from Markdown or CSS, we already handled it and there's no need to enhance it
|
||||||
if (MarkdownError.is(error) || CSSError.is(error) || AggregateError.is(error)) {
|
if (MarkdownError.is(error) || CSSError.is(error) || AggregateError.is(error)) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw enhanceViteSSRError({ error, filePath, loader: env.loader, renderers });
|
throw enhanceViteSSRError({ error, filePath, loader: env.loader });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,11 +148,7 @@ async function getScriptsAndStyles({ env, filePath }: GetScriptsAndStylesParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function renderPage(options: SSROptions): Promise<Response> {
|
export async function renderPage(options: SSROptions): Promise<Response> {
|
||||||
const [renderers, mod] = options.preload;
|
const mod = options.preload;
|
||||||
|
|
||||||
// Override the environment's renderers. This ensures that if renderers change (HMR)
|
|
||||||
// The new instances are passed through.
|
|
||||||
options.env.renderers = renderers;
|
|
||||||
|
|
||||||
const { scripts, links, styles, metadata } = await getScriptsAndStyles({
|
const { scripts, links, styles, metadata } = await getScriptsAndStyles({
|
||||||
env: options.env,
|
env: options.env,
|
||||||
|
|
|
@ -8,4 +8,4 @@ export {
|
||||||
} from './core.js';
|
} from './core.js';
|
||||||
export type { Environment } from './environment';
|
export type { Environment } from './environment';
|
||||||
export { createBasicEnvironment, createEnvironment } from './environment.js';
|
export { createBasicEnvironment, createEnvironment } from './environment.js';
|
||||||
export { loadRenderer } from './renderer.js';
|
export { loadRenderer, loadRenderers } from './renderer.js';
|
||||||
|
|
|
@ -1,36 +1,24 @@
|
||||||
import type { AstroRenderer, SSRLoadedRenderer } from '../../@types/astro';
|
import type { AstroRenderer, AstroSettings, SSRLoadedRenderer } from '../../@types/astro';
|
||||||
|
import type { ModuleLoader } from '../module-loader/index.js';
|
||||||
|
|
||||||
export type RendererServerEntrypointModule = {
|
export async function loadRenderers(
|
||||||
default: SSRLoadedRenderer['ssr'];
|
settings: AstroSettings,
|
||||||
};
|
moduleLoader: ModuleLoader
|
||||||
export type MaybeRendererServerEntrypointModule = Partial<RendererServerEntrypointModule>;
|
): Promise<SSRLoadedRenderer[]> {
|
||||||
export type RendererLoader = (entryPoint: string) => Promise<MaybeRendererServerEntrypointModule>;
|
const renderers = await Promise.all(settings.renderers.map((r) => loadRenderer(r, moduleLoader)));
|
||||||
|
return renderers.filter(Boolean) as SSRLoadedRenderer[];
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadRenderer(
|
export async function loadRenderer(
|
||||||
renderer: AstroRenderer,
|
renderer: AstroRenderer,
|
||||||
loader: RendererLoader
|
moduleLoader: ModuleLoader
|
||||||
): Promise<SSRLoadedRenderer | undefined> {
|
): Promise<SSRLoadedRenderer | undefined> {
|
||||||
const mod = await loader(renderer.serverEntrypoint);
|
const mod = await moduleLoader.import(renderer.serverEntrypoint);
|
||||||
if (typeof mod.default !== 'undefined') {
|
if (typeof mod.default !== 'undefined') {
|
||||||
return createLoadedRenderer(renderer, mod as RendererServerEntrypointModule);
|
return {
|
||||||
|
...renderer,
|
||||||
|
ssr: mod.default,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterFoundRenderers(
|
|
||||||
renderers: Array<SSRLoadedRenderer | undefined>
|
|
||||||
): SSRLoadedRenderer[] {
|
|
||||||
return renderers.filter((renderer): renderer is SSRLoadedRenderer => {
|
|
||||||
return !!renderer;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createLoadedRenderer(
|
|
||||||
renderer: AstroRenderer,
|
|
||||||
mod: RendererServerEntrypointModule
|
|
||||||
): SSRLoadedRenderer {
|
|
||||||
return {
|
|
||||||
...renderer,
|
|
||||||
ssr: mod.default,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import type { AstroSettings, RouteData } from '../@types/astro';
|
import type { AstroSettings, ComponentInstance, RouteData } from '../@types/astro';
|
||||||
import { RedirectComponentInstance, routeIsRedirect } from '../core/redirects/index.js';
|
import { RedirectComponentInstance, routeIsRedirect } from '../core/redirects/index.js';
|
||||||
import {
|
import { preload, type DevelopmentEnvironment } from '../core/render/dev/index.js';
|
||||||
preload,
|
|
||||||
type ComponentPreload,
|
|
||||||
type DevelopmentEnvironment,
|
|
||||||
} from '../core/render/dev/index.js';
|
|
||||||
import { getPrerenderStatus } from './metadata.js';
|
import { getPrerenderStatus } from './metadata.js';
|
||||||
|
|
||||||
type GetSortedPreloadedMatchesParams = {
|
type GetSortedPreloadedMatchesParams = {
|
||||||
|
@ -35,7 +31,7 @@ type PreloadAndSetPrerenderStatusParams = {
|
||||||
type PreloadAndSetPrerenderStatusResult = {
|
type PreloadAndSetPrerenderStatusResult = {
|
||||||
filePath: URL;
|
filePath: URL;
|
||||||
route: RouteData;
|
route: RouteData;
|
||||||
preloadedComponent: ComponentPreload;
|
preloadedComponent: ComponentInstance;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function preloadAndSetPrerenderStatus({
|
async function preloadAndSetPrerenderStatus({
|
||||||
|
@ -48,9 +44,8 @@ async function preloadAndSetPrerenderStatus({
|
||||||
const filePath = new URL(`./${route.component}`, settings.config.root);
|
const filePath = new URL(`./${route.component}`, settings.config.root);
|
||||||
|
|
||||||
if (routeIsRedirect(route)) {
|
if (routeIsRedirect(route)) {
|
||||||
const preloadedComponent: ComponentPreload = [[], RedirectComponentInstance];
|
|
||||||
return {
|
return {
|
||||||
preloadedComponent,
|
preloadedComponent: RedirectComponentInstance,
|
||||||
route,
|
route,
|
||||||
filePath,
|
filePath,
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,11 +7,7 @@ import { throwIfRedirectNotAllowed } from '../core/endpoint/index.js';
|
||||||
import { AstroErrorData } from '../core/errors/index.js';
|
import { AstroErrorData } 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';
|
||||||
import type {
|
import type { DevelopmentEnvironment, SSROptions } from '../core/render/dev/index';
|
||||||
ComponentPreload,
|
|
||||||
DevelopmentEnvironment,
|
|
||||||
SSROptions,
|
|
||||||
} from '../core/render/dev/index';
|
|
||||||
import { preload, renderPage } from '../core/render/dev/index.js';
|
import { preload, renderPage } from '../core/render/dev/index.js';
|
||||||
import { getParamsAndProps, GetParamsAndPropsError } from '../core/render/index.js';
|
import { getParamsAndProps, GetParamsAndPropsError } from '../core/render/index.js';
|
||||||
import { createRequest } from '../core/request.js';
|
import { createRequest } from '../core/request.js';
|
||||||
|
@ -33,7 +29,7 @@ interface MatchedRoute {
|
||||||
route: RouteData;
|
route: RouteData;
|
||||||
filePath: URL;
|
filePath: URL;
|
||||||
resolvedPathname: string;
|
resolvedPathname: string;
|
||||||
preloadedComponent: ComponentPreload;
|
preloadedComponent: ComponentInstance;
|
||||||
mod: ComponentInstance;
|
mod: ComponentInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,9 +50,8 @@ export async function matchRoute(
|
||||||
for await (const { preloadedComponent, route: maybeRoute, filePath } of preloadedMatches) {
|
for await (const { preloadedComponent, route: maybeRoute, filePath } of preloadedMatches) {
|
||||||
// attempt to get static paths
|
// attempt to get static paths
|
||||||
// if this fails, we have a bad URL match!
|
// if this fails, we have a bad URL match!
|
||||||
const [, mod] = preloadedComponent;
|
|
||||||
const paramsAndPropsRes = await getParamsAndProps({
|
const paramsAndPropsRes = await getParamsAndProps({
|
||||||
mod,
|
mod: preloadedComponent,
|
||||||
route: maybeRoute,
|
route: maybeRoute,
|
||||||
routeCache,
|
routeCache,
|
||||||
pathname: pathname,
|
pathname: pathname,
|
||||||
|
@ -70,7 +65,7 @@ export async function matchRoute(
|
||||||
filePath,
|
filePath,
|
||||||
resolvedPathname: pathname,
|
resolvedPathname: pathname,
|
||||||
preloadedComponent,
|
preloadedComponent,
|
||||||
mod,
|
mod: preloadedComponent,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,14 +96,13 @@ export async function matchRoute(
|
||||||
if (custom404) {
|
if (custom404) {
|
||||||
const filePath = new URL(`./${custom404.component}`, settings.config.root);
|
const filePath = new URL(`./${custom404.component}`, settings.config.root);
|
||||||
const preloadedComponent = await preload({ env, filePath });
|
const preloadedComponent = await preload({ env, filePath });
|
||||||
const [, mod] = preloadedComponent;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
route: custom404,
|
route: custom404,
|
||||||
filePath,
|
filePath,
|
||||||
resolvedPathname: pathname,
|
resolvedPathname: pathname,
|
||||||
preloadedComponent,
|
preloadedComponent,
|
||||||
mod,
|
mod: preloadedComponent,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { createAstroJSXComponent, renderer as jsxRenderer } from '../../../dist/
|
||||||
import { defaultLogging as logging } from '../../test-utils.js';
|
import { defaultLogging as logging } from '../../test-utils.js';
|
||||||
|
|
||||||
const createAstroModule = (AstroComponent) => ({ default: AstroComponent });
|
const createAstroModule = (AstroComponent) => ({ default: AstroComponent });
|
||||||
const loadJSXRenderer = () => loadRenderer(jsxRenderer, (s) => import(s));
|
const loadJSXRenderer = () => loadRenderer(jsxRenderer, { import: (s) => import(s) });
|
||||||
|
|
||||||
describe('core/render', () => {
|
describe('core/render', () => {
|
||||||
describe('Astro JSX components', () => {
|
describe('Astro JSX components', () => {
|
||||||
|
|
Loading…
Reference in a new issue