Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
42 changes: 42 additions & 0 deletions packages/astro/src/core/app/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import type { ComponentInstance } from '../../types/astro.js';
import type { RewritePayload } from '../../types/public/common.js';
import type { RouteData, SSRElement } from '../../types/public/internal.js';
import { type HeadElements, Pipeline, type TryRewriteResult } from '../base-pipeline.js';
import type { SinglePageBuiltModule } from '../build/types.js';
import { RedirectSinglePageBuiltModule } from '../redirects/index.js';
import {
createAssetLink,
createModuleScriptElement,
createStylesheetElementSet,
} from '../render/ssr-element.js';
import { getFallbackRoute, routeIsFallback, routeIsRedirect } from '../routing/index.js';
import { findRouteToRewrite } from '../routing/rewrite.js';

export class AppPipeline extends Pipeline {
Expand Down Expand Up @@ -79,6 +82,45 @@ export class AppPipeline extends Pipeline {
return module.page();
}

async getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule> {
for (const defaultRoute of this.defaultRoutes) {
if (route.component === defaultRoute.component) {
return {
page: () => Promise.resolve(defaultRoute.instance),
renderers: [],
};
}
}
let routeToProcess = route;
if (routeIsRedirect(route)) {
if (route.redirectRoute) {
// This is a static redirect
routeToProcess = route.redirectRoute;
} else {
// This is an external redirect, so we return a component stub
return RedirectSinglePageBuiltModule;
}
} else if (routeIsFallback(route)) {
// This is a i18n fallback route
routeToProcess = getFallbackRoute(route, this.manifest.routes);
}

if (this.manifest.pageMap) {
const importComponentInstance = this.manifest.pageMap.get(routeToProcess.component);
if (!importComponentInstance) {
throw new Error(
`Unexpectedly unable to find a component instance for route ${route.route}`,
);
}
return await importComponentInstance();
} else if (this.manifest.pageModule) {
return this.manifest.pageModule;
}
throw new Error(
"Astro couldn't find the correct page to render, probably because it wasn't correctly mapped for SSR usage. This is an internal error, please file an issue.",
);
}

async tryRewrite(payload: RewritePayload, request: Request): Promise<TryRewriteResult> {
const { newUrl, pathname, routeData } = findRouteToRewrite({
payload,
Expand Down
44 changes: 25 additions & 19 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,18 @@ import type { BaseApp } from '../app/base.js';
import type { SSRManifest } from '../app/types.js';
import { NoPrerenderedRoutesWithDomains } from '../errors/errors-data.js';
import { AstroError, AstroErrorData } from '../errors/index.js';
import { getRedirectLocationOrThrow, routeIsRedirect } from '../redirects/index.js';
import { getRedirectLocationOrThrow } from '../redirects/index.js';
import { callGetStaticPaths } from '../render/route-cache.js';
import { createRequest } from '../request.js';
import { redirectTemplate } from '../routing/3xx.js';
import { getFallbackRoute, routeIsFallback, routeIsRedirect } from '../routing/helpers.js';
import { matchRoute } from '../routing/match.js';
import { stringifyParams } from '../routing/params.js';
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 { SinglePageBuiltModule, StaticBuildOptions } from './types.js';
import type { StaticBuildOptions } from './types.js';
import { getTimeStat, shouldAppendForwardSlash } from './util.js';

export async function generatePages(
Expand Down Expand Up @@ -72,8 +73,6 @@ export async function generatePages(
const routeToHeaders: RouteToHeaders = new Map();

const app = prerenderEntry.app as BaseApp;
const pageMap = prerenderEntry.pageMap as Map<string, () => Promise<SinglePageBuiltModule>>;

if (ssr) {
for (const [routeData, _] of pagesToGenerate) {
if (routeData.prerender) {
Expand All @@ -85,12 +84,12 @@ export async function generatePages(
});
}

await generatePage(app, pageMap, routeData, builtPaths, pipeline, routeToHeaders);
await generatePage(app, routeData, builtPaths, pipeline, routeToHeaders);
}
}
} else {
for (const [routeData, _] of pagesToGenerate) {
await generatePage(app, pageMap, routeData, builtPaths, pipeline, routeToHeaders);
await generatePage(app, routeData, builtPaths, pipeline, routeToHeaders);
}
}
logger.info(
Expand Down Expand Up @@ -198,7 +197,6 @@ const THRESHOLD_SLOW_RENDER_TIME_MS = 500;

async function generatePage(
app: BaseApp,
pageMap: Map<string, () => Promise<SinglePageBuiltModule>>,
routeData: RouteData,
builtPaths: Set<string>,
pipeline: BuildPipeline,
Expand Down Expand Up @@ -239,12 +237,17 @@ async function generatePage(

const timeEnd = performance.now();
const isSlow = timeEnd - timeStart > THRESHOLD_SLOW_RENDER_TIME_MS;
const timeIncrease = (isSlow ? colors.red : colors.dim)(`(+${getTimeStat(timeStart, timeEnd)})`);
const timeIncrease = (isSlow ? colors.red : colors.dim)(
`(+${getTimeStat(timeStart, timeEnd)})`,
);
const notCreated =
created === false ? colors.yellow('(file not created, response body was empty)') : '';

if (isConcurrent) {
logger.info(null, ` ${colors.blue(lineIcon)} ${colors.dim(filePath)} ${timeIncrease} ${notCreated}`);
logger.info(
null,
` ${colors.blue(lineIcon)} ${colors.dim(filePath)} ${timeIncrease} ${notCreated}`,
);
} else {
logger.info('SKIP_FORMAT', ` ${timeIncrease} ${notCreated}`);
}
Expand All @@ -260,7 +263,7 @@ async function generatePage(
logger.info(null, `${icon} ${getPrettyRouteName(route)}`);

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

// Generate each paths
if (config.build.concurrency > 1) {
Expand Down Expand Up @@ -291,8 +294,6 @@ function* eachRouteInRouteData(route: RouteData) {

async function getPathsForRoute(
route: RouteData,
componentPath: string,
pageMap: Map<string, () => Promise<SinglePageBuiltModule>>,
app: BaseApp,
pipeline: BuildPipeline,
builtPaths: Set<string>,
Expand All @@ -308,18 +309,23 @@ async function getPathsForRoute(
builtPaths.add(removeTrailingForwardSlash(route.pathname));
} else {
// Load page module only when we need it for getStaticPaths
const pageModuleFn = pageMap.get(componentPath);
if (!pageModuleFn) {
const pageModule = await app.pipeline.getComponentByRoute(route);
Copy link
Contributor

Choose a reason for hiding this comment

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

ah very cool, I assume this is what prevents us from needing to pass through the pageMap now, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes exactly! The method reads the pageMap, which is injected into the manifest


if (!pageModule) {
throw new Error(
`Unable to find module for ${componentPath}. This is unexpected and likely a bug in Astro, please report.`,
`Unable to find module for ${route.component}. This is unexpected and likely a bug in Astro, please report.`,
);
}
const pageModule = await pageModuleFn();
const mod = await pageModule.page();

const routeToProcess = routeIsRedirect(route)
? route.redirectRoute
: routeIsFallback(route)
? getFallbackRoute(route, pipeline.manifest.routes)
: route;

const staticPaths = await callGetStaticPaths({
mod,
route,
mod: pageModule,
route: routeToProcess ?? route,
routeCache,
ssr: manifest.serverLike,
base: manifest.base,
Expand Down
31 changes: 28 additions & 3 deletions packages/astro/src/core/build/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ 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';
import type { TryRewriteResult } from '../base-pipeline.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 { getFallbackRoute, routeIsFallback, routeIsRedirect } from '../routing/helpers.js';
import { findRouteToRewrite } from '../routing/rewrite.js';
import { getOutDirWithinCwd } from './common.js';
import { type BuildInternals, cssOrder, getPageData, mergeInlineCss } from './internal.js';
Expand Down Expand Up @@ -144,6 +144,17 @@ export class BuildPipeline extends Pipeline {
const pages = new Map<RouteData, string>();

for (const { routeData } of this.manifest.routes) {
if (routeIsRedirect(routeData)) {
// the component path isn't really important for redirects
pages.set(routeData, '');
continue;
}

if (routeIsFallback(routeData) && i18nHasFallback(this.manifest)) {
pages.set(routeData, '');
continue;
}

// Here, we take the component path and transform it in the virtual module name
const moduleSpecifier = getVirtualModulePageName(
VIRTUAL_PAGE_RESOLVED_MODULE_ID,
Expand Down Expand Up @@ -202,6 +213,7 @@ export class BuildPipeline extends Pipeline {
// SAFETY: it is checked inside the if
return this.#componentsInterner.get(route)!;
}

let entry;
if (routeIsRedirect(route)) {
entry = await this.#getEntryForRedirectRoute(route, this.outFolder);
Expand All @@ -222,8 +234,12 @@ export class BuildPipeline extends Pipeline {
if (route.type !== 'fallback') {
throw new Error(`Expected a redirect route.`);
}
if (route.redirectRoute) {
const filePath = getEntryFilePath(this.internals, route.redirectRoute);

// Retrieve the route where we should fall back
let fallbackRoute = getFallbackRoute(route, this.manifest.routes);

if (fallbackRoute) {
const filePath = getEntryFilePath(this.internals, fallbackRoute);
if (filePath) {
const url = createEntryURL(filePath, outFolder);
const ssrEntryPage: SinglePageBuiltModule = await import(url.toString());
Expand Down Expand Up @@ -265,3 +281,12 @@ function getEntryFilePath(internals: BuildInternals, pageData: RouteData) {
const id = '\x00' + getVirtualModulePageName(VIRTUAL_PAGE_MODULE_ID, pageData.component);
return internals.entrySpecifierToBundleMap.get(id);
}

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

return false;
}
Loading
Loading