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