Skip to content

Commit 564ac6c

Browse files
feat: route manifest refactor (#12597)
Co-authored-by: Emanuele Stoppa <[email protected]>
1 parent 7c7398c commit 564ac6c

File tree

13 files changed

+80
-69
lines changed

13 files changed

+80
-69
lines changed

.changeset/empty-crews-scream.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes an issue where image and server islands routes would not be passed to the `astro:routes:resolved` hook during builds

packages/astro/src/assets/endpoint/config.ts

-11
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,6 @@ export function injectImageEndpoint(
1616
manifest.routes.unshift(getImageEndpointData(settings, mode, cwd));
1717
}
1818

19-
export function ensureImageEndpointRoute(
20-
settings: AstroSettings,
21-
manifest: ManifestData,
22-
mode: 'dev' | 'build',
23-
cwd?: string,
24-
) {
25-
if (!manifest.routes.some((route) => route.route === settings.config.image.endpoint.route)) {
26-
manifest.routes.unshift(getImageEndpointData(settings, mode, cwd));
27-
}
28-
}
29-
3019
function getImageEndpointData(
3120
settings: AstroSettings,
3221
mode: 'dev' | 'build',

packages/astro/src/core/app/index.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import {
2020
} from '../path.js';
2121
import { RenderContext } from '../render-context.js';
2222
import { createAssetLink } from '../render/ssr-element.js';
23-
import { createDefaultRoutes, injectDefaultRoutes } from '../routing/default.js';
23+
import { ensure404Route } from '../routing/astro-designed-error-pages.js';
24+
import { createDefaultRoutes } from '../routing/default.js';
2425
import { matchRoute } from '../routing/match.js';
2526
import { AppPipeline } from './pipeline.js';
2627

@@ -88,9 +89,12 @@ export class App {
8889

8990
constructor(manifest: SSRManifest, streaming = true) {
9091
this.#manifest = manifest;
91-
this.#manifestData = injectDefaultRoutes(manifest, {
92+
this.#manifestData = {
9293
routes: manifest.routes.map((route) => route.routeData),
93-
});
94+
};
95+
// This is necessary to allow running middlewares for 404 in SSR. There's special handling
96+
// to return the host 404 if the user doesn't provide a custom 404
97+
ensure404Route(this.#manifestData);
9498
this.#baseWithoutTrailingSlash = removeTrailingForwardSlash(this.#manifest.base);
9599
this.#pipeline = this.#createPipeline(this.#manifestData, streaming);
96100
this.#adapterLogger = new AstroIntegrationLogger(

packages/astro/src/core/build/index.ts

+1-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { performance } from 'node:perf_hooks';
33
import { fileURLToPath } from 'node:url';
44
import { blue, bold, green } from 'kleur/colors';
55
import type * as vite from 'vite';
6-
import { injectImageEndpoint } from '../../assets/endpoint/config.js';
76
import { telemetry } from '../../events/index.js';
87
import { eventCliSession } from '../../events/session.js';
98
import {
@@ -24,7 +23,6 @@ import type { Logger } from '../logger/core.js';
2423
import { levels, timerMessage } from '../logger/core.js';
2524
import { apply as applyPolyfill } from '../polyfill.js';
2625
import { createRouteManifest } from '../routing/index.js';
27-
import { getServerIslandRouteData } from '../server-islands/endpoint.js';
2826
import { clearContentLayerCache } from '../sync/index.js';
2927
import { ensureProcessNodeEnv } from '../util.js';
3028
import { collectPagesData } from './page-data.js';
@@ -123,10 +121,6 @@ class AstroBuilder {
123121

124122
this.manifest = await createRouteManifest({ settings: this.settings }, this.logger);
125123

126-
if (this.settings.buildOutput === 'server') {
127-
injectImageEndpoint(this.settings, this.manifest, 'build');
128-
}
129-
130124
await runHookConfigDone({ settings: this.settings, logger: logger, command: 'build' });
131125

132126
// If we're building for the server, we need to ensure that an adapter is installed.
@@ -239,8 +233,7 @@ class AstroBuilder {
239233
pages: pageNames,
240234
routes: Object.values(allPages)
241235
.flat()
242-
.map((pageData) => pageData.route)
243-
.concat(hasServerIslands ? getServerIslandRouteData(this.settings.config) : []),
236+
.map((pageData) => pageData.route),
244237
logging: this.logger,
245238
});
246239

packages/astro/src/core/build/page-data.ts

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { AllPagesData } from './types.js';
55
import * as colors from 'kleur/colors';
66
import { debug } from '../logger/core.js';
77
import { makePageDataKey } from './plugins/util.js';
8+
import { DEFAULT_COMPONENTS } from '../routing/default.js';
89

910
export interface CollectPagesDataOptions {
1011
settings: AstroSettings;
@@ -29,6 +30,11 @@ export function collectPagesData(opts: CollectPagesDataOptions): CollectPagesDat
2930
// and is then cached across all future SSR builds. In the past, we've had trouble
3031
// with parallelized builds without guaranteeing that this is called first.
3132
for (const route of manifest.routes) {
33+
// There's special handling in SSR
34+
if (DEFAULT_COMPONENTS.some((component) => route.component === component)) {
35+
continue;
36+
}
37+
3238
// Generate a unique key to identify each page in the build process.
3339
const key = makePageDataKey(route.route, route.component);
3440
// static route:

packages/astro/src/core/build/plugins/plugin-manifest.ts

+17
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { type BuildInternals, cssOrder, mergeInlineCss } from '../internal.js';
2121
import type { AstroBuildPlugin } from '../plugin.js';
2222
import type { StaticBuildOptions } from '../types.js';
2323
import { makePageDataKey } from './util.js';
24+
import { DEFAULT_COMPONENTS } from '../../routing/default.js';
2425

2526
const manifestReplace = '@@ASTRO_MANIFEST_REPLACE@@';
2627
const replaceExp = new RegExp(`['"]${manifestReplace}['"]`, 'g');
@@ -172,6 +173,22 @@ function buildManifest(
172173
}
173174
};
174175

176+
// Default components follow a special flow during build. We prevent their processing earlier
177+
// in the build. As a result, they are not present on `internals.pagesByKeys` and not serialized
178+
// in the manifest file. But we need them in the manifest, so we handle them here
179+
for (const route of opts.manifest.routes) {
180+
if (!DEFAULT_COMPONENTS.find((component) => route.component === component)) {
181+
continue;
182+
}
183+
routes.push({
184+
file: '',
185+
links: [],
186+
scripts: [],
187+
styles: [],
188+
routeData: serializeRouteData(route, settings.config.trailingSlash),
189+
});
190+
}
191+
175192
for (const route of opts.manifest.routes) {
176193
if (!route.prerender) continue;
177194
if (!route.pathname) continue;

packages/astro/src/core/dev/container.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import * as vite from 'vite';
77
import {
88
runHookConfigDone,
99
runHookConfigSetup,
10-
runHookRoutesResolved,
1110
runHookServerDone,
1211
runHookServerStart,
1312
} from '../../integrations/hooks.js';
@@ -16,7 +15,6 @@ import { createDevelopmentManifest } from '../../vite-plugin-astro-server/plugin
1615
import { createVite } from '../create-vite.js';
1716
import type { Logger } from '../logger/core.js';
1817
import { apply as applyPolyfill } from '../polyfill.js';
19-
import { injectDefaultDevRoutes } from '../routing/dev-default.js';
2018
import { createRouteManifest } from '../routing/index.js';
2119
import { syncInternal } from '../sync/index.js';
2220
import { warnMissingAdapter } from './adapter-validation.js';
@@ -84,12 +82,9 @@ export async function createContainer({
8482
.filter(Boolean) as string[];
8583

8684
// Create the route manifest already outside of Vite so that `runHookConfigDone` can use it to inform integrations of the build output
87-
let manifest = await createRouteManifest({ settings, fsMod: fs }, logger, { dev: true });
85+
const manifest = await createRouteManifest({ settings, fsMod: fs }, logger, { dev: true });
8886
const devSSRManifest = createDevelopmentManifest(settings);
8987

90-
manifest = injectDefaultDevRoutes(settings, devSSRManifest, manifest);
91-
await runHookRoutesResolved({ settings, logger, routes: manifest.routes });
92-
9388
await runHookConfigDone({ settings, logger, command: 'dev' });
9489

9590
warnMissingAdapter(logger, settings);

packages/astro/src/core/routing/default.ts

+4-13
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,12 @@
1-
import type { ComponentInstance, ManifestData } from '../../types/astro.js';
1+
import type { ComponentInstance } from '../../types/astro.js';
22
import type { SSRManifest } from '../app/types.js';
33
import { DEFAULT_404_COMPONENT } from '../constants.js';
44
import {
55
SERVER_ISLAND_COMPONENT,
66
SERVER_ISLAND_ROUTE,
77
createEndpoint as createServerIslandEndpoint,
8-
ensureServerIslandRoute,
98
} from '../server-islands/endpoint.js';
10-
import {
11-
DEFAULT_404_ROUTE,
12-
default404Instance,
13-
ensure404Route,
14-
} from './astro-designed-error-pages.js';
15-
16-
export function injectDefaultRoutes(ssrManifest: SSRManifest, routeManifest: ManifestData) {
17-
ensure404Route(routeManifest);
18-
ensureServerIslandRoute(ssrManifest, routeManifest);
19-
return routeManifest;
20-
}
9+
import { DEFAULT_404_ROUTE, default404Instance } from './astro-designed-error-pages.js';
2110

2211
type DefaultRouteParams = {
2312
instance: ComponentInstance;
@@ -26,6 +15,8 @@ type DefaultRouteParams = {
2615
component: string;
2716
};
2817

18+
export const DEFAULT_COMPONENTS = [DEFAULT_404_COMPONENT, SERVER_ISLAND_COMPONENT];
19+
2920
export function createDefaultRoutes(manifest: SSRManifest): DefaultRouteParams[] {
3021
const root = new URL(manifest.hrefRoot);
3122
return [

packages/astro/src/core/routing/dev-default.ts

-14
This file was deleted.

packages/astro/src/core/routing/manifest/create.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import { routeComparator } from '../priority.js';
2121
import { getRouteGenerator } from './generator.js';
2222
import { getPattern } from './pattern.js';
2323
import { getRoutePrerenderOption } from './prerender.js';
24+
import { ensure404Route } from '../astro-designed-error-pages.js';
25+
import { injectImageEndpoint } from '../../../assets/endpoint/config.js';
26+
import { injectServerIslandRoute } from '../../server-islands/endpoint.js';
2427
const require = createRequire(import.meta.url);
2528

2629
interface Item {
@@ -732,9 +735,22 @@ export async function createRouteManifest(
732735
}
733736
}
734737

735-
if (!dev) {
736-
await runHookRoutesResolved({ routes, settings, logger });
738+
if (dev) {
739+
// In SSR, a 404 route is injected in the App directly for some special handling,
740+
// it must not appear in the manifest
741+
ensure404Route({ routes });
737742
}
743+
if (dev || settings.buildOutput === 'server') {
744+
injectImageEndpoint(settings, { routes }, dev ? 'dev' : 'build');
745+
// Ideally we would only inject the server islands route if server islands are used in the project.
746+
// Unforunately, there is a "circular dependency": to know if server islands are used, we need to run
747+
// the build but the build relies on the routes manifest.
748+
// This situation also means we cannot update the buildOutput based on wether or not server islands
749+
// are used in the project. If server islands are detected after the build but the buildOutput is
750+
// static, we fail the build.
751+
injectServerIslandRoute(settings.config, { routes });
752+
}
753+
await runHookRoutesResolved({ routes, settings, logger });
738754

739755
return {
740756
routes,

packages/astro/src/core/server-islands/endpoint.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,7 @@ export function getServerIslandRouteData(config: ConfigFields) {
3737
return route;
3838
}
3939

40-
export function ensureServerIslandRoute(config: ConfigFields, routeManifest: ManifestData) {
41-
if (routeManifest.routes.some((route) => route.route === '/_server-islands/[name]')) {
42-
return;
43-
}
44-
40+
export function injectServerIslandRoute(config: ConfigFields, routeManifest: ManifestData) {
4541
routeManifest.routes.unshift(getServerIslandRouteData(config));
4642
}
4743

packages/astro/src/vite-plugin-astro-server/plugin.ts

+2-7
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { patchOverlay } from '../core/errors/overlay.js';
1313
import type { Logger } from '../core/logger/core.js';
1414
import { NOOP_MIDDLEWARE_FN } from '../core/middleware/noop-middleware.js';
1515
import { createViteLoader } from '../core/module-loader/index.js';
16-
import { injectDefaultDevRoutes } from '../core/routing/dev-default.js';
1716
import { createRouteManifest } from '../core/routing/index.js';
1817
import { getRoutePrerenderOption } from '../core/routing/manifest/prerender.js';
1918
import { toFallbackType, toRoutingStrategy } from '../i18n/utils.js';
@@ -74,15 +73,11 @@ export default function createVitePluginAstroServer({
7473
try {
7574
const content = await fsMod.promises.readFile(routePath, 'utf-8');
7675
await getRoutePrerenderOption(content, route, settings, logger);
76+
await runHookRoutesResolved({ routes: routeManifest.routes, settings, logger });
7777
} catch (_) {}
7878
} else {
79-
routeManifest = injectDefaultDevRoutes(
80-
settings,
81-
devSSRManifest,
82-
await createRouteManifest({ settings, fsMod }, logger, { dev: true }),
83-
);
79+
routeManifest = await createRouteManifest({ settings, fsMod }, logger, { dev: true });
8480
}
85-
await runHookRoutesResolved({ routes: routeManifest.routes, settings, logger });
8681

8782
warnMissingAdapter(logger, settings);
8883
pipeline.manifest.checkOrigin =

packages/astro/test/units/routing/manifest.test.js

+18
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,14 @@ describe('routing - createRouteManifest', () => {
260260
});
261261

262262
assert.deepEqual(getManifestRoutes(manifest), [
263+
{
264+
route: '/_server-islands/[name]',
265+
type: 'page',
266+
},
267+
{
268+
route: '/_image',
269+
type: 'endpoint',
270+
},
263271
{
264272
route: '/blog/[...slug]',
265273
type: 'page',
@@ -306,6 +314,14 @@ describe('routing - createRouteManifest', () => {
306314
});
307315

308316
assert.deepEqual(getManifestRoutes(manifest), [
317+
{
318+
route: '/_server-islands/[name]',
319+
type: 'page',
320+
},
321+
{
322+
route: '/_image',
323+
type: 'endpoint',
324+
},
309325
{
310326
route: '/blog/about',
311327
type: 'redirect',
@@ -441,6 +457,8 @@ describe('routing - createRouteManifest', () => {
441457
});
442458

443459
assert.deepEqual(getManifestRoutes(manifest), [
460+
{ type: 'page', route: '/_server-islands/[name]' },
461+
{ type: 'endpoint', route: '/_image' },
444462
{ type: 'endpoint', route: '/blog/a-[b].233' },
445463
{ type: 'redirect', route: '/posts/a-[b].233' },
446464
{ type: 'page', route: '/[c]-d' },

0 commit comments

Comments
 (0)