astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
264 lines (263 loc) • 9.85 kB
JavaScript
import { getServerOutputDirectory } from "../../prerender/utils.js";
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from "../../vite-plugin-scripts/index.js";
import { routeIsFallback, routeIsRedirect } from "../redirects/helpers.js";
import { RedirectSinglePageBuiltModule } from "../redirects/index.js";
import { Pipeline } from "../render/index.js";
import { createAssetLink, createStylesheetElementSet } from "../render/ssr-element.js";
import { createDefaultRoutes } from "../routing/default.js";
import { findRouteToRewrite } from "../routing/rewrite.js";
import { getOutDirWithinCwd } from "./common.js";
import { cssOrder, getPageData, mergeInlineCss } from "./internal.js";
import { ASTRO_PAGE_MODULE_ID, ASTRO_PAGE_RESOLVED_MODULE_ID } from "./plugins/plugin-pages.js";
import { getPagesFromVirtualModulePageName, getVirtualModulePageName } from "./plugins/util.js";
import { i18nHasFallback } from "./util.js";
class BuildPipeline extends Pipeline {
constructor(internals, manifest, options, config = options.settings.config, settings = options.settings, defaultRoutes = createDefaultRoutes(manifest)) {
const resolveCache = /* @__PURE__ */ new Map();
async function resolve(specifier) {
if (resolveCache.has(specifier)) {
return resolveCache.get(specifier);
}
const hashedFilePath = manifest.entryModules[specifier];
if (typeof hashedFilePath !== "string" || hashedFilePath === "") {
if (specifier === BEFORE_HYDRATION_SCRIPT_ID) {
resolveCache.set(specifier, "");
return "";
}
throw new Error(`Cannot find the built path for ${specifier}`);
}
const assetLink = createAssetLink(hashedFilePath, manifest.base, manifest.assetsPrefix);
resolveCache.set(specifier, assetLink);
return assetLink;
}
const serverLike = settings.buildOutput === "server";
const streaming = serverLike;
super(
options.logger,
manifest,
options.runtimeMode,
manifest.renderers,
resolve,
serverLike,
streaming
);
this.internals = internals;
this.manifest = manifest;
this.options = options;
this.config = config;
this.settings = settings;
this.defaultRoutes = defaultRoutes;
}
#componentsInterner = /* @__PURE__ */ new WeakMap();
/**
* This cache is needed to map a single `RouteData` to its file path.
* @private
*/
#routesByFilePath = /* @__PURE__ */ new WeakMap();
get outFolder() {
return this.settings.buildOutput === "server" ? this.settings.config.build.server : getOutDirWithinCwd(this.settings.config.outDir);
}
getRoutes() {
return this.options.routesList.routes;
}
static create({
internals,
manifest,
options
}) {
return new BuildPipeline(internals, manifest, options);
}
/**
* The SSR build emits two important files:
* - dist/server/manifest.mjs
* - dist/renderers.mjs
*
* These two files, put together, will be used to generate the pages.
*
* ## Errors
*
* It will throw errors if the previous files can't be found in the file system.
*
* @param staticBuildOptions
*/
static async retrieveManifest(settings, internals) {
const baseDirectory = getServerOutputDirectory(settings);
const manifestEntryUrl = new URL(
`${internals.manifestFileName}?time=${Date.now()}`,
baseDirectory
);
const { manifest } = await import(manifestEntryUrl.toString());
if (!manifest) {
throw new Error(
"Astro couldn't find the emitted manifest. This is an internal error, please file an issue."
);
}
const renderersEntryUrl = new URL(`renderers.mjs?time=${Date.now()}`, baseDirectory);
const renderers = await import(renderersEntryUrl.toString());
const middleware = internals.middlewareEntryPoint ? async function() {
const mod = await import(internals.middlewareEntryPoint.toString());
return { onRequest: mod.onRequest };
} : manifest.middleware;
if (!renderers) {
throw new Error(
"Astro couldn't find the emitted renderers. This is an internal error, please file an issue."
);
}
return {
...manifest,
renderers: renderers.renderers,
middleware
};
}
headElements(routeData) {
const {
internals,
manifest: { assetsPrefix, base },
settings
} = this;
const links = /* @__PURE__ */ new Set();
const pageBuildData = getPageData(internals, routeData.route, routeData.component);
const scripts = /* @__PURE__ */ new Set();
const sortedCssAssets = pageBuildData?.styles.sort(cssOrder).map(({ sheet }) => sheet).reduce(mergeInlineCss, []);
const styles = createStylesheetElementSet(sortedCssAssets ?? [], base, assetsPrefix);
if (settings.scripts.some((script) => script.stage === "page")) {
const hashedFilePath = internals.entrySpecifierToBundleMap.get(PAGE_SCRIPT_ID);
if (typeof hashedFilePath !== "string") {
throw new Error(`Cannot find the built path for ${PAGE_SCRIPT_ID}`);
}
const src = createAssetLink(hashedFilePath, base, assetsPrefix);
scripts.add({
props: { type: "module", src },
children: ""
});
}
for (const script of settings.scripts) {
if (script.stage === "head-inline") {
scripts.add({
props: {},
children: script.content
});
}
}
return { scripts, styles, links };
}
componentMetadata() {
}
/**
* It collects the routes to generate during the build.
* It returns a map of page information and their relative entry point as a string.
*/
retrieveRoutesToGenerate() {
const pages = /* @__PURE__ */ new Map();
for (const [virtualModulePageName, filePath] of this.internals.entrySpecifierToBundleMap) {
if (virtualModulePageName.includes(ASTRO_PAGE_RESOLVED_MODULE_ID)) {
let pageDatas = [];
pageDatas.push(
...getPagesFromVirtualModulePageName(
this.internals,
ASTRO_PAGE_RESOLVED_MODULE_ID,
virtualModulePageName
)
);
for (const pageData of pageDatas) {
pages.set(pageData, filePath);
}
}
}
for (const pageData of this.internals.pagesByKeys.values()) {
if (routeIsRedirect(pageData.route)) {
pages.set(pageData, pageData.component);
} else if (routeIsFallback(pageData.route) && (i18nHasFallback(this.config) || routeIsFallback(pageData.route) && pageData.route.route === "/")) {
const moduleSpecifier = getVirtualModulePageName(ASTRO_PAGE_MODULE_ID, pageData.component);
const filePath = this.internals.entrySpecifierToBundleMap.get(moduleSpecifier);
if (filePath) {
pages.set(pageData, filePath);
}
}
}
for (const [buildData, filePath] of pages.entries()) {
this.#routesByFilePath.set(buildData.route, filePath);
}
return pages;
}
async getComponentByRoute(routeData) {
if (this.#componentsInterner.has(routeData)) {
const entry = this.#componentsInterner.get(routeData);
return await entry.page();
}
for (const route of this.defaultRoutes) {
if (route.component === routeData.component) {
return route.instance;
}
}
const filePath = this.#routesByFilePath.get(routeData);
const module = await this.retrieveSsrEntry(routeData, filePath);
return module.page();
}
async tryRewrite(payload, request) {
const { routeData, pathname, newUrl } = findRouteToRewrite({
payload,
request,
routes: this.options.routesList.routes,
trailingSlash: this.config.trailingSlash,
buildFormat: this.config.build.format,
base: this.config.base
});
const componentInstance = await this.getComponentByRoute(routeData);
return { routeData, componentInstance, newUrl, pathname };
}
async retrieveSsrEntry(route, filePath) {
if (this.#componentsInterner.has(route)) {
return this.#componentsInterner.get(route);
}
let entry;
if (routeIsRedirect(route)) {
entry = await this.#getEntryForRedirectRoute(route, this.outFolder);
} else if (routeIsFallback(route)) {
entry = await this.#getEntryForFallbackRoute(route, this.outFolder);
} else {
const ssrEntryURLPage = createEntryURL(filePath, this.outFolder);
entry = await import(ssrEntryURLPage.toString());
}
this.#componentsInterner.set(route, entry);
return entry;
}
async #getEntryForFallbackRoute(route, outFolder) {
if (route.type !== "fallback") {
throw new Error(`Expected a redirect route.`);
}
if (route.redirectRoute) {
const filePath = getEntryFilePath(this.internals, route.redirectRoute);
if (filePath) {
const url = createEntryURL(filePath, outFolder);
const ssrEntryPage = await import(url.toString());
return ssrEntryPage;
}
}
return RedirectSinglePageBuiltModule;
}
async #getEntryForRedirectRoute(route, outFolder) {
if (route.type !== "redirect") {
throw new Error(`Expected a redirect route.`);
}
if (route.redirectRoute) {
const filePath = getEntryFilePath(this.internals, route.redirectRoute);
if (filePath) {
const url = createEntryURL(filePath, outFolder);
const ssrEntryPage = await import(url.toString());
return ssrEntryPage;
}
}
return RedirectSinglePageBuiltModule;
}
}
function createEntryURL(filePath, outFolder) {
return new URL("./" + filePath + `?time=${Date.now()}`, outFolder);
}
function getEntryFilePath(internals, pageData) {
const id = "\0" + getVirtualModulePageName(ASTRO_PAGE_MODULE_ID, pageData.component);
return internals.entrySpecifierToBundleMap.get(id);
}
export {
BuildPipeline
};