import { fileURLToPath } from "node:url"; import { normalizePath } from "vite"; import { ACTIONS_ENTRYPOINT_VIRTUAL_MODULE_ID } from "../actions/consts.js"; import { toFallbackType } from "../core/app/common.js"; import { toRoutingStrategy } from "../core/app/entrypoints/index.js"; import { MANIFEST_REPLACE } from "../core/build/plugins/plugin-manifest.js"; import { getAlgorithm, getDirectives, getScriptHashes, getScriptResources, getStrictDynamic, getStyleHashes, getStyleResources, shouldTrackCspHashes } from "../core/csp/common.js"; import { createKey, encodeKey, getEnvironmentKey, hasEnvironmentKey } from "../core/encryption.js"; import { MIDDLEWARE_MODULE_ID } from "../core/middleware/vite-plugin.js"; import { SERVER_ISLAND_MANIFEST } from "../core/server-islands/vite-plugin-server-islands.js"; import { VIRTUAL_CACHE_PROVIDER_ID } from "../core/cache/vite-plugin.js"; import { VIRTUAL_SESSION_DRIVER_ID } from "../core/session/vite-plugin.js"; import { VIRTUAL_PAGES_MODULE_ID } from "../vite-plugin-pages/index.js"; import { ASTRO_RENDERERS_MODULE_ID } from "../vite-plugin-renderers/index.js"; import { ASTRO_ROUTES_MODULE_ID } from "../vite-plugin-routes/index.js"; import { cacheConfigToManifest } from "../core/cache/utils.js"; import { sessionConfigToManifest } from "../core/session/utils.js"; import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js"; import { resolveMiddlewareMode } from "../integrations/adapter-utils.js"; const SERIALIZED_MANIFEST_ID = "virtual:astro:manifest"; const SERIALIZED_MANIFEST_RESOLVED_ID = "\0" + SERIALIZED_MANIFEST_ID; function serializedManifestPlugin({ settings, command, sync }) { const normalizedSrcDir = normalizePath(fileURLToPath(settings.config.srcDir)); let encodedKeyPromise; function getEncodedKey() { encodedKeyPromise ??= (async () => { const key = hasEnvironmentKey() ? await getEnvironmentKey() : await createKey(); return encodeKey(key); })(); return encodedKeyPromise; } function reloadManifest(path, server) { if (path != null && normalizePath(path).startsWith(normalizedSrcDir)) { const environment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr]; const virtualMod = environment.moduleGraph.getModuleById(SERIALIZED_MANIFEST_RESOLVED_ID); if (!virtualMod) return; environment.moduleGraph.invalidateModule(virtualMod); } } return { name: SERIALIZED_MANIFEST_ID, enforce: "pre", configureServer(server) { server.watcher.on("add", (path) => reloadManifest(path, server)); server.watcher.on("unlink", (path) => reloadManifest(path, server)); server.watcher.on("change", (path) => reloadManifest(path, server)); }, // Restrict to server environments only since the generated code imports // server-only virtual modules (virtual:astro:routes, virtual:astro:pages) applyToEnvironment(environment) { return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.astro || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender; }, resolveId: { filter: { id: new RegExp(`^${SERIALIZED_MANIFEST_ID}$`) }, handler() { return SERIALIZED_MANIFEST_RESOLVED_ID; } }, load: { filter: { id: new RegExp(`^${SERIALIZED_MANIFEST_RESOLVED_ID}$`) }, async handler() { let manifestData; if (command === "build" && !sync) { manifestData = `'${MANIFEST_REPLACE}'`; } else { const serialized = await createSerializedManifest(settings, await getEncodedKey()); manifestData = JSON.stringify(serialized); } const hasCacheConfig = !!settings.config.experimental?.cache?.provider; const cacheProviderLine = hasCacheConfig ? `cacheProvider: () => import('${VIRTUAL_CACHE_PROVIDER_ID}'),` : ""; const code = ` import { deserializeManifest as _deserializeManifest } from 'astro/app'; import { renderers } from '${ASTRO_RENDERERS_MODULE_ID}'; import { routes } from '${ASTRO_ROUTES_MODULE_ID}'; import { pageMap } from '${VIRTUAL_PAGES_MODULE_ID}'; const _manifest = _deserializeManifest((${manifestData})); // _manifest.routes contains enriched route info with scripts and styles, // TODO port this info over to virtual:astro:routes to prevent the need to // have this duplication const isDev = ${JSON.stringify(command === "dev")}; const manifestRoutes = isDev ? routes : _manifest.routes; const manifest = Object.assign(_manifest, { renderers, actions: () => import('${ACTIONS_ENTRYPOINT_VIRTUAL_MODULE_ID}'), middleware: () => import('${MIDDLEWARE_MODULE_ID}'), sessionDriver: () => import('${VIRTUAL_SESSION_DRIVER_ID}'), ${cacheProviderLine} serverIslandMappings: () => import('${SERVER_ISLAND_MANIFEST}'), routes: manifestRoutes, pageMap, }); export { manifest }; `; return { code }; } } }; } async function createSerializedManifest(settings, encodedKey) { let i18nManifest; let csp; if (settings.config.i18n) { i18nManifest = { fallback: settings.config.i18n.fallback, strategy: toRoutingStrategy(settings.config.i18n.routing, settings.config.i18n.domains), defaultLocale: settings.config.i18n.defaultLocale, locales: settings.config.i18n.locales, domainLookupTable: {}, fallbackType: toFallbackType(settings.config.i18n.routing), domains: settings.config.i18n.domains }; } if (shouldTrackCspHashes(settings.config.security.csp)) { csp = { cspDestination: settings.adapter?.adapterFeatures?.staticHeaders ? "adapter" : void 0, scriptHashes: getScriptHashes(settings.config.security.csp), scriptResources: getScriptResources(settings.config.security.csp), styleHashes: getStyleHashes(settings.config.security.csp), styleResources: getStyleResources(settings.config.security.csp), algorithm: getAlgorithm(settings.config.security.csp), directives: getDirectives(settings), isStrictDynamic: getStrictDynamic(settings.config.security.csp) }; } return { rootDir: settings.config.root.toString(), srcDir: settings.config.srcDir.toString(), cacheDir: settings.config.cacheDir.toString(), outDir: settings.config.outDir.toString(), buildServerDir: settings.config.build.server.toString(), buildClientDir: settings.config.build.client.toString(), publicDir: settings.config.publicDir.toString(), assetsDir: settings.config.build.assets, trailingSlash: settings.config.trailingSlash, buildFormat: settings.config.build.format, compressHTML: settings.config.compressHTML, serverLike: settings.buildOutput === "server", middlewareMode: resolveMiddlewareMode(settings.adapter?.adapterFeatures), assets: [], entryModules: {}, routes: [], adapterName: settings?.adapter?.name ?? "", clientDirectives: Array.from(settings.clientDirectives.entries()), renderers: [], base: settings.config.base, userAssetsBase: settings.config?.vite?.base, assetsPrefix: settings.config.build.assetsPrefix, site: settings.config.site, componentMetadata: [], inlinedScripts: [], i18n: i18nManifest, checkOrigin: (settings.config.security?.checkOrigin && settings.buildOutput === "server") ?? false, allowedDomains: settings.config.security?.allowedDomains, actionBodySizeLimit: settings.config.security?.actionBodySizeLimit ? settings.config.security.actionBodySizeLimit : 1024 * 1024, // 1mb default serverIslandBodySizeLimit: settings.config.security?.serverIslandBodySizeLimit ? settings.config.security.serverIslandBodySizeLimit : 1024 * 1024, // 1mb default key: encodedKey ?? await encodeKey(hasEnvironmentKey() ? await getEnvironmentKey() : await createKey()), sessionConfig: sessionConfigToManifest(settings.config.session), cacheConfig: cacheConfigToManifest( settings.config.experimental?.cache, settings.config.experimental?.routeRules ), csp, image: { objectFit: settings.config.image.objectFit, objectPosition: settings.config.image.objectPosition, layout: settings.config.image.layout }, devToolbar: { enabled: settings.config.devToolbar.enabled && await settings.preferences.get("devToolbar.enabled"), latestAstroVersion: settings.latestAstroVersion, debugInfoOutput: "", placement: settings.config.devToolbar.placement }, logLevel: settings.logLevel, shouldInjectCspMetaTags: false, experimentalQueuedRendering: settings.config.experimental?.queuedRendering }; } export { SERIALIZED_MANIFEST_ID, SERIALIZED_MANIFEST_RESOLVED_ID, serializedManifestPlugin };