mirror of
https://github.com/withastro/astro.git
synced 2024-12-30 22:03:56 -05:00
feat: route manifest refactor (#12597)
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
This commit is contained in:
parent
7c7398c046
commit
564ac6c2f2
13 changed files with 80 additions and 69 deletions
5
.changeset/empty-crews-scream.md
Normal file
5
.changeset/empty-crews-scream.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixes an issue where image and server islands routes would not be passed to the `astro:routes:resolved` hook during builds
|
|
@ -16,17 +16,6 @@ export function injectImageEndpoint(
|
||||||
manifest.routes.unshift(getImageEndpointData(settings, mode, cwd));
|
manifest.routes.unshift(getImageEndpointData(settings, mode, cwd));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ensureImageEndpointRoute(
|
|
||||||
settings: AstroSettings,
|
|
||||||
manifest: ManifestData,
|
|
||||||
mode: 'dev' | 'build',
|
|
||||||
cwd?: string,
|
|
||||||
) {
|
|
||||||
if (!manifest.routes.some((route) => route.route === settings.config.image.endpoint.route)) {
|
|
||||||
manifest.routes.unshift(getImageEndpointData(settings, mode, cwd));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getImageEndpointData(
|
function getImageEndpointData(
|
||||||
settings: AstroSettings,
|
settings: AstroSettings,
|
||||||
mode: 'dev' | 'build',
|
mode: 'dev' | 'build',
|
||||||
|
|
|
@ -20,7 +20,8 @@ import {
|
||||||
} from '../path.js';
|
} from '../path.js';
|
||||||
import { RenderContext } from '../render-context.js';
|
import { RenderContext } from '../render-context.js';
|
||||||
import { createAssetLink } from '../render/ssr-element.js';
|
import { createAssetLink } from '../render/ssr-element.js';
|
||||||
import { createDefaultRoutes, injectDefaultRoutes } from '../routing/default.js';
|
import { ensure404Route } from '../routing/astro-designed-error-pages.js';
|
||||||
|
import { createDefaultRoutes } from '../routing/default.js';
|
||||||
import { matchRoute } from '../routing/match.js';
|
import { matchRoute } from '../routing/match.js';
|
||||||
import { AppPipeline } from './pipeline.js';
|
import { AppPipeline } from './pipeline.js';
|
||||||
|
|
||||||
|
@ -88,9 +89,12 @@ export class App {
|
||||||
|
|
||||||
constructor(manifest: SSRManifest, streaming = true) {
|
constructor(manifest: SSRManifest, streaming = true) {
|
||||||
this.#manifest = manifest;
|
this.#manifest = manifest;
|
||||||
this.#manifestData = injectDefaultRoutes(manifest, {
|
this.#manifestData = {
|
||||||
routes: manifest.routes.map((route) => route.routeData),
|
routes: manifest.routes.map((route) => route.routeData),
|
||||||
});
|
};
|
||||||
|
// This is necessary to allow running middlewares for 404 in SSR. There's special handling
|
||||||
|
// to return the host 404 if the user doesn't provide a custom 404
|
||||||
|
ensure404Route(this.#manifestData);
|
||||||
this.#baseWithoutTrailingSlash = removeTrailingForwardSlash(this.#manifest.base);
|
this.#baseWithoutTrailingSlash = removeTrailingForwardSlash(this.#manifest.base);
|
||||||
this.#pipeline = this.#createPipeline(this.#manifestData, streaming);
|
this.#pipeline = this.#createPipeline(this.#manifestData, streaming);
|
||||||
this.#adapterLogger = new AstroIntegrationLogger(
|
this.#adapterLogger = new AstroIntegrationLogger(
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { performance } from 'node:perf_hooks';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { blue, bold, green } from 'kleur/colors';
|
import { blue, bold, green } from 'kleur/colors';
|
||||||
import type * as vite from 'vite';
|
import type * as vite from 'vite';
|
||||||
import { injectImageEndpoint } from '../../assets/endpoint/config.js';
|
|
||||||
import { telemetry } from '../../events/index.js';
|
import { telemetry } from '../../events/index.js';
|
||||||
import { eventCliSession } from '../../events/session.js';
|
import { eventCliSession } from '../../events/session.js';
|
||||||
import {
|
import {
|
||||||
|
@ -24,7 +23,6 @@ import type { Logger } from '../logger/core.js';
|
||||||
import { levels, timerMessage } from '../logger/core.js';
|
import { levels, timerMessage } from '../logger/core.js';
|
||||||
import { apply as applyPolyfill } from '../polyfill.js';
|
import { apply as applyPolyfill } from '../polyfill.js';
|
||||||
import { createRouteManifest } from '../routing/index.js';
|
import { createRouteManifest } from '../routing/index.js';
|
||||||
import { getServerIslandRouteData } from '../server-islands/endpoint.js';
|
|
||||||
import { clearContentLayerCache } from '../sync/index.js';
|
import { clearContentLayerCache } from '../sync/index.js';
|
||||||
import { ensureProcessNodeEnv } from '../util.js';
|
import { ensureProcessNodeEnv } from '../util.js';
|
||||||
import { collectPagesData } from './page-data.js';
|
import { collectPagesData } from './page-data.js';
|
||||||
|
@ -123,10 +121,6 @@ class AstroBuilder {
|
||||||
|
|
||||||
this.manifest = await createRouteManifest({ settings: this.settings }, this.logger);
|
this.manifest = await createRouteManifest({ settings: this.settings }, this.logger);
|
||||||
|
|
||||||
if (this.settings.buildOutput === 'server') {
|
|
||||||
injectImageEndpoint(this.settings, this.manifest, 'build');
|
|
||||||
}
|
|
||||||
|
|
||||||
await runHookConfigDone({ settings: this.settings, logger: logger, command: 'build' });
|
await runHookConfigDone({ settings: this.settings, logger: logger, command: 'build' });
|
||||||
|
|
||||||
// If we're building for the server, we need to ensure that an adapter is installed.
|
// If we're building for the server, we need to ensure that an adapter is installed.
|
||||||
|
@ -239,8 +233,7 @@ class AstroBuilder {
|
||||||
pages: pageNames,
|
pages: pageNames,
|
||||||
routes: Object.values(allPages)
|
routes: Object.values(allPages)
|
||||||
.flat()
|
.flat()
|
||||||
.map((pageData) => pageData.route)
|
.map((pageData) => pageData.route),
|
||||||
.concat(hasServerIslands ? getServerIslandRouteData(this.settings.config) : []),
|
|
||||||
logging: this.logger,
|
logging: this.logger,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import type { AllPagesData } from './types.js';
|
||||||
import * as colors from 'kleur/colors';
|
import * as colors from 'kleur/colors';
|
||||||
import { debug } from '../logger/core.js';
|
import { debug } from '../logger/core.js';
|
||||||
import { makePageDataKey } from './plugins/util.js';
|
import { makePageDataKey } from './plugins/util.js';
|
||||||
|
import { DEFAULT_COMPONENTS } from '../routing/default.js';
|
||||||
|
|
||||||
export interface CollectPagesDataOptions {
|
export interface CollectPagesDataOptions {
|
||||||
settings: AstroSettings;
|
settings: AstroSettings;
|
||||||
|
@ -29,6 +30,11 @@ export function collectPagesData(opts: CollectPagesDataOptions): CollectPagesDat
|
||||||
// and is then cached across all future SSR builds. In the past, we've had trouble
|
// and is then cached across all future SSR builds. In the past, we've had trouble
|
||||||
// with parallelized builds without guaranteeing that this is called first.
|
// with parallelized builds without guaranteeing that this is called first.
|
||||||
for (const route of manifest.routes) {
|
for (const route of manifest.routes) {
|
||||||
|
// There's special handling in SSR
|
||||||
|
if (DEFAULT_COMPONENTS.some((component) => route.component === component)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Generate a unique key to identify each page in the build process.
|
// Generate a unique key to identify each page in the build process.
|
||||||
const key = makePageDataKey(route.route, route.component);
|
const key = makePageDataKey(route.route, route.component);
|
||||||
// static route:
|
// static route:
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { type BuildInternals, cssOrder, mergeInlineCss } from '../internal.js';
|
||||||
import type { AstroBuildPlugin } from '../plugin.js';
|
import type { AstroBuildPlugin } from '../plugin.js';
|
||||||
import type { StaticBuildOptions } from '../types.js';
|
import type { StaticBuildOptions } from '../types.js';
|
||||||
import { makePageDataKey } from './util.js';
|
import { makePageDataKey } from './util.js';
|
||||||
|
import { DEFAULT_COMPONENTS } from '../../routing/default.js';
|
||||||
|
|
||||||
const manifestReplace = '@@ASTRO_MANIFEST_REPLACE@@';
|
const manifestReplace = '@@ASTRO_MANIFEST_REPLACE@@';
|
||||||
const replaceExp = new RegExp(`['"]${manifestReplace}['"]`, 'g');
|
const replaceExp = new RegExp(`['"]${manifestReplace}['"]`, 'g');
|
||||||
|
@ -172,6 +173,22 @@ function buildManifest(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Default components follow a special flow during build. We prevent their processing earlier
|
||||||
|
// in the build. As a result, they are not present on `internals.pagesByKeys` and not serialized
|
||||||
|
// in the manifest file. But we need them in the manifest, so we handle them here
|
||||||
|
for (const route of opts.manifest.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.manifest.routes) {
|
for (const route of opts.manifest.routes) {
|
||||||
if (!route.prerender) continue;
|
if (!route.prerender) continue;
|
||||||
if (!route.pathname) continue;
|
if (!route.pathname) continue;
|
||||||
|
|
|
@ -7,7 +7,6 @@ import * as vite from 'vite';
|
||||||
import {
|
import {
|
||||||
runHookConfigDone,
|
runHookConfigDone,
|
||||||
runHookConfigSetup,
|
runHookConfigSetup,
|
||||||
runHookRoutesResolved,
|
|
||||||
runHookServerDone,
|
runHookServerDone,
|
||||||
runHookServerStart,
|
runHookServerStart,
|
||||||
} from '../../integrations/hooks.js';
|
} from '../../integrations/hooks.js';
|
||||||
|
@ -16,7 +15,6 @@ import { createDevelopmentManifest } from '../../vite-plugin-astro-server/plugin
|
||||||
import { createVite } from '../create-vite.js';
|
import { createVite } from '../create-vite.js';
|
||||||
import type { Logger } from '../logger/core.js';
|
import type { Logger } from '../logger/core.js';
|
||||||
import { apply as applyPolyfill } from '../polyfill.js';
|
import { apply as applyPolyfill } from '../polyfill.js';
|
||||||
import { injectDefaultDevRoutes } from '../routing/dev-default.js';
|
|
||||||
import { createRouteManifest } from '../routing/index.js';
|
import { createRouteManifest } from '../routing/index.js';
|
||||||
import { syncInternal } from '../sync/index.js';
|
import { syncInternal } from '../sync/index.js';
|
||||||
import { warnMissingAdapter } from './adapter-validation.js';
|
import { warnMissingAdapter } from './adapter-validation.js';
|
||||||
|
@ -84,12 +82,9 @@ export async function createContainer({
|
||||||
.filter(Boolean) as string[];
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
// Create the route manifest already outside of Vite so that `runHookConfigDone` can use it to inform integrations of the build output
|
// Create the route manifest already outside of Vite so that `runHookConfigDone` can use it to inform integrations of the build output
|
||||||
let manifest = await createRouteManifest({ settings, fsMod: fs }, logger, { dev: true });
|
const manifest = await createRouteManifest({ settings, fsMod: fs }, logger, { dev: true });
|
||||||
const devSSRManifest = createDevelopmentManifest(settings);
|
const devSSRManifest = createDevelopmentManifest(settings);
|
||||||
|
|
||||||
manifest = injectDefaultDevRoutes(settings, devSSRManifest, manifest);
|
|
||||||
await runHookRoutesResolved({ settings, logger, routes: manifest.routes });
|
|
||||||
|
|
||||||
await runHookConfigDone({ settings, logger, command: 'dev' });
|
await runHookConfigDone({ settings, logger, command: 'dev' });
|
||||||
|
|
||||||
warnMissingAdapter(logger, settings);
|
warnMissingAdapter(logger, settings);
|
||||||
|
|
|
@ -1,23 +1,12 @@
|
||||||
import type { ComponentInstance, ManifestData } from '../../types/astro.js';
|
import type { ComponentInstance } from '../../types/astro.js';
|
||||||
import type { SSRManifest } from '../app/types.js';
|
import type { SSRManifest } from '../app/types.js';
|
||||||
import { DEFAULT_404_COMPONENT } from '../constants.js';
|
import { DEFAULT_404_COMPONENT } from '../constants.js';
|
||||||
import {
|
import {
|
||||||
SERVER_ISLAND_COMPONENT,
|
SERVER_ISLAND_COMPONENT,
|
||||||
SERVER_ISLAND_ROUTE,
|
SERVER_ISLAND_ROUTE,
|
||||||
createEndpoint as createServerIslandEndpoint,
|
createEndpoint as createServerIslandEndpoint,
|
||||||
ensureServerIslandRoute,
|
|
||||||
} from '../server-islands/endpoint.js';
|
} from '../server-islands/endpoint.js';
|
||||||
import {
|
import { DEFAULT_404_ROUTE, default404Instance } from './astro-designed-error-pages.js';
|
||||||
DEFAULT_404_ROUTE,
|
|
||||||
default404Instance,
|
|
||||||
ensure404Route,
|
|
||||||
} from './astro-designed-error-pages.js';
|
|
||||||
|
|
||||||
export function injectDefaultRoutes(ssrManifest: SSRManifest, routeManifest: ManifestData) {
|
|
||||||
ensure404Route(routeManifest);
|
|
||||||
ensureServerIslandRoute(ssrManifest, routeManifest);
|
|
||||||
return routeManifest;
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultRouteParams = {
|
type DefaultRouteParams = {
|
||||||
instance: ComponentInstance;
|
instance: ComponentInstance;
|
||||||
|
@ -26,6 +15,8 @@ type DefaultRouteParams = {
|
||||||
component: string;
|
component: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_COMPONENTS = [DEFAULT_404_COMPONENT, SERVER_ISLAND_COMPONENT];
|
||||||
|
|
||||||
export function createDefaultRoutes(manifest: SSRManifest): DefaultRouteParams[] {
|
export function createDefaultRoutes(manifest: SSRManifest): DefaultRouteParams[] {
|
||||||
const root = new URL(manifest.hrefRoot);
|
const root = new URL(manifest.hrefRoot);
|
||||||
return [
|
return [
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { ensureImageEndpointRoute } from '../../assets/endpoint/config.js';
|
|
||||||
import type { AstroSettings, ManifestData } from '../../types/astro.js';
|
|
||||||
import type { SSRManifest } from '../app/types.js';
|
|
||||||
import { injectDefaultRoutes } from './default.js';
|
|
||||||
|
|
||||||
export function injectDefaultDevRoutes(
|
|
||||||
settings: AstroSettings,
|
|
||||||
ssrManifest: SSRManifest,
|
|
||||||
routeManifest: ManifestData,
|
|
||||||
) {
|
|
||||||
ensureImageEndpointRoute(settings, routeManifest, 'dev');
|
|
||||||
injectDefaultRoutes(ssrManifest, routeManifest);
|
|
||||||
return routeManifest;
|
|
||||||
}
|
|
|
@ -21,6 +21,9 @@ import { routeComparator } from '../priority.js';
|
||||||
import { getRouteGenerator } from './generator.js';
|
import { getRouteGenerator } from './generator.js';
|
||||||
import { getPattern } from './pattern.js';
|
import { getPattern } from './pattern.js';
|
||||||
import { getRoutePrerenderOption } from './prerender.js';
|
import { getRoutePrerenderOption } from './prerender.js';
|
||||||
|
import { ensure404Route } from '../astro-designed-error-pages.js';
|
||||||
|
import { injectImageEndpoint } from '../../../assets/endpoint/config.js';
|
||||||
|
import { injectServerIslandRoute } from '../../server-islands/endpoint.js';
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
interface Item {
|
interface Item {
|
||||||
|
@ -732,9 +735,22 @@ export async function createRouteManifest(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dev) {
|
if (dev) {
|
||||||
await runHookRoutesResolved({ routes, settings, logger });
|
// In SSR, a 404 route is injected in the App directly for some special handling,
|
||||||
|
// it must not appear in the manifest
|
||||||
|
ensure404Route({ routes });
|
||||||
}
|
}
|
||||||
|
if (dev || settings.buildOutput === 'server') {
|
||||||
|
injectImageEndpoint(settings, { routes }, dev ? 'dev' : 'build');
|
||||||
|
// Ideally we would only inject the server islands route if server islands are used in the project.
|
||||||
|
// Unforunately, there is a "circular dependency": to know if server islands are used, we need to run
|
||||||
|
// the build but the build relies on the routes manifest.
|
||||||
|
// This situation also means we cannot update the buildOutput based on wether or not server islands
|
||||||
|
// are used in the project. If server islands are detected after the build but the buildOutput is
|
||||||
|
// static, we fail the build.
|
||||||
|
injectServerIslandRoute(settings.config, { routes });
|
||||||
|
}
|
||||||
|
await runHookRoutesResolved({ routes, settings, logger });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
routes,
|
routes,
|
||||||
|
|
|
@ -37,11 +37,7 @@ export function getServerIslandRouteData(config: ConfigFields) {
|
||||||
return route;
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ensureServerIslandRoute(config: ConfigFields, routeManifest: ManifestData) {
|
export function injectServerIslandRoute(config: ConfigFields, routeManifest: ManifestData) {
|
||||||
if (routeManifest.routes.some((route) => route.route === '/_server-islands/[name]')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
routeManifest.routes.unshift(getServerIslandRouteData(config));
|
routeManifest.routes.unshift(getServerIslandRouteData(config));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { patchOverlay } from '../core/errors/overlay.js';
|
||||||
import type { Logger } from '../core/logger/core.js';
|
import type { Logger } from '../core/logger/core.js';
|
||||||
import { NOOP_MIDDLEWARE_FN } from '../core/middleware/noop-middleware.js';
|
import { NOOP_MIDDLEWARE_FN } from '../core/middleware/noop-middleware.js';
|
||||||
import { createViteLoader } from '../core/module-loader/index.js';
|
import { createViteLoader } from '../core/module-loader/index.js';
|
||||||
import { injectDefaultDevRoutes } from '../core/routing/dev-default.js';
|
|
||||||
import { createRouteManifest } from '../core/routing/index.js';
|
import { createRouteManifest } from '../core/routing/index.js';
|
||||||
import { getRoutePrerenderOption } from '../core/routing/manifest/prerender.js';
|
import { getRoutePrerenderOption } from '../core/routing/manifest/prerender.js';
|
||||||
import { toFallbackType, toRoutingStrategy } from '../i18n/utils.js';
|
import { toFallbackType, toRoutingStrategy } from '../i18n/utils.js';
|
||||||
|
@ -74,15 +73,11 @@ export default function createVitePluginAstroServer({
|
||||||
try {
|
try {
|
||||||
const content = await fsMod.promises.readFile(routePath, 'utf-8');
|
const content = await fsMod.promises.readFile(routePath, 'utf-8');
|
||||||
await getRoutePrerenderOption(content, route, settings, logger);
|
await getRoutePrerenderOption(content, route, settings, logger);
|
||||||
|
await runHookRoutesResolved({ routes: routeManifest.routes, settings, logger });
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
} else {
|
} else {
|
||||||
routeManifest = injectDefaultDevRoutes(
|
routeManifest = await createRouteManifest({ settings, fsMod }, logger, { dev: true });
|
||||||
settings,
|
|
||||||
devSSRManifest,
|
|
||||||
await createRouteManifest({ settings, fsMod }, logger, { dev: true }),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
await runHookRoutesResolved({ routes: routeManifest.routes, settings, logger });
|
|
||||||
|
|
||||||
warnMissingAdapter(logger, settings);
|
warnMissingAdapter(logger, settings);
|
||||||
pipeline.manifest.checkOrigin =
|
pipeline.manifest.checkOrigin =
|
||||||
|
|
|
@ -260,6 +260,14 @@ describe('routing - createRouteManifest', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.deepEqual(getManifestRoutes(manifest), [
|
assert.deepEqual(getManifestRoutes(manifest), [
|
||||||
|
{
|
||||||
|
route: '/_server-islands/[name]',
|
||||||
|
type: 'page',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: '/_image',
|
||||||
|
type: 'endpoint',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
route: '/blog/[...slug]',
|
route: '/blog/[...slug]',
|
||||||
type: 'page',
|
type: 'page',
|
||||||
|
@ -306,6 +314,14 @@ describe('routing - createRouteManifest', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.deepEqual(getManifestRoutes(manifest), [
|
assert.deepEqual(getManifestRoutes(manifest), [
|
||||||
|
{
|
||||||
|
route: '/_server-islands/[name]',
|
||||||
|
type: 'page',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: '/_image',
|
||||||
|
type: 'endpoint',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
route: '/blog/about',
|
route: '/blog/about',
|
||||||
type: 'redirect',
|
type: 'redirect',
|
||||||
|
@ -441,6 +457,8 @@ describe('routing - createRouteManifest', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.deepEqual(getManifestRoutes(manifest), [
|
assert.deepEqual(getManifestRoutes(manifest), [
|
||||||
|
{ type: 'page', route: '/_server-islands/[name]' },
|
||||||
|
{ type: 'endpoint', route: '/_image' },
|
||||||
{ type: 'endpoint', route: '/blog/a-[b].233' },
|
{ type: 'endpoint', route: '/blog/a-[b].233' },
|
||||||
{ type: 'redirect', route: '/posts/a-[b].233' },
|
{ type: 'redirect', route: '/posts/a-[b].233' },
|
||||||
{ type: 'page', route: '/[c]-d' },
|
{ type: 'page', route: '/[c]-d' },
|
||||||
|
|
Loading…
Reference in a new issue