Refactor component and renderers loading flow (#7518)

This commit is contained in:
Bjorn Lu 2023-06-29 21:19:43 +08:00 committed by GitHub
parent 56f1b1229c
commit baaeab0a67
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 43 additions and 82 deletions

View file

@ -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);

View file

@ -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,

View file

@ -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';

View file

@ -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,
};
}

View file

@ -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,
}; };

View file

@ -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,
}; };
} }

View file

@ -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', () => {