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
JavaScript
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);
}
}
}
};
}