UNPKG

astro

Version:

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

80 lines (79 loc) • 3.44 kB
import { init, parse } from "es-module-lexer/minimal"; import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../../constants.js"; /** * Returns the static module specifier of an import, or `undefined` if it can't * be statically determined. es-module-lexer only populates `imp.n` for static * imports and quoted-string dynamic imports. Rolldown's minifier rewrites * dynamic-import specifiers to template literals (`import(`./chunk.js`)`), for * which `imp.n` is undefined even with no interpolation, so the specifier is * derived from the raw slice (skipping any interpolation). * * Upstream issue: https://github.com/guybedford/es-module-lexer/issues/198 * Once es-module-lexer populates `imp.n` for no-substitution template literals, * this helper can be replaced with `imp.n` again. */ function getImportSpecifier(code, imp) { if (imp.n != null) return imp.n; if (imp.d > -1) { const raw = code.slice(imp.s, imp.e); const quote = raw[0]; if ((quote === "`" || quote === "\"" || quote === "'") && raw.at(-1) === quote) { const inner = raw.slice(1, -1); if (!inner.includes("${")) return inner; } } return undefined; } /** * Appends assetQueryParams (e.g., ?dpl=<VERCEL_DEPLOYMENT_ID>) to relative * JS import paths inside client chunks. Without this, inter-chunk imports * bypass the HTML rendering pipeline and miss skew protection query params. * * Uses es-module-lexer to reliably parse both static and dynamic imports. * * This runs in `generateBundle` (not `renderChunk`) so that Vite's CSS plugin * can first remove pure-CSS wrapper chunks and replace their imports with * `/* empty css * /` comments. If we rewrote imports earlier (in `renderChunk`), * the appended query params would break Vite's regex-based CSS chunk cleanup, * leaving dangling imports to deleted chunks that 404 at runtime. */ export function pluginChunkImports(options) { const assetQueryParams = options.settings.adapter?.client?.assetQueryParams; if (!assetQueryParams || assetQueryParams.toString() === "") { return undefined; } const queryString = assetQueryParams.toString(); return { name: "@astro/plugin-chunk-imports", enforce: "post", applyToEnvironment(environment) { return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client; }, async generateBundle(_options, bundle) { await init(); for (const [, chunk] of Object.entries(bundle)) { if (chunk.type !== "chunk") continue; if (!chunk.code.includes("./")) continue; const [imports] = parse(chunk.code); // Filter to relative JS imports only const relativeImports = imports.filter((imp) => { const name = getImportSpecifier(chunk.code, imp); return name != null && /^\.\.?\//.test(name) && /\.(?:js|mjs)$/.test(name); }); if (relativeImports.length === 0) continue; // Build new code by replacing specifiers from end to start // (reverse order preserves earlier offsets) let rewritten = chunk.code; for (let i = relativeImports.length - 1; i >= 0; i--) { const imp = relativeImports[i]; // imp.s and imp.e are the start/end offsets of the module specifier. // Static imports: e points after the specifier text, before the quote. // Dynamic string imports: e points after the closing quote. const insertAt = imp.d > -1 ? imp.e - 1 : imp.e; rewritten = rewritten.slice(0, insertAt) + "?" + queryString + rewritten.slice(insertAt); } chunk.code = rewritten; } } }; }