UNPKG

astro

Version:

Astro is a modern site builder with web best practices, performance, and DX front-of-mind.

127 lines (125 loc) • 5.59 kB
import { addRolldownInput } from "../build/add-rolldown-input.js"; import { pathToDirectoryURL } from "../build/common.js"; import { ASTRO_VITE_ENVIRONMENT_NAMES, MIDDLEWARE_PATH_SEGMENT_NAME } from "../constants.js"; import { MissingMiddlewareForInternationalization } from "../errors/errors-data.js"; import { AstroError } from "../errors/index.js"; import { normalizePath } from "../viteUtils.js"; import { isAstroServerEnvironment } from "../../environments.js"; // This module name is used in Cloudflare's optimizedDeps configuration, // if th name changes that needs to be updated as well. export const MIDDLEWARE_MODULE_ID = "virtual:astro:middleware"; const MIDDLEWARE_RESOLVED_MODULE_ID = "\0" + MIDDLEWARE_MODULE_ID; const NOOP_MIDDLEWARE = "\0noop-middleware"; export function isMiddlewarePath(relativePath) { return relativePath.startsWith(`${MIDDLEWARE_PATH_SEGMENT_NAME}.`) || relativePath.startsWith(`${MIDDLEWARE_PATH_SEGMENT_NAME}/`); } export function vitePluginMiddleware({ settings }) { let resolvedMiddlewareId = undefined; const hasIntegrationMiddleware = settings.middlewares.pre.length > 0 || settings.middlewares.post.length > 0; let userMiddlewareIsPresent = false; return { name: "@astro/plugin-middleware", applyToEnvironment(environment) { return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.astro || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender; }, hotUpdate: { handler(ctx) { if (!isAstroServerEnvironment(this.environment)) return; // A change that matched no modules in the graph can't affect the // middleware or anything it imports, so there's nothing to invalidate. // Writes outside the graph (e.g. `@astrojs/cloudflare`'s `.wrangler/state`) // fire a hotUpdate per write and would otherwise reload the middleware // on every request. // https://github.com/withastro/astro/issues/17933 if (ctx.modules.length === 0) return; const middlewareVirtualMod = this.environment.moduleGraph.getModuleById(MIDDLEWARE_RESOLVED_MODULE_ID); if (!middlewareVirtualMod) return; this.environment.moduleGraph.invalidateModule(middlewareVirtualMod); this.environment.hot.send("astro:middleware-updated", {}); } }, resolveId: { filter: { id: new RegExp(`^${MIDDLEWARE_MODULE_ID}$`) }, async handler() { const middlewareId = await this.resolve(`${decodeURI(settings.config.srcDir.pathname)}${MIDDLEWARE_PATH_SEGMENT_NAME}`); userMiddlewareIsPresent = !!middlewareId; if (middlewareId) { resolvedMiddlewareId = middlewareId.id; return MIDDLEWARE_RESOLVED_MODULE_ID; } else if (hasIntegrationMiddleware) { return MIDDLEWARE_RESOLVED_MODULE_ID; } else { return NOOP_MIDDLEWARE; } } }, load: { filter: { id: new RegExp(`^(${NOOP_MIDDLEWARE}|${MIDDLEWARE_RESOLVED_MODULE_ID})$`) }, async handler(id) { if (id === NOOP_MIDDLEWARE) { if (!userMiddlewareIsPresent && settings.config.i18n?.routing === "manual") { throw new AstroError(MissingMiddlewareForInternationalization); } return { code: "export const onRequest = (_, next) => next()" }; } if (id === MIDDLEWARE_RESOLVED_MODULE_ID) { if (!userMiddlewareIsPresent && settings.config.i18n?.routing === "manual") { throw new AstroError(MissingMiddlewareForInternationalization); } const preMiddleware = createMiddlewareImports(settings.middlewares.pre, "pre"); const postMiddleware = createMiddlewareImports(settings.middlewares.post, "post"); const code = ` ${userMiddlewareIsPresent ? `import { onRequest as userOnRequest } from ${JSON.stringify(resolvedMiddlewareId)};` : ""} import { sequence } from 'astro:middleware'; ${preMiddleware.importsCode}${postMiddleware.importsCode} export const onRequest = sequence( ${preMiddleware.sequenceCode}${preMiddleware.sequenceCode ? "," : ""} ${userMiddlewareIsPresent ? `userOnRequest${postMiddleware.sequenceCode ? "," : ""}` : ""} ${postMiddleware.sequenceCode} ); `.trim(); return { code }; } } } }; } function createMiddlewareImports(entrypoints, prefix) { let importsRaw = ""; let sequenceRaw = ""; let index = 0; for (const entrypoint of entrypoints) { const name = `_${prefix}_${index}`; importsRaw += `import { onRequest as ${name} } from ${JSON.stringify(normalizePath(entrypoint))};\n`; sequenceRaw += `${index > 0 ? "," : ""}${name}`; index++; } return { importsCode: importsRaw, sequenceCode: sequenceRaw }; } export function vitePluginMiddlewareBuild(internals) { let canSplitMiddleware = true; return { name: "@astro/plugin-middleware-build", configResolved(config) { // Cloudflare Workers (webworker target) can't have multiple entrypoints, // so we only add middleware as a separate bundle for other targets (Node, Deno, etc). canSplitMiddleware = config.ssr.target !== "webworker"; }, options(options) { if (canSplitMiddleware) { // Add middleware as a separate rolldown input for environments that support multiple entrypoints. // This allows the middleware to be bundled independently. return addRolldownInput(options, [MIDDLEWARE_MODULE_ID]); } else {} }, writeBundle(_, bundle) { for (const [chunkName, chunk] of Object.entries(bundle)) { if (chunk.type !== "asset" && chunk.facadeModuleId === MIDDLEWARE_RESOLVED_MODULE_ID) { const outputDirectory = pathToDirectoryURL(this.environment.config.build.outDir); internals.middlewareEntryPoint = new URL(chunkName, outputDirectory); } } } }; }