From ee38b3a94697fe883ce8300eff9f001470b8adb6 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Fri, 13 Sep 2024 11:44:55 +0200 Subject: [PATCH] refactor(next): send `IntegrationRouteData` to integrations (#11864) Co-authored-by: Luiz Ferraz Co-authored-by: Alexander Niebuhr Co-authored-by: Florian Lefebvre --- .changeset/brave-elephants-fly.md | 21 +++++ .changeset/fuzzy-pugs-live.md | 21 +++++ .changeset/ten-walls-tap.md | 24 +++++ packages/astro/src/core/build/generate.ts | 6 +- .../astro/src/core/routing/manifest/create.ts | 3 + packages/astro/src/integrations/hooks.ts | 28 +++++- .../astro/src/types/public/integrations.ts | 17 +++- packages/astro/src/types/public/internal.ts | 88 ++++++++++++++++++- .../test/static-build-page-dist-url.test.js | 4 +- 9 files changed, 203 insertions(+), 9 deletions(-) create mode 100644 .changeset/brave-elephants-fly.md create mode 100644 .changeset/fuzzy-pugs-live.md create mode 100644 .changeset/ten-walls-tap.md diff --git a/.changeset/brave-elephants-fly.md b/.changeset/brave-elephants-fly.md new file mode 100644 index 0000000000..0c76730279 --- /dev/null +++ b/.changeset/brave-elephants-fly.md @@ -0,0 +1,21 @@ +--- +'astro': major +--- + +### [changed]: `entryPoint` type inside the hook `astro:build:ssr` +In Astro v4.x, the `entryPoint` type was `RouteData`. + +Astro v5.0 the `entryPoint` type is `IntegrationRouteData`, which contains a subset of the `RouteData` type. The fields `isIndex` and `fallbackRoutes` were removed. + +#### What should I do? +Update your adapter to change the type of `entryPoint` from `RouteData` to `IntegrationRouteData`. + +```diff +-import type {RouteData} from 'astro'; ++import type {IntegrationRouteData} from "astro" + +-function useRoute(route: RouteData) { ++function useRoute(route: IntegrationRouteData) { + +} +``` diff --git a/.changeset/fuzzy-pugs-live.md b/.changeset/fuzzy-pugs-live.md new file mode 100644 index 0000000000..bf1bd39dc7 --- /dev/null +++ b/.changeset/fuzzy-pugs-live.md @@ -0,0 +1,21 @@ +--- +'astro': major +--- + +### [changed]: `routes` type inside the hook `astro:build:done` +In Astro v4.x, the `routes` type was `RouteData`. + +Astro v5.0 the `routes` type is `IntegrationRouteData`, which contains a subset of the `RouteData` type. The fields `isIndex` and `fallbackRoutes` were removed. + +#### What should I do? +Update your adapter to change the type of `routes` from `RouteData` to `IntegrationRouteData`. + +```diff +-import type {RouteData} from 'astro'; ++import type {IntegrationRouteData} from "astro" + +-function useRoute(route: RouteData) { ++function useRoute(route: IntegrationRouteData) { + +} +``` diff --git a/.changeset/ten-walls-tap.md b/.changeset/ten-walls-tap.md new file mode 100644 index 0000000000..afc8c07ed8 --- /dev/null +++ b/.changeset/ten-walls-tap.md @@ -0,0 +1,24 @@ +--- +'astro': major +--- + +### [changed]: `RouteData.distURL` is now an array +In Astro v4.x, `RouteData.distURL` was `undefined` or a `URL` + +Astro v5.0, `RouteData.distURL` is `undefined` or an array of `URL`. This was a bug, because a route can generate multiple files on disk, especially when using dynamic routes such as `[slug]` or `[...slug]`. + +#### What should I do? +Update your code to handle `RouteData.distURL` as an array. + +```diff +if (route.distURL) { +- if (route.distURL.endsWith('index.html')) { +- // do something +- } ++ for (const url of route.distURL) { ++ if (url.endsWith('index.html')) { ++ // do something ++ } ++ } +} +``` diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index 9eaf4ff1d6..7c8776f1d0 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -466,7 +466,11 @@ async function generatePath( const outFolder = getOutFolder(pipeline.settings, pathname, route); const outFile = getOutFile(config, outFolder, pathname, route); - route.distURL = outFile; + if (route.distURL) { + route.distURL.push(outFile); + } else { + route.distURL = [outFile]; + } await fs.promises.mkdir(outFolder, { recursive: true }); await fs.promises.writeFile(outFile, body); diff --git a/packages/astro/src/core/routing/manifest/create.ts b/packages/astro/src/core/routing/manifest/create.ts index 834c37efe2..4e0fb964a8 100644 --- a/packages/astro/src/core/routing/manifest/create.ts +++ b/packages/astro/src/core/routing/manifest/create.ts @@ -254,6 +254,7 @@ function createFileBasedRoutes( pathname: pathname || undefined, prerender, fallbackRoutes: [], + distURL: [], }); } } @@ -318,6 +319,7 @@ function createInjectedRoutes({ settings, cwd }: CreateRouteManifestParams): Rou pathname: pathname || void 0, prerender: prerenderInjected ?? prerender, fallbackRoutes: [], + distURL: [], }); } @@ -386,6 +388,7 @@ function createRedirectRoutes( redirect: to, redirectRoute: routeMap.get(destination), fallbackRoutes: [], + distURL: [], }); } diff --git a/packages/astro/src/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts index 0c40e60cdb..18544ae800 100644 --- a/packages/astro/src/integrations/hooks.ts +++ b/packages/astro/src/integrations/hooks.ts @@ -25,6 +25,7 @@ import type { AstroIntegration, AstroRenderer, HookParameters, + IntegrationRouteData, RouteOptions, } from '../types/public/integrations.js'; import type { RouteData } from '../types/public/internal.js'; @@ -532,6 +533,10 @@ export async function runHookBuildSsr({ entryPoints, middlewareEntryPoint, }: RunHookBuildSsr) { + const entryPointsMap = new Map(); + for (const [key, value] of entryPoints) { + entryPointsMap.set(toIntegrationRouteData(key), value); + } for (const integration of config.integrations) { if (integration?.hooks?.['astro:build:ssr']) { await withTakingALongTimeMsg({ @@ -539,7 +544,7 @@ export async function runHookBuildSsr({ hookName: 'astro:build:ssr', hookResult: integration.hooks['astro:build:ssr']({ manifest, - entryPoints, + entryPoints: entryPointsMap, middlewareEntryPoint, logger: getLogger(integration, logger), }), @@ -592,7 +597,7 @@ export async function runHookBuildDone({ const dir = settings.buildOutput === 'server' ? settings.config.build.client : settings.config.outDir; await fsMod.promises.mkdir(dir, { recursive: true }); - + const integrationRoutes = routes.map(toIntegrationRouteData); for (const integration of settings.config.integrations) { if (integration?.hooks?.['astro:build:done']) { const logger = getLogger(integration, logging); @@ -603,7 +608,7 @@ export async function runHookBuildDone({ hookResult: integration.hooks['astro:build:done']({ pages: pages.map((p) => ({ pathname: p })), dir, - routes, + routes: integrationRoutes, logger, cacheManifest, }), @@ -653,3 +658,20 @@ export async function runHookRouteSetup({ ); } } + +function toIntegrationRouteData(route: RouteData): IntegrationRouteData { + return { + route: route.route, + component: route.component, + generate: route.generate, + params: route.params, + pathname: route.pathname, + segments: route.segments, + prerender: route.prerender, + redirect: route.redirect, + redirectRoute: route.redirectRoute ? toIntegrationRouteData(route.redirectRoute) : undefined, + type: route.type, + pattern: route.pattern, + distURL: route.distURL, + }; +} diff --git a/packages/astro/src/types/public/integrations.ts b/packages/astro/src/types/public/integrations.ts index 0836bdc9e9..1980c0ab10 100644 --- a/packages/astro/src/types/public/integrations.ts +++ b/packages/astro/src/types/public/integrations.ts @@ -206,7 +206,7 @@ export interface BaseIntegrationHooks { * This maps a {@link RouteData} to an {@link URL}, this URL represents * the physical file you should import. */ - entryPoints: Map; + entryPoints: Map; /** * File path of the emitted middleware */ @@ -228,7 +228,7 @@ export interface BaseIntegrationHooks { 'astro:build:done': (options: { pages: { pathname: string }[]; dir: URL; - routes: RouteData[]; + routes: IntegrationRouteData[]; logger: AstroIntegrationLogger; cacheManifest: boolean; }) => void | Promise; @@ -246,3 +246,16 @@ export interface AstroIntegration { [K in keyof Astro.IntegrationHooks]?: Astro.IntegrationHooks[K]; } & Partial>; } + +/** + * A smaller version of the {@link RouteData} that is used in the integrations. + */ +export type IntegrationRouteData = Omit< + RouteData, + 'isIndex' | 'fallbackRoutes' | 'redirectRoute' +> & { + /** + * {@link RouteData.redirectRoute} + */ + redirectRoute?: IntegrationRouteData; +}; diff --git a/packages/astro/src/types/public/internal.ts b/packages/astro/src/types/public/internal.ts index bee15c6f75..c970ab3d18 100644 --- a/packages/astro/src/types/public/internal.ts +++ b/packages/astro/src/types/public/internal.ts @@ -36,21 +36,105 @@ export interface SSRLoadedRendererValue { renderHydrationScript?: () => string; } +/** + * It contains the information about a route + */ export interface RouteData { + /** + * The current **pattern** of the route. For example: + * - `src/pages/index.astro` has a pattern of `/` + * - `src/pages/blog/[...slug].astro` has a pattern of `/blog/[...slug]` + * - `src/pages/site/[blog]/[...slug].astro` has a pattern of `/site/[blog]/[...slug]` + */ route: string; + /** + * Source component URL + */ component: string; + /** + * @param {any} data The optional parameters of the route + * + * @description + * A function that accepts a list of params, interpolates them with the route pattern, and returns the path name of the route. + * + * ## Example + * + * For a route such as `/blog/[...id].astro`, the `generate` function would return something like this: + * + * ```js + * console.log(generate({ id: 'presentation' })) // will log `/blog/presentation` + * ``` + */ generate: (data?: any) => string; + /** + * Dynamic and spread route params + * ex. "/pages/[lang]/[...slug].astro" will output the params ['lang', '...slug'] + */ params: string[]; + /** + * Output URL pathname where this route will be served + * note: will be undefined for [dynamic] and [...spread] routes + */ pathname?: string; - // expose the real path name on SSG - distURL?: URL; + /** + * The paths of the physical files emitted by this route. When a route **isn't** prerendered, the value is either `undefined` or an empty array. + */ + distURL?: URL[]; + /** + * + * regex used for matching an input URL against a requested route + * ex. "[fruit]/about.astro" will generate the pattern: /^\/([^/]+?)\/about\/?$/ + * where pattern.test("banana/about") is "true" + * + * ## Example + * + * ```js + * if (route.pattern.test('/blog')) { + * // do something + * } + * ``` + */ pattern: RegExp; + /** + * Similar to the "params" field, but with more associated metadata. For example, for `/site/[blog]/[...slug].astro`, the segments are: + * + * 1. `{ content: 'site', dynamic: false, spread: false }` + * 2. `{ content: 'blog', dynamic: true, spread: false }` + * 3. `{ content: '...slug', dynamic: true, spread: true }` + */ segments: RoutePart[][]; + /** + * + * The type of the route. It can be: + * - `page`: a route that lives in the file system, usually an Astro component + * - `endpoint`: a route that lives in the file system, usually a JS file that exposes endpoints methods + * - `redirect`: a route points to another route that lives in the file system + * - `fallback`: a route that doesn't exist in the file system that needs to be handled with other means, usually the middleware + */ type: RouteType; + /** + * Whether the route is prerendered or not + */ prerender: boolean; + /** + * The route to redirect to. It holds information regarding the status code and its destination. + */ redirect?: RedirectConfig; + /** + * The {@link RouteData} to redirect to. It's present when `RouteData.type` is `redirect`. + */ redirectRoute?: RouteData; + /** + * A list of {@link RouteData} to fallback to. They are present when `i18n.fallback` has a list of locales. + */ fallbackRoutes: RouteData[]; + + /** + * If this route is a directory index + * For example: + * - src/pages/index.astro + * - src/pages/blog/index.astro + */ isIndex: boolean; } diff --git a/packages/astro/test/static-build-page-dist-url.test.js b/packages/astro/test/static-build-page-dist-url.test.js index 72602274b9..ebfa75d0e9 100644 --- a/packages/astro/test/static-build-page-dist-url.test.js +++ b/packages/astro/test/static-build-page-dist-url.test.js @@ -25,7 +25,9 @@ describe('Static build: pages routes have distURL', () => { it('Pages routes have distURL', async () => { assert.equal(checkRoutes.length > 0, true, 'Pages not found: build end hook not being called'); checkRoutes.forEach((p) => { - assert.equal(p.distURL instanceof URL, true, `${p.pathname} doesn't include distURL`); + p.distURL.forEach((distURL) => { + assert.equal(distURL instanceof URL, true, `${p.pathname} doesn't include distURL`); + }); }); }); });