UNPKG

astro

Version:

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

85 lines (84 loc) • 3.83 kB
import { fileURLToPath } from "node:url"; import { normalizePath as viteNormalizePath } from "vite"; import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../constants.js"; /** * Virtual module that exposes the app's top-level fetch handler. * * If the user has a `src/fetch.ts` (or `.js` / `.mjs` / `.mts`) file that * default-exports a `{ fetch }` object, the virtual module re-exports it. * Otherwise it re-exports `defaultFetchHandler` from the built-in pipeline. * * Consumed by the generated app entrypoints (`prod.ts` / `dev.ts`) which * pass the resolved handler to `app.setFetchHandler(...)` after the `App` * is instantiated. */ const FETCHABLE_MODULE_ID = "virtual:astro:fetchable"; const FETCHABLE_RESOLVED_MODULE_ID = "\0" + FETCHABLE_MODULE_ID; // Default segment under `srcDir` to probe for the user's handler module. // Matches how `vitePluginMiddleware` resolves `src/middleware`. const DEFAULT_FETCH_FILE = "fetch"; export function vitePluginFetchable({ settings }) { let resolvedUserAppId; let userAppPresent = false; const fetchFile = settings.config.fetchFile ?? DEFAULT_FETCH_FILE; // When fetchFile is null the feature is explicitly disabled. const fetchFileDisabled = settings.config.fetchFile === null; const normalizedSrcDir = viteNormalizePath(fileURLToPath(settings.config.srcDir)); return { name: "@astro/plugin-fetchable", applyToEnvironment(environment) { return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.astro || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender; }, configureServer(server) { server.watcher.on("change", (path) => { const normalizedPath = viteNormalizePath(path); if (!normalizedPath.startsWith(normalizedSrcDir)) return; const relativePath = normalizedPath.slice(normalizedSrcDir.length); // Dot-prefix guard: match `app.ts` but not e.g. `app-utils.ts`. if (!relativePath.startsWith(`${fetchFile}.`)) return; for (const name of [ASTRO_VITE_ENVIRONMENT_NAMES.ssr, ASTRO_VITE_ENVIRONMENT_NAMES.astro]) { const environment = server.environments[name]; if (!environment) continue; const virtualMod = environment.moduleGraph.getModuleById(FETCHABLE_RESOLVED_MODULE_ID); if (virtualMod) { environment.moduleGraph.invalidateModule(virtualMod); } } }); }, resolveId: { filter: { id: new RegExp(`^${FETCHABLE_MODULE_ID}$`) }, async handler() { if (fetchFileDisabled) { userAppPresent = false; return FETCHABLE_RESOLVED_MODULE_ID; } const resolved = await this.resolve(`${normalizedSrcDir}${fetchFile}`); userAppPresent = !!resolved; resolvedUserAppId = resolved?.id; return FETCHABLE_RESOLVED_MODULE_ID; } }, load: { filter: { id: new RegExp(`^${FETCHABLE_RESOLVED_MODULE_ID}$`) }, handler() { if (userAppPresent && resolvedUserAppId) { // Re-export the user's default export (expected to be a // `{ fetch }` object, per the web Fetch API convention). return { code: `export { default } from ${JSON.stringify(resolvedUserAppId)};` }; } // No user-authored app — fall back to the built-in pipeline. // `isDefaultFetchHandler` lets the dev server's per-request // re-import recognize this fallback WITHOUT relying on // `instanceof`: in dev the module graph containing this virtual // module can be invalidated (any src change invalidates the // manifest module, whose importers include the default handler), // so a re-evaluation would otherwise produce a different class // identity than the facade's own DefaultFetchHandler. return { code: `import { DefaultFetchHandler } from 'astro/app/fetch/default-handler'; export default new DefaultFetchHandler(); export const isDefaultFetchHandler = true;` }; } } }; }