Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/astro/src/core/app/dev/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ export class DevPipeline extends Pipeline {
}

async headElements(routeData: RouteData): Promise<HeadElements> {
const { assetsPrefix, base } = this.manifest;
const routeInfo = this.manifest.routes.find((route) => route.routeData === routeData);
// may be used in the future for handling rel=modulepreload, rel=icon, rel=manifest etc.
const links = new Set<never>();
const scripts = new Set<SSRElement>();
const styles = createStylesheetElementSet(routeInfo?.styles ?? []);
const styles = createStylesheetElementSet(routeInfo?.styles ?? [], base, assetsPrefix);

for (const script of routeInfo?.scripts ?? []) {
if ('stage' in script) {
Expand Down
5 changes: 3 additions & 2 deletions packages/astro/src/core/app/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ export class AppPipeline extends Pipeline {
}

async headElements(routeData: RouteData): Promise<HeadElements> {
const { assetsPrefix, base } = this.manifest;
const routeInfo = this.manifest.routes.find((route) => route.routeData === routeData);
// may be used in the future for handling rel=modulepreload, rel=icon, rel=manifest etc.
const links = new Set<never>();
const scripts = new Set<SSRElement>();
const styles = createStylesheetElementSet(routeInfo?.styles ?? []);
const styles = createStylesheetElementSet(routeInfo?.styles ?? [], base, assetsPrefix);

for (const script of routeInfo?.scripts ?? []) {
if ('stage' in script) {
Expand All @@ -62,7 +63,7 @@ export class AppPipeline extends Pipeline {
});
}
} else {
scripts.add(createModuleScriptElement(script));
scripts.add(createModuleScriptElement(script, base, assetsPrefix));
}
}
return { links, styles, scripts };
Expand Down
31 changes: 16 additions & 15 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { getOutputFilename } from '../util.js';
import { getOutFile, getOutFolder } from './common.js';
import { type BuildInternals, hasPrerenderedPages } from './internal.js';
import { BuildPipeline } from './pipeline.js';
import type { PageBuildData, SinglePageBuiltModule, StaticBuildOptions } from './types.js';
import type { SinglePageBuiltModule, StaticBuildOptions } from './types.js';
import { getTimeStat, shouldAppendForwardSlash } from './util.js';

export async function generatePages(
Expand All @@ -45,7 +45,8 @@ export async function generatePages(
const generatePagesTimer = performance.now();
const ssr = options.settings.buildOutput === 'server';
// Import from the single prerender entrypoint
const prerenderEntryUrl = new URL('prerender-entry.mjs', prerenderOutputDir);
const prerenderEntryFileName = internals.prerenderEntryFileName ?? 'prerender-entry.mjs';
const prerenderEntryUrl = new URL(prerenderEntryFileName, prerenderOutputDir);
const prerenderEntry = await import(prerenderEntryUrl.toString());

// Grab the manifest and create the pipeline
Expand All @@ -69,22 +70,22 @@ export async function generatePages(
const pageMap = prerenderEntry.pageMap as Map<string, () => Promise<SinglePageBuiltModule>>;

if (ssr) {
for (const [pageData, _] of pagesToGenerate) {
if (pageData.route.prerender) {
for (const [routeData, _] of pagesToGenerate) {
if (routeData.prerender) {
// i18n domains won't work with pre rendered routes at the moment, so we need to throw an error
if (config.i18n?.domains && Object.keys(config.i18n.domains).length > 0) {
throw new AstroError({
...NoPrerenderedRoutesWithDomains,
message: NoPrerenderedRoutesWithDomains.message(pageData.component),
message: NoPrerenderedRoutesWithDomains.message(routeData.component),
});
}

await generatePage(app, pageMap, pageData, builtPaths, pipeline, routeToHeaders);
await generatePage(app, pageMap, routeData, builtPaths, pipeline, routeToHeaders);
}
}
} else {
for (const [pageData, _] of pagesToGenerate) {
await generatePage(app, pageMap, pageData, builtPaths, pipeline, routeToHeaders);
for (const [routeData, _] of pagesToGenerate) {
await generatePage(app, pageMap, routeData, builtPaths, pipeline, routeToHeaders);
}
}
logger.info(
Expand Down Expand Up @@ -193,7 +194,7 @@ const THRESHOLD_SLOW_RENDER_TIME_MS = 500;
async function generatePage(
app: BaseApp,
pageMap: Map<string, () => Promise<SinglePageBuiltModule>>,
pageData: PageBuildData,
routeData: RouteData,
builtPaths: Set<string>,
pipeline: BuildPipeline,
routeToHeaders: RouteToHeaders,
Expand All @@ -212,7 +213,7 @@ async function generatePage(
const timeStart = performance.now();
pipeline.logger.debug('build', `Generating: ${path}`);

const filePath = getOutputFilename(config, path, pageData.route);
const filePath = getOutputFilename(config, path, routeData);
const lineIcon =
(index === paths.length - 1 && !isConcurrent) || paths.length === 1 ? '└─' : '├─';

Expand Down Expand Up @@ -245,7 +246,7 @@ async function generatePage(
}

// Now we explode the routes. A route render itself, and it can render its fallbacks (i18n routing)
for (const route of eachRouteInRouteData(pageData)) {
for (const route of eachRouteInRouteData(routeData)) {
const integrationRoute = toIntegrationResolvedRoute(route, pipeline.manifest.trailingSlash);
const icon =
route.type === 'page' || route.type === 'redirect' || route.type === 'fallback'
Expand All @@ -254,7 +255,7 @@ async function generatePage(
logger.info(null, `${icon} ${getPrettyRouteName(route)}`);

// Get paths for the route, calling getStaticPaths if needed.
const paths = await getPathsForRoute(route, pageData.component, pageMap, pipeline, builtPaths);
const paths = await getPathsForRoute(route, routeData.component, pageMap, pipeline, builtPaths);

// Generate each paths
if (config.build.concurrency > 1) {
Expand All @@ -276,9 +277,9 @@ async function generatePage(
}
}

function* eachRouteInRouteData(data: PageBuildData) {
yield data.route;
for (const fallbackRoute of data.route.fallbackRoutes) {
function* eachRouteInRouteData(route: RouteData) {
yield route;
for (const fallbackRoute of route.fallbackRoutes) {
yield fallbackRoute;
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/build/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export interface BuildInternals {
clientInput: Set<string>;

manifestFileName?: string;
prerenderEntryFileName?: string;
componentMetadata: SSRResult['componentMetadata'];
middlewareEntryPoint: URL | undefined;
astroActionsEntryPoint: URL | undefined;
Expand Down
48 changes: 14 additions & 34 deletions packages/astro/src/core/build/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
SSRElement,
SSRResult,
} from '../../types/public/internal.js';
import { VIRTUAL_PAGE_MODULE_ID } from '../../vite-plugin-pages/index.js';
import { VIRTUAL_PAGE_MODULE_ID, VIRTUAL_PAGE_RESOLVED_MODULE_ID } from '../../vite-plugin-pages/index.js';
import { getVirtualModulePageName } from '../../vite-plugin-pages/util.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import type { SSRManifest } from '../app/types.js';
Expand All @@ -18,8 +18,7 @@ import { createDefaultRoutes } from '../routing/default.js';
import { findRouteToRewrite } from '../routing/rewrite.js';
import { getOutDirWithinCwd } from './common.js';
import { type BuildInternals, cssOrder, getPageData, mergeInlineCss } from './internal.js';
import type { PageBuildData, SinglePageBuiltModule, StaticBuildOptions } from './types.js';
import { i18nHasFallback } from './util.js';
import type { SinglePageBuiltModule, StaticBuildOptions } from './types.js';

/**
* The build pipeline is responsible to gather the files emitted by the SSR build and generate the pages by executing these files.
Expand Down Expand Up @@ -141,42 +140,23 @@ export class BuildPipeline extends Pipeline {
* 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(): Map<PageBuildData, string> {
const pages = new Map<PageBuildData, string>();
retrieveRoutesToGenerate(): Map<RouteData, string> {
const pages = new Map<RouteData, string>();

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 === '/'))
) {
Comment on lines -148 to -154
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we removed essential logic that hasn't been placed anywhere else. Why? Maybe we should update the docs of this method to better align it with the new logic

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now add all routes. This function is quite different because it now operates on the list of routes in the manifest rather than these other maps we used to use.

Can you explain what this logic was doing? It's filtering out fallback routes unless i18n has fallback configuration or the route happens to be the base?

I was of course trying to be careful to preserve logic as much as possible but given that prerendering works much differently now (being based on the manifest) it was difficult to preserve everything, and I figured as we went through the tests we would fix these kinds of things.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, then maybe it's best if later you can share with us how the prerendering works. Not a blocker

// The original component is transformed during the first build, so we have to retrieve
// the actual `.mjs` that was created.
// During the build, we transform the names of our pages with some weird name, and those weird names become the keys of a map.
// The values of the map are the actual `.mjs` files that are generated during the build
for(const { routeData } of this.manifest.routes) {
// Here, we take the component path and transform it in the virtual module name
const moduleSpecifier = getVirtualModulePageName(VIRTUAL_PAGE_RESOLVED_MODULE_ID, routeData.component);
// We retrieve the original JS module
const filePath = this.internals.entrySpecifierToBundleMap.get(moduleSpecifier);
if (filePath) {
// it exists, added it to pages to render, using the file path that we just retrieved
pages.set(routeData, filePath);

// Here, we take the component path and transform it in the virtual module name
const moduleSpecifier = getVirtualModulePageName(VIRTUAL_PAGE_MODULE_ID, pageData.component);
// We retrieve the original JS module
const filePath = this.internals.entrySpecifierToBundleMap.get(moduleSpecifier);
if (filePath) {
// it exists, added it to pages to render, using the file path that we just retrieved
pages.set(pageData, filePath);
}
}
// Regular page
else {
// TODO: The value doesn't matter anymore. In a future refactor, we can remove it from the Map entirely.
pages.set(pageData, '');
// Populate the cache
this.#routesByFilePath.set(routeData, filePath);
}
}

for (const [buildData, filePath] of pages.entries()) {
this.#routesByFilePath.set(buildData.route, filePath);
}

return pages;
}

Expand Down
6 changes: 3 additions & 3 deletions packages/astro/src/core/build/plugins/plugin-css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] {
name: 'astro:rollup-plugin-build-css',

applyToEnvironment(environment) {
return environment.name === 'client' || environment.name === 'ssr';
return environment.name === 'client' || environment.name === 'ssr' || environment.name === 'prerender';
},

async generateBundle(_outputOptions, bundle) {
Expand Down Expand Up @@ -105,7 +105,7 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] {
name: 'astro:rollup-plugin-single-css',
enforce: 'post',
applyToEnvironment(environment) {
return environment.name === 'client' || environment.name === 'ssr';
return environment.name === 'client' || environment.name === 'ssr' || environment.name === 'prerender';
},
configResolved(config) {
resolvedConfig = config;
Expand All @@ -131,7 +131,7 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] {
name: 'astro:rollup-plugin-inline-stylesheets',
enforce: 'post',
applyToEnvironment(environment) {
return environment.name === 'client' || environment.name === 'ssr';
return environment.name === 'client' || environment.name === 'ssr' || environment.name === 'prerender';
},
configResolved(config) {
assetsInlineLimit = config.build.assetsInlineLimit;
Expand Down
35 changes: 11 additions & 24 deletions packages/astro/src/core/build/plugins/plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export async function manifestBuildPostHook(
},
) {
const manifest = await createManifest(options, internals);

if(ssrOutputs.length > 0) {
let manifestEntryChunk: OutputChunk | undefined;

Expand Down Expand Up @@ -153,7 +153,8 @@ async function createManifest(

const staticFiles = internals.staticFiles;
const encodedKey = await encodeKey(await buildOpts.key);
return await buildManifest(buildOpts, internals, Array.from(staticFiles), encodedKey);
const manifest = await buildManifest(buildOpts, internals, Array.from(staticFiles), encodedKey);
return manifest;
}

/**
Expand Down Expand Up @@ -207,32 +208,10 @@ async function buildManifest(
});
}

for (const route of opts.routesList.routes) {
if (!route.prerender) continue;
if (!route.pathname) continue;

const outFolder = getOutFolder(opts.settings, route.pathname, route);
const outFile = getOutFile(opts.settings.config, outFolder, route.pathname, route);
const file = outFile.toString().replace(opts.settings.config.build.client.toString(), '');
routes.push({
file,
links: [],
scripts: [],
styles: [],
routeData: serializeRouteData(route, settings.config.trailingSlash),
});
staticFiles.push(file);
}

const needsStaticHeaders = settings.adapter?.adapterFeatures?.experimentalStaticHeaders ?? false;

for (const route of opts.routesList.routes) {
const pageData = internals.pagesByKeys.get(makePageDataKey(route.route, route.component));
if (!pageData) continue;

if (route.prerender && route.type !== 'redirect' && !needsStaticHeaders) {
continue;
}
const scripts: SerializedRouteInfo['scripts'] = [];
if (settings.scripts.some((script) => script.stage === 'page')) {
const src = entryModules[PAGE_SCRIPT_ID];
Expand Down Expand Up @@ -264,6 +243,14 @@ async function buildManifest(
styles,
routeData: serializeRouteData(route, settings.config.trailingSlash),
});

// Add the built .html file as a staticFile
if(route.prerender && route.pathname) {
const outFolder = getOutFolder(opts.settings, route.pathname, route);
const outFile = getOutFile(opts.settings.config, outFolder, route.pathname, route);
const file = outFile.toString().replace(opts.settings.config.build.client.toString(), '');
staticFiles.push(file);
}
}

/**
Expand Down
35 changes: 34 additions & 1 deletion packages/astro/src/core/build/static-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,12 @@ async function buildEnvironments(
...(viteConfig.environments ?? {}),
prerender: {
build: {
emitAssets: true,
outDir: fileURLToPath(new URL('./.prerender/', out)),
rollupOptions: {
input: 'astro/entrypoints/prerender',
output: {
entryFileNames: 'prerender-entry.mjs',
entryFileNames: 'prerender-entry.[hash].mjs',
format: 'esm',
},
},
Expand All @@ -218,13 +219,19 @@ async function buildEnvironments(
},
client: {
build: {
emitAssets: true,
target: 'esnext',
emptyOutDir: false,
outDir: fileURLToPath(getClientOutputDirectory(settings)),
copyPublicDir: ssr,
sourcemap: false,
rollupOptions: {
preserveEntrySignatures: 'exports-only',
output: {
entryFileNames: `${settings.config.build.assets}/[name].[hash].js`,
chunkFileNames: `${settings.config.build.assets}/[name].[hash].js`,
assetFileNames: `${settings.config.build.assets}/[name].[hash][extname]`,
},
}
},
},
Expand Down Expand Up @@ -252,6 +259,9 @@ async function buildEnvironments(
// Build prerender environment for static generation
const prerenderOutput = await builder.build(builder.environments.prerender);

// Extract prerender entry filename and store in internals
extractPrerenderEntryFileName(internals, prerenderOutput);

// Build client environment
// We must discover client inputs after SSR build because hydration/client-only directives
// are only detected during SSR. We mutate the config here since the builder was already created
Expand All @@ -265,6 +275,29 @@ async function buildEnvironments(

type MutateChunk = (chunk: vite.Rollup.OutputChunk, targets: string[], newCode: string) => void;

/**
* Extracts the prerender entry filename from the build output
* and stores it in internals for later retrieval in generatePages.
*/
function extractPrerenderEntryFileName(
internals: BuildInternals,
prerenderOutput: vite.Rollup.RollupOutput | vite.Rollup.RollupOutput[],
) {
const outputs = Array.isArray(prerenderOutput) ? prerenderOutput : [prerenderOutput];

for (const output of outputs) {
for (const chunk of output.output) {
if (chunk.type !== 'asset' && 'fileName' in chunk) {
const fileName = chunk.fileName;
if (fileName.startsWith('prerender-entry')) {
internals.prerenderEntryFileName = fileName;
return;
}
}
}
}
}

async function runManifestInjection(
opts: StaticBuildOptions,
internals: BuildInternals,
Expand Down
9 changes: 0 additions & 9 deletions packages/astro/src/core/build/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,6 @@ export function shouldAppendForwardSlash(
}
}

export function i18nHasFallback(config: AstroConfig): boolean {
if (config.i18n && config.i18n.fallback) {
// we have some fallback and the control is not none
return Object.keys(config.i18n.fallback).length > 0;
}

return false;
}

export function encodeName(name: string): string {
// Detect if the chunk name has as % sign that is not encoded.
// This is borrowed from Node core: https://github.com/nodejs/node/blob/3838b579e44bf0c2db43171c3ce0da51eb6b05d5/lib/internal/url.js#L1382-L1391
Expand Down
Loading
Loading