diff --git a/.changeset/twenty-papayas-agree.md b/.changeset/twenty-papayas-agree.md new file mode 100644 index 000000000000..980f35987539 --- /dev/null +++ b/.changeset/twenty-papayas-agree.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Simplifies internals that handle middleware. diff --git a/packages/astro/src/core/app/common.ts b/packages/astro/src/core/app/common.ts index 5426d77217cc..c1b445b526ab 100644 --- a/packages/astro/src/core/app/common.ts +++ b/packages/astro/src/core/app/common.ts @@ -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, diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index 6189b00f8efe..67ae8acf5861 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -76,7 +76,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; } @@ -259,16 +259,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) { @@ -428,8 +422,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 @@ -439,7 +433,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, diff --git a/packages/astro/src/core/app/types.ts b/packages/astro/src/core/app/types.ts index b38f51d64f72..b4da7dc6829d 100644 --- a/packages/astro/src/core/app/types.ts +++ b/packages/astro/src/core/app/types.ts @@ -1,5 +1,6 @@ import type { Locales, + MiddlewareHandler, RouteData, SerializedRouteData, SSRComponentMetadata, @@ -54,6 +55,7 @@ export type SSRManifest = { pageModule?: SinglePageBuiltModule; pageMap?: Map; 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[]; diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index e3b93ea44f62..747f8554e6f8 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -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 }; } diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts index 09408e23af36..292a9c962cea 100644 --- a/packages/astro/src/core/build/plugins/plugin-manifest.ts +++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts @@ -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'); } }, diff --git a/packages/astro/src/core/build/plugins/plugin-pages.ts b/packages/astro/src/core/build/plugins/plugin-pages.ts index fee94071ee25..a7ccf44765c9 100644 --- a/packages/astro/src/core/build/plugins/plugin-pages.ts +++ b/packages/astro/src/core/build/plugins/plugin-pages.ts @@ -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(); @@ -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'], diff --git a/packages/astro/src/core/build/plugins/plugin-ssr.ts b/packages/astro/src/core/build/plugins/plugin-ssr.ts index 6a6dd224af4f..81485d18ce45 100644 --- a/packages/astro/src/core/build/plugins/plugin-ssr.ts +++ b/packages/astro/src/core/build/plugins/plugin-ssr.ts @@ -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, diff --git a/packages/astro/src/core/middleware/vite-plugin.ts b/packages/astro/src/core/middleware/vite-plugin.ts index 918c7c9526c5..38f66783f8bf 100644 --- a/packages/astro/src/core/middleware/vite-plugin.ts +++ b/packages/astro/src/core/middleware/vite-plugin.ts @@ -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'); diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts index 4bbd85969e8a..714ca3aac1ae 100644 --- a/packages/astro/src/vite-plugin-astro-server/plugin.ts +++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts @@ -140,5 +140,8 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest : settings.config.site, componentMetadata: new Map(), i18n: i18nManifest, + middleware(_, next) { + return next() + } }; } diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts index 67a2a4baa3af..ecfefb0dc89f 100644 --- a/packages/astro/src/vite-plugin-astro-server/route.ts +++ b/packages/astro/src/vite-plugin-astro-server/route.ts @@ -276,10 +276,8 @@ export async function handleRoute({ pathname, request, route, + middleware }; - if (middleware) { - options.middleware = middleware; - } mod = options.preload; @@ -306,7 +304,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, @@ -316,16 +314,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); }