UNPKG

astro

Version:

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

292 lines (291 loc) • 11.7 kB
import { fileURLToPath } from "node:url"; import { glob } from "tinyglobby"; import { getAssetsPrefix } from "../../../assets/utils/getAssetsPrefix.js"; import { normalizeTheLocale } from "../../../i18n/index.js"; import { resolveMiddlewareMode } from "../../../integrations/adapter-utils.js"; import { runHookBuildSsr } from "../../../integrations/hooks.js"; import { SERIALIZED_MANIFEST_RESOLVED_ID } from "../../../manifest/serialized.js"; import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from "../../../vite-plugin-scripts/index.js"; import { toFallbackType } from "../../app/common.js"; import { serializeRouteData, toRoutingStrategy } from "../../app/entrypoints/index.js"; import { getAlgorithm, getDirectives, getScriptHashes, getScriptResources, getStrictDynamic, getStyleHashes, getStyleResources, shouldTrackCspHashes, trackScriptHashes, trackStyleHashes } from "../../csp/common.js"; import { encodeKey } from "../../encryption.js"; import { fileExtension, joinPaths, prependForwardSlash } from "../../path.js"; import { DEFAULT_COMPONENTS } from "../../routing/default.js"; import { getOutFile, getOutFolder } from "../common.js"; import { cssOrder, mergeInlineCss } from "../runtime.js"; import { makePageDataKey } from "./util.js"; import { cacheConfigToManifest } from "../../cache/utils.js"; import { sessionConfigToManifest } from "../../session/utils.js"; const MANIFEST_REPLACE = "@@ASTRO_MANIFEST_REPLACE@@"; const replaceExp = new RegExp(`['"]${MANIFEST_REPLACE}['"]`, "g"); async function manifestBuildPostHook(options, internals, { chunks, mutate }) { const manifest = await createManifest(options, internals); const ssrManifestChunk = chunks.find( (c) => !c.prerender && c.moduleIds.includes(SERIALIZED_MANIFEST_RESOLVED_ID) ); if (ssrManifestChunk) { const middlewareMode = resolveMiddlewareMode(options.settings.adapter?.adapterFeatures); const shouldPassMiddlewareEntryPoint = middlewareMode === "edge"; await runHookBuildSsr({ config: options.settings.config, manifest, logger: options.logger, middlewareEntryPoint: shouldPassMiddlewareEntryPoint ? internals.middlewareEntryPoint : void 0 }); const ssrManifest = stripPrerenderedRouteStyles(manifest); const code = injectManifest(ssrManifest, ssrManifestChunk.code); mutate(ssrManifestChunk.fileName, code, false); } const prerenderManifestChunk = chunks.find( (c) => c.prerender && c.moduleIds.includes(SERIALIZED_MANIFEST_RESOLVED_ID) ); if (prerenderManifestChunk) { const code = injectManifest(manifest, prerenderManifestChunk.code); mutate(prerenderManifestChunk.fileName, code, true); } } async function createManifest(buildOpts, internals) { const clientStatics = new Set( await glob("**/*", { cwd: fileURLToPath(buildOpts.settings.config.build.client) }) ); for (const file of clientStatics) { internals.staticFiles.add(file); } for (const [, ssrAssets] of internals.ssrAssetsPerEnvironment) { for (const asset of ssrAssets) { internals.staticFiles.add(asset); } } const staticFiles = internals.staticFiles; const encodedKey = await encodeKey(await buildOpts.key); const manifest = await buildManifest(buildOpts, internals, Array.from(staticFiles), encodedKey); return manifest; } function injectManifest(manifest, code) { return code.replace(replaceExp, () => { return JSON.stringify(manifest); }); } function stripPrerenderedRouteStyles(manifest) { let stripped = false; const routes = manifest.routes.map((route) => { if (!route.routeData.prerender || route.styles.length === 0) return route; stripped = true; return { ...route, styles: [] }; }); return stripped ? { ...manifest, routes } : manifest; } async function buildManifest(opts, internals, staticFiles, encodedKey) { const { settings } = opts; const routes = []; const domainLookupTable = {}; const rawEntryModules = Object.fromEntries(internals.entrySpecifierToBundleMap.entries()); const assetQueryParams = settings.adapter?.client?.assetQueryParams; const assetQueryString = assetQueryParams ? assetQueryParams.toString() : void 0; const appendAssetQuery = (pth) => assetQueryString ? `${pth}?${assetQueryString}` : pth; const entryModules = Object.fromEntries( Object.entries(rawEntryModules).map(([key, value]) => [ key, value ? appendAssetQuery(value) : value ]) ); if (settings.scripts.some((script) => script.stage === "page")) { staticFiles.push(rawEntryModules[PAGE_SCRIPT_ID]); } const prefixAssetPath = (pth) => { let result = ""; if (settings.config.build.assetsPrefix) { const pf = getAssetsPrefix(fileExtension(pth), settings.config.build.assetsPrefix); result = joinPaths(pf, pth); } else { result = prependForwardSlash(joinPaths(settings.config.base, pth)); } if (assetQueryString) { result += "?" + assetQueryString; } return result; }; for (const route of opts.routesList.routes) { if (!DEFAULT_COMPONENTS.find((component) => route.component === component)) { continue; } routes.push({ file: "", links: [], scripts: [], styles: [], routeData: serializeRouteData(route, settings.config.trailingSlash) }); } for (const route of opts.routesList.routes) { const pageData = internals.pagesByKeys.get(makePageDataKey(route.route, route.component)); if (!pageData) continue; const scripts = []; if (settings.scripts.some((script) => script.stage === "page")) { const src = rawEntryModules[PAGE_SCRIPT_ID]; scripts.push({ type: "external", value: appendAssetQuery(src) }); } const links = []; const styles = pageData.styles.sort(cssOrder).map(({ sheet }) => sheet).map((s) => s.type === "external" ? { ...s, src: appendAssetQuery(s.src) } : s).reduce(mergeInlineCss, []); routes.push({ file: "", links, scripts: [ ...scripts, ...settings.scripts.filter((script) => script.stage === "head-inline").map(({ stage, content }) => ({ stage, children: content })) ], styles, routeData: serializeRouteData(route, settings.config.trailingSlash) }); if (route.prerender && route.pathname) { const outFolder = getOutFolder(opts.settings, route.pathname, route); const outFile = getOutFile( opts.settings.config.build.format, outFolder, route.pathname, route ); const file = outFile.toString().replace(opts.settings.config.build.client.toString(), ""); staticFiles.push(file); } } const i18n = settings.config.i18n; if (i18n && i18n.domains) { for (const [locale, domainValue] of Object.entries(i18n.domains)) { domainLookupTable[domainValue] = normalizeTheLocale(locale); } } if (!(BEFORE_HYDRATION_SCRIPT_ID in entryModules)) { entryModules[BEFORE_HYDRATION_SCRIPT_ID] = ""; } let i18nManifest = void 0; if (settings.config.i18n) { i18nManifest = { fallback: settings.config.i18n.fallback, fallbackType: toFallbackType(settings.config.i18n.routing), strategy: toRoutingStrategy(settings.config.i18n.routing, settings.config.i18n.domains), locales: settings.config.i18n.locales, defaultLocale: settings.config.i18n.defaultLocale, domainLookupTable, domains: settings.config.i18n.domains }; } let csp = void 0; if (shouldTrackCspHashes(settings.config.security.csp)) { const algorithm = getAlgorithm(settings.config.security.csp); const scriptHashes = [ ...getScriptHashes(settings.config.security.csp), ...await trackScriptHashes(internals, settings, algorithm) ]; const styleHashes = [ ...getStyleHashes(settings.config.security.csp), ...settings.injectedCsp.styleHashes, ...await trackStyleHashes(internals, settings, algorithm) ]; csp = { cspDestination: settings.adapter?.adapterFeatures?.staticHeaders ? "adapter" : void 0, scriptHashes, scriptResources: getScriptResources(settings.config.security.csp), styleHashes, styleResources: getStyleResources(settings.config.security.csp), algorithm, directives: getDirectives(settings), isStrictDynamic: getStrictDynamic(settings.config.security.csp) }; } let internalFetchHeaders = void 0; if (settings.adapter?.client?.internalFetchHeaders) { const headers = typeof settings.adapter.client.internalFetchHeaders === "function" ? settings.adapter.client.internalFetchHeaders() : settings.adapter.client.internalFetchHeaders; if (Object.keys(headers).length > 0) { internalFetchHeaders = headers; } } const middlewareMode = resolveMiddlewareMode(opts.settings.adapter?.adapterFeatures); let experimentalLogger = void 0; if (settings.config.experimental.logger) { experimentalLogger = settings.config.experimental.logger; } return { rootDir: opts.settings.config.root.toString(), cacheDir: opts.settings.config.cacheDir.toString(), outDir: opts.settings.config.outDir.toString(), srcDir: opts.settings.config.srcDir.toString(), publicDir: opts.settings.config.publicDir.toString(), buildClientDir: opts.settings.config.build.client.toString(), buildServerDir: opts.settings.config.build.server.toString(), adapterName: opts.settings.adapter?.name ?? "", assetsDir: opts.settings.config.build.assets, routes, serverLike: opts.settings.buildOutput === "server", middlewareMode, site: settings.config.site, base: settings.config.base, userAssetsBase: settings.config?.vite?.base, trailingSlash: settings.config.trailingSlash, compressHTML: settings.config.compressHTML, assetsPrefix: settings.config.build.assetsPrefix, experimentalQueuedRendering: { enabled: settings.config.experimental.queuedRendering?.enabled ?? false, poolSize: 0, contentCache: false }, componentMetadata: Array.from(internals.componentMetadata), renderers: [], clientDirectives: Array.from(settings.clientDirectives), entryModules, inlinedScripts: Array.from(internals.inlinedScripts), assets: staticFiles.map(prefixAssetPath), i18n: i18nManifest, buildFormat: settings.config.build.format, checkOrigin: (settings.config.security?.checkOrigin && settings.buildOutput === "server") ?? false, actionBodySizeLimit: settings.config.security?.actionBodySizeLimit && settings.buildOutput === "server" ? settings.config.security.actionBodySizeLimit : 1024 * 1024, serverIslandBodySizeLimit: settings.config.security?.serverIslandBodySizeLimit && settings.buildOutput === "server" ? settings.config.security.serverIslandBodySizeLimit : 1024 * 1024, allowedDomains: settings.config.security?.allowedDomains, key: encodedKey, 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: false, latestAstroVersion: void 0, debugInfoOutput: "", placement: void 0 }, internalFetchHeaders, logLevel: settings.logLevel, shouldInjectCspMetaTags: shouldTrackCspHashes(settings.config.security.csp), experimentalLogger }; } export { MANIFEST_REPLACE, manifestBuildPostHook };