Skip to content

Commit

Permalink
feat(next): astro:routes:resolved (#12329)
Browse files Browse the repository at this point in the history
Co-authored-by: Luiz Ferraz <[email protected]>
Co-authored-by: Emanuele Stoppa <[email protected]>
Co-authored-by: Sarah Rainsberger <[email protected]>
  • Loading branch information
4 people authored Nov 21, 2024
1 parent 3f02d5f commit 8309c61
Show file tree
Hide file tree
Showing 16 changed files with 434 additions and 35 deletions.
50 changes: 50 additions & 0 deletions .changeset/giant-ravens-look.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
'astro': minor
---

Adds a new `astro:routes:resolved` hook to the Integration API. Also update the `astro:build:done` hook by deprecating `routes` and adding a new `assets` map.

When building an integration, you can now get access to routes inside the `astro:routes:resolved` hook:

```js
const integration = () => {
return {
name: 'my-integration',
hooks: {
'astro:routes:resolved': ({ routes }) => {
console.log(routes)
}
}
}
}
```

This hook runs before `astro:config:done`, and whenever a route changes in development.

The `routes` array from `astro:build:done` is now deprecated, and exposed properties are now available on `astro:routes:resolved`, except for `distURL`. For this, you can use the newly exposed `assets` map:

```diff
const integration = () => {
+ let routes
return {
name: 'my-integration',
hooks: {
+ 'astro:routes:resolved': (params) => {
+ routes = params.routes
+ },
'astro:build:done': ({
- routes
+ assets
}) => {
+ for (const route of routes) {
+ const distURL = assets.get(route.pattern)
+ if (distURL) {
+ Object.assign(route, { distURL })
+ }
+ }
console.log(routes)
}
}
}
}
```
3 changes: 2 additions & 1 deletion packages/astro/src/actions/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ export default function astroIntegrationActionsRouteHandler({
name: VIRTUAL_MODULE_ID,
hooks: {
async 'astro:config:setup'(params) {
params.injectRoute({
settings.injectedRoutes.push({
pattern: ACTION_RPC_ROUTE_PATTERN,
entrypoint: 'astro/actions/runtime/route.js',
prerender: false,
origin: 'internal',
});

params.addMiddleware({
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/assets/endpoint/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,6 @@ function getImageEndpointData(
pathname: settings.config.image.endpoint.route,
prerender: false,
fallbackRoutes: [],
origin: 'internal',
};
}
1 change: 1 addition & 0 deletions packages/astro/src/container/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@ export class experimental_AstroContainer {
type,
fallbackRoutes: [],
isIndex: false,
origin: 'internal',
};
}

Expand Down
4 changes: 3 additions & 1 deletion packages/astro/src/core/dev/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as vite from 'vite';
import {
runHookConfigDone,
runHookConfigSetup,
runHookRoutesResolved,
runHookServerDone,
runHookServerStart,
} from '../../integrations/hooks.js';
Expand Down Expand Up @@ -83,10 +84,11 @@ export async function createContainer({
.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
let manifest = await createRouteManifest({ settings, fsMod: fs }, logger);
let manifest = await createRouteManifest({ settings, fsMod: fs }, logger, { dev: true });
const devSSRManifest = createDevelopmentManifest(settings);

manifest = injectDefaultDevRoutes(settings, devSSRManifest, manifest);
await runHookRoutesResolved({ settings, logger, routes: manifest.routes });

await runHookConfigDone({ settings, logger, command: 'dev' });

Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/core/routing/astro-designed-error-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const DEFAULT_404_ROUTE: RouteData = {
route: '/404',
fallbackRoutes: [],
isIndex: false,
origin: 'internal',
};

export const DEFAULT_500_ROUTE: RouteData = {
Expand All @@ -29,6 +30,7 @@ export const DEFAULT_500_ROUTE: RouteData = {
route: '/500',
fallbackRoutes: [],
isIndex: false,
origin: 'internal',
};

export function ensure404Route(manifest: ManifestData) {
Expand Down
11 changes: 10 additions & 1 deletion packages/astro/src/core/routing/manifest/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { routeComparator } from '../priority.js';
import { getRouteGenerator } from './generator.js';
import { getPattern } from './pattern.js';
import { getRoutePrerenderOption } from './prerender.js';
import { runHookRoutesResolved } from '../../../integrations/hooks.js';
const require = createRequire(import.meta.url);

interface Item {
Expand Down Expand Up @@ -255,6 +256,7 @@ function createFileBasedRoutes(
prerender,
fallbackRoutes: [],
distURL: [],
origin: 'project',
});
}
}
Expand All @@ -280,7 +282,7 @@ function createInjectedRoutes({ settings, cwd }: CreateRouteManifestParams): Rou
const routes: RouteData[] = [];

for (const injectedRoute of settings.injectedRoutes) {
const { pattern: name, entrypoint, prerender: prerenderInjected } = injectedRoute;
const { pattern: name, entrypoint, prerender: prerenderInjected, origin } = injectedRoute;
const { resolved, component } = resolveInjectedRoute(entrypoint.toString(), config.root, cwd);

const segments = removeLeadingForwardSlash(name)
Expand Down Expand Up @@ -320,6 +322,7 @@ function createInjectedRoutes({ settings, cwd }: CreateRouteManifestParams): Rou
prerender: prerenderInjected ?? prerender,
fallbackRoutes: [],
distURL: [],
origin,
});
}

Expand Down Expand Up @@ -389,6 +392,7 @@ function createRedirectRoutes(
redirectRoute: routeMap.get(destination),
fallbackRoutes: [],
distURL: [],
origin: 'project',
});
}

Expand Down Expand Up @@ -480,6 +484,7 @@ function detectRouteCollision(a: RouteData, b: RouteData, _config: AstroConfig,
export async function createRouteManifest(
params: CreateRouteManifestParams,
logger: Logger,
{ dev = false }: { dev?: boolean } = {},
): Promise<ManifestData> {
const { settings } = params;
const { config } = settings;
Expand Down Expand Up @@ -727,6 +732,10 @@ export async function createRouteManifest(
}
}

if (!dev) {
await runHookRoutesResolved({ routes, settings, logger });
}

return {
routes,
};
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/routing/manifest/serialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ export function deserializeRouteData(rawRouteData: SerializedRouteData): RouteDa
return deserializeRouteData(fallback);
}),
isIndex: rawRouteData.isIndex,
origin: rawRouteData.origin,
};
}
1 change: 1 addition & 0 deletions packages/astro/src/core/server-islands/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export function getServerIslandRouteData(config: ConfigFields) {
isIndex: false,
fallbackRoutes: [],
route: SERVER_ISLAND_ROUTE,
origin: 'internal',
};
return route;
}
Expand Down
50 changes: 48 additions & 2 deletions packages/astro/src/integrations/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import type {
import type {
AstroIntegration,
AstroRenderer,
BaseIntegrationHooks,
HookParameters,
IntegrationResolvedRoute,
IntegrationRouteData,
RouteOptions,
} from '../types/public/integrations.js';
Expand All @@ -39,7 +41,7 @@ async function withTakingALongTimeMsg<T>({
logger,
}: {
name: string;
hookName: string;
hookName: keyof BaseIntegrationHooks;
hookResult: T | Promise<T>;
timeoutMs?: number;
logger: Logger;
Expand Down Expand Up @@ -204,7 +206,7 @@ export async function runHookConfigSetup({
);
injectRoute.entrypoint = injectRoute.entryPoint as string;
}
updatedSettings.injectedRoutes.push(injectRoute);
updatedSettings.injectedRoutes.push({ ...injectRoute, origin: 'external' });
},
addWatchFile: (path) => {
updatedSettings.watchFiles.push(path instanceof URL ? fileURLToPath(path) : path);
Expand Down Expand Up @@ -599,6 +601,9 @@ export async function runHookBuildDone({ settings, pages, routes, logging }: Run
pages: pages.map((p) => ({ pathname: p })),
dir,
routes: integrationRoutes,
assets: new Map(
routes.filter((r) => r.distURL !== undefined).map((r) => [r.route, r.distURL!]),
),
logger,
}),
logger: logging,
Expand Down Expand Up @@ -648,6 +653,47 @@ export async function runHookRouteSetup({
}
}

export async function runHookRoutesResolved({
routes,
settings,
logger,
}: { routes: Array<RouteData>; settings: AstroSettings; logger: Logger }) {
for (const integration of settings.config.integrations) {
if (integration?.hooks?.['astro:routes:resolved']) {
const integrationLogger = getLogger(integration, logger);

await withTakingALongTimeMsg({
name: integration.name,
hookName: 'astro:routes:resolved',
hookResult: integration.hooks['astro:routes:resolved']({
routes: routes.map((route) => toIntegrationResolvedRoute(route)),
logger: integrationLogger,
}),
logger,
});
}
}
}

function toIntegrationResolvedRoute(route: RouteData): IntegrationResolvedRoute {
return {
isPrerendered: route.prerender,
entrypoint: route.component,
pattern: route.route,
params: route.params,
origin: route.origin,
generate: route.generate,
patternRegex: route.pattern,
segments: route.segments,
type: route.type,
pathname: route.pathname,
redirect: route.redirect,
redirectRoute: route.redirectRoute
? toIntegrationResolvedRoute(route.redirectRoute)
: undefined,
};
}

function toIntegrationRouteData(route: RouteData): IntegrationRouteData {
return {
route: route.route,
Expand Down
6 changes: 2 additions & 4 deletions packages/astro/src/types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ import type { ContentEntryType, DataEntryType } from './public/content.js';
import type {
AstroAdapter,
AstroRenderer,
InjectedRoute,
InjectedScriptStage,
InjectedType,
ResolvedInjectedRoute,
} from './public/integrations.js';
import type { RouteData } from './public/internal.js';
import type { InternalInjectedRoute, RouteData, ResolvedInjectedRoute } from './public/internal.js';
import type { DevToolbarAppEntry } from './public/toolbar.js';

export type SerializedRouteData = Omit<
Expand All @@ -35,7 +33,7 @@ export interface AstroSettings {
config: AstroConfig;
adapter: AstroAdapter | undefined;
preferences: AstroPreferences;
injectedRoutes: InjectedRoute[];
injectedRoutes: InternalInjectedRoute[];
resolvedInjectedRoutes: ResolvedInjectedRoute[];
pageExtensions: string[];
contentEntryTypes: ContentEntryType[];
Expand Down
52 changes: 41 additions & 11 deletions packages/astro/src/types/public/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { getToolbarServerCommunicationHelpers } from '../../integrations/ho
import type { DeepPartial } from '../../type-utils.js';
import type { AstroConfig } from './config.js';
import type { RefreshContentOptions } from './content.js';
import type { RouteData } from './internal.js';
import type { InternalInjectedRoute, RouteData } from './internal.js';
import type { DevToolbarAppEntry } from './toolbar.js';

export interface RouteOptions {
Expand Down Expand Up @@ -138,15 +138,7 @@ export type AstroAdapterFeatureMap = {
*/
export type InjectedScriptStage = 'before-hydration' | 'head-inline' | 'page' | 'page-ssr';

export interface InjectedRoute {
pattern: string;
entrypoint: string | URL;
prerender?: boolean;
}

export interface ResolvedInjectedRoute extends InjectedRoute {
resolvedEntryPoint?: URL;
}
export type InjectedRoute = Omit<InternalInjectedRoute, 'origin'>;

export interface InjectedType {
filename: string;
Expand Down Expand Up @@ -225,13 +217,19 @@ export interface BaseIntegrationHooks {
'astro:build:done': (options: {
pages: { pathname: string }[];
dir: URL;
/** @deprecated Use the `assets` map and the new `astro:routes:resolved` hook */
routes: IntegrationRouteData[];
assets: Map<string, URL[]>;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:route:setup': (options: {
route: RouteOptions;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:routes:resolved': (options: {
routes: IntegrationResolvedRoute[];
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
}

export interface AstroIntegration {
Expand All @@ -245,13 +243,45 @@ export interface AstroIntegration {

/**
* A smaller version of the {@link RouteData} that is used in the integrations.
* @deprecated Use {@link IntegrationResolvedRoute}
*/
export type IntegrationRouteData = Omit<
RouteData,
'isIndex' | 'fallbackRoutes' | 'redirectRoute'
'isIndex' | 'fallbackRoutes' | 'redirectRoute' | 'origin'
> & {
/**
* {@link RouteData.redirectRoute}
*/
redirectRoute?: IntegrationRouteData;
};

export interface IntegrationResolvedRoute
extends Pick<
RouteData,
'generate' | 'params' | 'pathname' | 'segments' | 'type' | 'redirect' | 'origin'
> {
/**
* {@link RouteData.route}
*/
pattern: RouteData['route'];

/**
* {@link RouteData.pattern}
*/
patternRegex: RouteData['pattern'];

/**
* {@link RouteData.component}
*/
entrypoint: RouteData['component'];

/**
* {@link RouteData.prerender}
*/
isPrerendered: RouteData['prerender'];

/**
* {@link RouteData.redirectRoute}
*/
redirectRoute?: IntegrationResolvedRoute;
}
Loading

0 comments on commit 8309c61

Please sign in to comment.