0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-03 22:57:08 -05:00

refactor: singular middleware (#9776)

* per-route middleware -> singular middleware

* add changeset

* emit middleware.mjs only when user middleware exists

* Apply suggestions from code review
This commit is contained in:
Arsh 2024-01-25 19:38:15 +00:00 committed by GitHub
parent b2197a2bee
commit dc75180aa6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 100 additions and 129 deletions

View file

@ -0,0 +1,5 @@
---
"astro": patch
---
Simplifies internals that handle middleware.

View file

@ -18,6 +18,8 @@ export function deserializeManifest(serializedManifest: SerializedSSRManifest):
const clientDirectives = new Map(serializedManifest.clientDirectives);
return {
// in case user middleware exists, this no-op middleware will be reassigned (see plugin-ssr.ts)
middleware(_, next) { return next() },
...serializedManifest,
assets,
componentMetadata,

View file

@ -77,7 +77,7 @@ export interface RenderErrorOptions {
response?: Response;
status: 404 | 500;
/**
* Whether to skip onRequest() while rendering the error page. Defaults to false.
* Whether to skip middleware while rendering the error page. Defaults to false.
*/
skipMiddleware?: boolean;
}
@ -260,16 +260,10 @@ export class App {
this.#manifest.buildFormat
);
if (i18nMiddleware) {
if (mod.onRequest) {
this.#pipeline.setMiddlewareFunction(sequence(i18nMiddleware, mod.onRequest));
} else {
this.#pipeline.setMiddlewareFunction(i18nMiddleware);
}
this.#pipeline.setMiddlewareFunction(sequence(i18nMiddleware, this.#manifest.middleware));
this.#pipeline.onBeforeRenderRoute(i18nPipelineHook);
} else {
if (mod.onRequest) {
this.#pipeline.setMiddlewareFunction(mod.onRequest);
}
this.#pipeline.setMiddlewareFunction(this.#manifest.middleware);
}
response = await this.#pipeline.renderRoute(renderContext, pageModule);
} catch (err: any) {
@ -429,8 +423,8 @@ export class App {
status
);
const page = (await mod.page()) as any;
if (skipMiddleware === false && mod.onRequest) {
this.#pipeline.setMiddlewareFunction(mod.onRequest);
if (skipMiddleware === false) {
this.#pipeline.setMiddlewareFunction(this.#manifest.middleware);
}
if (skipMiddleware) {
// make sure middleware set by other requests is cleared out
@ -440,7 +434,7 @@ export class App {
return this.#mergeResponses(response, originalResponse);
} catch {
// Middleware may be the cause of the error, so we try rendering 404/500.astro without it.
if (skipMiddleware === false && mod.onRequest) {
if (skipMiddleware === false) {
return this.#renderError(request, {
status,
response: originalResponse,

View file

@ -1,5 +1,6 @@
import type {
Locales,
MiddlewareHandler,
RouteData,
SerializedRouteData,
SSRComponentMetadata,
@ -54,6 +55,7 @@ export type SSRManifest = {
pageModule?: SinglePageBuiltModule;
pageMap?: Map<ComponentPath, ImportComponentInstance>;
i18n: SSRManifestI18n | undefined;
middleware: MiddlewareHandler;
};
export type SSRManifestI18n = {
@ -65,7 +67,7 @@ export type SSRManifestI18n = {
export type SerializedSSRManifest = Omit<
SSRManifest,
'routes' | 'assets' | 'componentMetadata' | 'clientDirectives'
'middleware' | 'routes' | 'assets' | 'componentMetadata' | 'clientDirectives'
> & {
routes: SerializedRouteInfo[];
assets: string[];

View file

@ -8,6 +8,7 @@ import type {
AstroSettings,
ComponentInstance,
GetStaticPathsItem,
MiddlewareHandler,
RouteData,
RouteType,
SSRError,
@ -145,10 +146,17 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn
const baseDirectory = getOutputDirectory(opts.settings.config);
const renderersEntryUrl = new URL('renderers.mjs', baseDirectory);
const renderers = await import(renderersEntryUrl.toString());
let middleware: MiddlewareHandler = (_, next) => next();
try {
// middleware.mjs is not emitted if there is no user middleware
// in which case the import fails with ERR_MODULE_NOT_FOUND, and we fall back to a no-op middleware
middleware = await import(new URL('middleware.mjs', baseDirectory).toString()).then(mod => mod.onRequest);
} catch {}
manifest = createBuildManifest(
opts.settings,
internals,
renderers.renderers as SSRLoadedRenderer[]
renderers.renderers as SSRLoadedRenderer[],
middleware
);
}
const pipeline = new BuildPipeline(opts, internals, manifest);
@ -249,8 +257,9 @@ async function generatePage(
// prepare information we need
const logger = pipeline.getLogger();
const config = pipeline.getConfig();
const manifest = pipeline.getManifest();
const pageModulePromise = ssrEntry.page;
const onRequest = ssrEntry.onRequest;
const onRequest = manifest.middleware;
const pageInfo = getPageDataByComponent(pipeline.getInternals(), pageData.route.component);
// Calculate information of the page, like scripts, links and styles
@ -263,19 +272,15 @@ async function generatePage(
const scripts = pageInfo?.hoistedScript ?? null;
// prepare the middleware
const i18nMiddleware = createI18nMiddleware(
pipeline.getManifest().i18n,
pipeline.getManifest().base,
pipeline.getManifest().trailingSlash,
pipeline.getManifest().buildFormat
manifest.i18n,
manifest.base,
manifest.trailingSlash,
manifest.buildFormat
);
if (config.i18n && i18nMiddleware) {
if (onRequest) {
pipeline.setMiddlewareFunction(sequence(i18nMiddleware, onRequest));
} else {
pipeline.setMiddlewareFunction(i18nMiddleware);
}
pipeline.setMiddlewareFunction(sequence(i18nMiddleware, onRequest));
pipeline.onBeforeRenderRoute(i18nPipelineHook);
} else if (onRequest) {
} else {
pipeline.setMiddlewareFunction(onRequest);
}
if (!pageModulePromise) {
@ -629,10 +634,11 @@ function getPrettyRouteName(route: RouteData): string {
* @param settings
* @param renderers
*/
export function createBuildManifest(
function createBuildManifest(
settings: AstroSettings,
internals: BuildInternals,
renderers: SSRLoadedRenderer[]
renderers: SSRLoadedRenderer[],
middleware: MiddlewareHandler
): SSRManifest {
let i18nManifest: SSRManifestI18n | undefined = undefined;
if (settings.config.i18n) {
@ -660,5 +666,6 @@ export function createBuildManifest(
componentMetadata: internals.componentMetadata,
i18n: i18nManifest,
buildFormat: settings.config.build.format,
middleware
};
}

View file

@ -42,22 +42,18 @@ function vitePluginManifest(options: StaticBuildOptions, internals: BuildInterna
},
async load(id) {
if (id === RESOLVED_SSR_MANIFEST_VIRTUAL_MODULE_ID) {
const imports = [];
const contents = [];
const exports = [];
imports.push(
const imports = [
`import { deserializeManifest as _deserializeManifest } from 'astro/app'`,
`import { _privateSetManifestDontUseThis } from 'astro:ssr-manifest'`
);
contents.push(`
const manifest = _deserializeManifest('${manifestReplace}');
_privateSetManifestDontUseThis(manifest);
`);
exports.push('export { manifest }');
return `${imports.join('\n')}${contents.join('\n')}${exports.join('\n')}`;
];
const contents = [
`const manifest = _deserializeManifest('${manifestReplace}');`,
`_privateSetManifestDontUseThis(manifest);`
];
const exports = [
`export { manifest }`
];
return [...imports, ...contents, ...exports].join('\n');
}
},

View file

@ -1,12 +1,10 @@
import { extname } from 'node:path';
import type { Plugin as VitePlugin } from 'vite';
import type { AstroSettings } from '../../../@types/astro.js';
import { routeIsRedirect } from '../../redirects/index.js';
import { addRollupInput } from '../add-rollup-input.js';
import { eachPageFromAllPages, type BuildInternals } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin.js';
import type { StaticBuildOptions } from '../types.js';
import { MIDDLEWARE_MODULE_ID } from './plugin-middleware.js';
import { RENDERERS_MODULE_ID } from './plugin-renderers.js';
import { ASTRO_PAGE_EXTENSION_POST_PATTERN, getPathFromVirtualModulePageName } from './util.js';
@ -37,7 +35,6 @@ export function getVirtualModulePageIdFromPath(path: string) {
function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): VitePlugin {
return {
name: '@astro/plugin-build-pages',
options(options) {
if (opts.settings.config.output === 'static') {
const inputs = new Set<string>();
@ -52,13 +49,11 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V
return addRollupInput(options, Array.from(inputs));
}
},
resolveId(id) {
if (id.startsWith(ASTRO_PAGE_MODULE_ID)) {
return '\0' + id;
}
},
async load(id) {
if (id.startsWith(ASTRO_PAGE_RESOLVED_MODULE_ID)) {
const imports: string[] = [];
@ -74,15 +69,6 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V
imports.push(`import { renderers } from "${RENDERERS_MODULE_ID}";`);
exports.push(`export { renderers };`);
// The middleware should not be imported by the pages
if (shouldBundleMiddleware(opts.settings)) {
const middlewareModule = await this.resolve(MIDDLEWARE_MODULE_ID);
if (middlewareModule) {
imports.push(`import { onRequest } from "${middlewareModule.id}";`);
exports.push(`export { onRequest };`);
}
}
return `${imports.join('\n')}${exports.join('\n')}`;
}
}
@ -91,13 +77,6 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V
};
}
export function shouldBundleMiddleware(settings: AstroSettings) {
if (settings.adapter?.adapterFeatures?.edgeMiddleware === true) {
return false;
}
return true;
}
export function pluginPages(opts: StaticBuildOptions, internals: BuildInternals): AstroBuildPlugin {
return {
targets: ['server'],

View file

@ -1,7 +1,7 @@
import { join } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import type { Plugin as VitePlugin } from 'vite';
import type { AstroAdapter, AstroConfig } from '../../../@types/astro.js';
import type { AstroAdapter } from '../../../@types/astro.js';
import { isFunctionPerRouteEnabled } from '../../../integrations/index.js';
import { isServerLikeOutput } from '../../../prerender/utils.js';
import { routeIsRedirect } from '../../redirects/index.js';
@ -14,6 +14,7 @@ import { SSR_MANIFEST_VIRTUAL_MODULE_ID } from './plugin-manifest.js';
import { ASTRO_PAGE_MODULE_ID } from './plugin-pages.js';
import { RENDERERS_MODULE_ID } from './plugin-renderers.js';
import { getPathFromVirtualModulePageName, getVirtualModulePageNameFromPath } from './util.js';
import { MIDDLEWARE_MODULE_ID } from './plugin-middleware.js';
export const SSR_VIRTUAL_MODULE_ID = '@astrojs-ssr-virtual-entry';
export const RESOLVED_SSR_VIRTUAL_MODULE_ID = '\0' + SSR_VIRTUAL_MODULE_ID;
@ -52,7 +53,7 @@ function vitePluginSSR(
if (module) {
const variable = `_page${i}`;
// we need to use the non-resolved ID in order to resolve correctly the virtual module
imports.push(`const ${variable} = () => import("${virtualModuleName}");`);
imports.push(`const ${variable} = () => import("${virtualModuleName}");`);
const pageData2 = internals.pagesByComponent.get(path);
if (pageData2) {
@ -61,13 +62,13 @@ function vitePluginSSR(
i++;
}
}
contents.push(`const pageMap = new Map([${pageMap.join(',')}]);`);
contents.push(`const pageMap = new Map([\n ${pageMap.join(',\n ')}\n]);`);
exports.push(`export { pageMap }`);
const ssrCode = generateSSRCode(options.settings.config, adapter);
const middleware = await this.resolve(MIDDLEWARE_MODULE_ID);
const ssrCode = generateSSRCode(adapter, middleware!.id);
imports.push(...ssrCode.imports);
contents.push(...ssrCode.contents);
return `${imports.join('\n')}${contents.join('\n')}${exports.join('\n')}`;
return [...imports, ...contents, ...exports].join('\n');
}
return void 0;
},
@ -174,14 +175,14 @@ function vitePluginSSRSplit(
// we need to use the non-resolved ID in order to resolve correctly the virtual module
imports.push(`import * as pageModule from "${virtualModuleName}";`);
}
const ssrCode = generateSSRCode(options.settings.config, adapter);
const middleware = await this.resolve(MIDDLEWARE_MODULE_ID);
const ssrCode = generateSSRCode(adapter, middleware!.id);
imports.push(...ssrCode.imports);
contents.push(...ssrCode.contents);
exports.push('export { pageModule }');
return `${imports.join('\n')}${contents.join('\n')}${exports.join('\n')}`;
return [...imports, ...contents, ...exports].join('\n');
}
return void 0;
},
@ -233,45 +234,36 @@ export function pluginSSRSplit(
};
}
function generateSSRCode(config: AstroConfig, adapter: AstroAdapter) {
const imports: string[] = [];
const contents: string[] = [];
let pageMap;
if (isFunctionPerRouteEnabled(adapter)) {
pageMap = 'pageModule';
} else {
pageMap = 'pageMap';
}
function generateSSRCode(adapter: AstroAdapter, middlewareId: string) {
const edgeMiddleware = adapter?.adapterFeatures?.edgeMiddleware ?? false;
const pageMap = isFunctionPerRouteEnabled(adapter) ? 'pageModule' : 'pageMap';
contents.push(`import * as adapter from '${adapter.serverEntrypoint}';
import { renderers } from '${RENDERERS_MODULE_ID}';
import { manifest as defaultManifest} from '${SSR_MANIFEST_VIRTUAL_MODULE_ID}';
const _manifest = Object.assign(defaultManifest, {
${pageMap},
renderers,
});
const _args = ${adapter.args ? JSON.stringify(adapter.args) : 'undefined'};
const imports = [
`import { renderers } from '${RENDERERS_MODULE_ID}';`,
`import { manifest as defaultManifest } from '${SSR_MANIFEST_VIRTUAL_MODULE_ID}';`,
`import * as serverEntrypointModule from '${adapter.serverEntrypoint}';`,
edgeMiddleware ? `` : `import { onRequest as middleware } from '${middlewareId}';`,
];
const contents = [
edgeMiddleware ? `const middleware = (_, next) => next()` : '',
`const _manifest = Object.assign(defaultManifest, {`,
` ${pageMap},`,
` renderers,`,
` middleware`,
`});`,
`const _args = ${adapter.args ? JSON.stringify(adapter.args, null, 4) : 'undefined'};`,
adapter.exports ? `const _exports = serverEntrypointModule.createExports(_manifest, _args);` : '',
...adapter.exports?.map((name) => {
if (name === 'default') {
return `export default _exports.default;`;
} else {
return `export const ${name} = _exports['${name}'];`;
}
}) ?? [],
`serverEntrypointModule.start?.(_manifest, _args);`,
];
${
adapter.exports
? `const _exports = adapter.createExports(_manifest, _args);
${adapter.exports
.map((name) => {
if (name === 'default') {
return `const _default = _exports['default'];
export { _default as default };`;
} else {
return `export const ${name} = _exports['${name}'];`;
}
})
.join('\n')}
`
: ''
}
const _start = 'start';
if(_start in adapter) {
adapter[_start](_manifest, _args);
}`);
return {
imports,
contents,

View file

@ -8,7 +8,7 @@ import type { StaticBuildOptions } from '../build/types.js';
import { MIDDLEWARE_PATH_SEGMENT_NAME } from '../constants.js';
export const MIDDLEWARE_MODULE_ID = '\0astro-internal:middleware';
const EMPTY_MIDDLEWARE = '\0empty-middleware';
const NOOP_MIDDLEWARE = '\0noop-middleware';
export function vitePluginMiddleware({ settings }: { settings: AstroSettings }): VitePlugin {
let isCommandBuild = false;
@ -19,12 +19,10 @@ export function vitePluginMiddleware({ settings }: { settings: AstroSettings }):
return {
name: '@astro/plugin-middleware',
config(opts, { command }) {
isCommandBuild = command === 'build';
return opts;
},
async resolveId(id) {
if (id === MIDDLEWARE_MODULE_ID) {
const middlewareId = await this.resolve(
@ -37,17 +35,16 @@ export function vitePluginMiddleware({ settings }: { settings: AstroSettings }):
} else if (hasIntegrationMiddleware) {
return MIDDLEWARE_MODULE_ID;
} else {
return EMPTY_MIDDLEWARE;
return NOOP_MIDDLEWARE;
}
}
if (id === EMPTY_MIDDLEWARE) {
return EMPTY_MIDDLEWARE;
if (id === NOOP_MIDDLEWARE) {
return NOOP_MIDDLEWARE;
}
},
async load(id) {
if (id === EMPTY_MIDDLEWARE) {
return 'export const onRequest = undefined';
if (id === NOOP_MIDDLEWARE) {
return 'export const onRequest = (_, next) => next()';
} else if (id === MIDDLEWARE_MODULE_ID) {
// In the build, tell Vite to emit this file
if (isCommandBuild) {
@ -56,7 +53,7 @@ export function vitePluginMiddleware({ settings }: { settings: AstroSettings }):
preserveSignature: 'strict',
fileName: 'middleware.mjs',
id,
});
})
}
const preMiddleware = createMiddlewareImports(settings.middlewares.pre, 'pre');

View file

@ -140,5 +140,8 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest
: settings.config.site,
componentMetadata: new Map(),
i18n: i18nManifest,
middleware(_, next) {
return next()
}
};
}

View file

@ -277,10 +277,8 @@ export async function handleRoute({
pathname,
request,
route,
middleware
};
if (middleware) {
options.middleware = middleware;
}
mod = options.preload;
@ -307,7 +305,7 @@ export async function handleRoute({
});
}
const onRequest = middleware?.onRequest as MiddlewareHandler | undefined;
const onRequest: MiddlewareHandler = middleware.onRequest;
if (config.i18n) {
const i18Middleware = createI18nMiddleware(
config.i18n,
@ -317,16 +315,12 @@ export async function handleRoute({
);
if (i18Middleware) {
if (onRequest) {
pipeline.setMiddlewareFunction(sequence(i18Middleware, onRequest));
} else {
pipeline.setMiddlewareFunction(i18Middleware);
}
pipeline.setMiddlewareFunction(sequence(i18Middleware, onRequest));
pipeline.onBeforeRenderRoute(i18nPipelineHook);
} else if (onRequest) {
} else {
pipeline.setMiddlewareFunction(onRequest);
}
} else if (onRequest) {
} else {
pipeline.setMiddlewareFunction(onRequest);
}