diff --git a/.changeset/beige-clowns-read.md b/.changeset/beige-clowns-read.md new file mode 100644 index 000000000000..826fc9d7c747 --- /dev/null +++ b/.changeset/beige-clowns-read.md @@ -0,0 +1,27 @@ +--- +'astro': major +--- + +Removes `entryPoints` on `astro:build:ssr` hook (Integration API) + +In Astro 5.0, `functionPerRoute` was deprecated. That meant that `entryPoints` on the `astro:build:ssr` hook was always empty. Astro integrations may have continued to work, even while `entryPoints` was not providing any useful data. + +Astro 6.0 removes the `entryPoints` map passed to this hook entirely. Integrations may no longer include `entryPoints`. + +#### What should I do? + +Remove any instance of `entryPoints` passed to `astro:build:ssr`. This should be safe to remove because this logic was not providing any useful data, but you may need to restructure your code accordingly for its removal: + +```diff +// my-integration.mjs +const integration = () => { + return { + name: 'my-integration', + hooks: { + 'astro:build:ssr': (params) => { +- someLogic(params.entryPoints) + }, + } + } +} +``` diff --git a/.changeset/cuddly-worlds-beam.md b/.changeset/cuddly-worlds-beam.md new file mode 100644 index 000000000000..133173256d90 --- /dev/null +++ b/.changeset/cuddly-worlds-beam.md @@ -0,0 +1,40 @@ +--- +'astro': major +--- + +Removes `routes` on `astro:build:done` hook (Integration API) + +In Astro 5.0, accessing `routes` on the `astro:build:done` hook was deprecated in favour of a new `astro:routes:resolved` hook. However, Astro integrations may have continued to function using the `routes` array. + +Astro 6.0 removes the `routes` array passed to this hook entirely. Instead, the `astro:routes:resolved` hook must be used. + +#### What should I do? + +Remove any instance of `routes` passed to `astro:build:done` in your Astro integration and replace it with the new `astro:routes:resolved` hook. You can access `distURL` on the newly exposed `assets` map: + +```diff +// my-integration.mjs +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) + } + } + } +} +``` diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4469a8fb6623..15b7b9ea222f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -219,11 +219,11 @@ jobs: with: repository: withastro/docs path: smoke/docs - # For a commit event on the `next` branch (`ref_name`), use the `5.0.0-beta` branch. - # For a pull_request event merging into the `next` branch (`base_ref`), use the `5.0.0-beta` branch. + # For a commit event on the `next` branch (`ref_name`), use the `v6` branch. + # For a pull_request event merging into the `next` branch (`base_ref`), use the `v6` branch. # NOTE: For a pull_request event, the `ref_name` is something like `/merge` than the branch name. # NOTE: Perhaps docs repo should use a consistent `next` branch in the future. - ref: ${{ (github.ref_name == 'next' || github.base_ref == 'next') && '5.0.0-beta' || 'main' }} + ref: ${{ (github.ref_name == 'next' || github.base_ref == 'next') && 'v6' || 'main' }} - name: Install dependencies run: pnpm install --no-frozen-lockfile diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 2096723706e5..5307091e3558 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -23,7 +23,6 @@ import { AstroError, AstroErrorData } from '../errors/index.js'; import type { Logger } from '../logger/core.js'; import { levels, timerMessage } from '../logger/core.js'; import { createRoutesList } from '../routing/index.js'; -import { getServerIslandRouteData } from '../server-islands/endpoint.js'; import { clearContentLayerCache } from '../sync/index.js'; import { ensureProcessNodeEnv } from '../util.js'; import { collectPagesData } from './page-data.js'; @@ -242,8 +241,7 @@ class AstroBuilder { pages: pageNames, routes: Object.values(allPages) .flat() - .map((pageData) => pageData.route) - .concat(hasServerIslands ? getServerIslandRouteData(this.settings.config) : []), + .map((pageData) => pageData.route), logger: this.logger, }); diff --git a/packages/astro/src/core/build/internal.ts b/packages/astro/src/core/build/internal.ts index 5f303596b620..cb6e349f64fe 100644 --- a/packages/astro/src/core/build/internal.ts +++ b/packages/astro/src/core/build/internal.ts @@ -1,5 +1,5 @@ import type { Rollup } from 'vite'; -import type { RouteData, SSRResult } from '../../types/public/internal.js'; +import type { SSRResult } from '../../types/public/internal.js'; import { prependForwardSlash, removeFileExtension } from '../path.js'; import { viteID } from '../util.js'; import { makePageDataKey } from './plugins/util.js'; @@ -89,7 +89,6 @@ export interface BuildInternals { // The SSR manifest entry chunk. manifestEntryChunk?: Rollup.OutputChunk; manifestFileName?: string; - entryPoints: Map; componentMetadata: SSRResult['componentMetadata']; middlewareEntryPoint: URL | undefined; astroActionsEntryPoint: URL | undefined; @@ -121,7 +120,6 @@ export function createBuildInternals(): BuildInternals { discoveredScripts: new Set(), staticFiles: new Set(), componentMetadata: new Map(), - entryPoints: new Map(), prerenderOnlyChunks: [], astroActionsEntryPoint: undefined, middlewareEntryPoint: undefined, diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts index 8d2b146a7d65..b45e484c07ab 100644 --- a/packages/astro/src/core/build/plugins/plugin-manifest.ts +++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts @@ -140,7 +140,6 @@ export function pluginManifest( config: options.settings.config, manifest, logger: options.logger, - entryPoints: internals.entryPoints, middlewareEntryPoint: shouldPassMiddlewareEntryPoint ? internals.middlewareEntryPoint : undefined, diff --git a/packages/astro/src/core/server-islands/endpoint.ts b/packages/astro/src/core/server-islands/endpoint.ts index 6a640ee41df7..994455c7c431 100644 --- a/packages/astro/src/core/server-islands/endpoint.ts +++ b/packages/astro/src/core/server-islands/endpoint.ts @@ -17,7 +17,7 @@ export const SERVER_ISLAND_BASE_PREFIX = '_server-islands'; type ConfigFields = Pick; -export function getServerIslandRouteData(config: ConfigFields) { +function getServerIslandRouteData(config: ConfigFields) { const segments = [ [{ content: '_server-islands', dynamic: false, spread: false }], [{ content: 'name', dynamic: true, spread: false }], diff --git a/packages/astro/src/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts index 6d954cb843dc..777d4092c4d3 100644 --- a/packages/astro/src/integrations/hooks.ts +++ b/packages/astro/src/integrations/hooks.ts @@ -30,7 +30,6 @@ import type { BaseIntegrationHooks, HookParameters, IntegrationResolvedRoute, - IntegrationRouteData, RouteOptions, RouteToHeaders, } from '../types/public/integrations.js'; @@ -548,7 +547,6 @@ type RunHookBuildSsr = { config: AstroConfig; manifest: SerializedSSRManifest; logger: Logger; - entryPoints: Map; middlewareEntryPoint: URL | undefined; }; @@ -556,13 +554,8 @@ export async function runHookBuildSsr({ config, manifest, logger, - entryPoints, middlewareEntryPoint, }: RunHookBuildSsr) { - const entryPointsMap = new Map(); - for (const [key, value] of entryPoints) { - entryPointsMap.set(toIntegrationRouteData(key), value); - } for (const integration of config.integrations) { await runHookInternal({ integration, @@ -570,7 +563,6 @@ export async function runHookBuildSsr({ logger, params: () => ({ manifest, - entryPoints: entryPointsMap, middlewareEntryPoint, }), }); @@ -602,7 +594,6 @@ export async function runHookBuildGenerated({ type RunHookBuildDone = { settings: AstroSettings; pages: string[]; - // TODO: remove in Astro 6 routes: RouteData[]; logger: Logger; }; @@ -610,7 +601,6 @@ type RunHookBuildDone = { export async function runHookBuildDone({ settings, pages, routes, logger }: RunHookBuildDone) { const dir = getClientOutputDirectory(settings); await fsMod.promises.mkdir(dir, { recursive: true }); - const integrationRoutes = routes.map(toIntegrationRouteData); for (const integration of settings.config.integrations) { await runHookInternal({ @@ -620,7 +610,6 @@ export async function runHookBuildDone({ settings, pages, routes, logger }: RunH params: () => ({ pages: pages.map((p) => ({ pathname: p })), dir, - routes: integrationRoutes, assets: new Map( routes.filter((r) => r.distURL !== undefined).map((r) => [r.route, r.distURL!]), ), @@ -701,20 +690,3 @@ export function toIntegrationResolvedRoute(route: RouteData): IntegrationResolve : undefined, }; } - -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 da08c0bd913f..0c07b3e89257 100644 --- a/packages/astro/src/types/public/integrations.ts +++ b/packages/astro/src/types/public/integrations.ts @@ -211,12 +211,6 @@ export interface BaseIntegrationHooks { 'astro:server:done': (options: { logger: AstroIntegrationLogger }) => void | Promise; 'astro:build:ssr': (options: { manifest: SerializedSSRManifest; - /** - * This maps a {@link RouteData} to an {@link URL}, this URL represents - * the physical file you should import. - */ - // TODO: Change in Astro 6.0 - entryPoints: Map; /** * File path of the emitted middleware */ @@ -239,8 +233,6 @@ 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; logger: AstroIntegrationLogger; }) => void | Promise; @@ -263,20 +255,6 @@ export interface AstroIntegration { } & Partial>; } -/** - * 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' | 'origin' -> & { - /** - * {@link RouteData.redirectRoute} - */ - redirectRoute?: IntegrationRouteData; -}; - export type RouteToHeaders = Map; export type HeaderPayload = { diff --git a/packages/astro/test/middleware.test.js b/packages/astro/test/middleware.test.js index 5d0322ea2ccd..2d8c39909789 100644 --- a/packages/astro/test/middleware.test.js +++ b/packages/astro/test/middleware.test.js @@ -349,8 +349,8 @@ describe('Middleware API in PROD mode, SSR', () => { edgeMiddleware: true, }, }, - setMiddlewareEntryPoint(entryPointsOrMiddleware) { - middlewarePath = entryPointsOrMiddleware; + setMiddlewareEntryPoint(middlewareEntryPoint) { + middlewarePath = middlewareEntryPoint; }, }), }); 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 ebfa75d0e90e..671de39c56b2 100644 --- a/packages/astro/test/static-build-page-dist-url.test.js +++ b/packages/astro/test/static-build-page-dist-url.test.js @@ -3,8 +3,8 @@ import { before, describe, it } from 'node:test'; import { loadFixture } from './test-utils.js'; describe('Static build: pages routes have distURL', () => { - /** @type {RouteData[]} */ - let checkRoutes; + /** @type {Map} */ + let assets; before(async () => { /** @type {import('./test-utils').Fixture} */ const fixture = await loadFixture({ @@ -13,8 +13,8 @@ describe('Static build: pages routes have distURL', () => { { name: '@astrojs/distURL', hooks: { - 'astro:build:done': ({ routes }) => { - checkRoutes = routes.filter((p) => p.type === 'page'); + 'astro:build:done': (params) => { + assets = params.assets; }, }, }, @@ -23,11 +23,11 @@ describe('Static build: pages routes have distURL', () => { await fixture.build(); }); it('Pages routes have distURL', async () => { - assert.equal(checkRoutes.length > 0, true, 'Pages not found: build end hook not being called'); - checkRoutes.forEach((p) => { - p.distURL.forEach((distURL) => { - assert.equal(distURL instanceof URL, true, `${p.pathname} doesn't include distURL`); - }); - }); + assert.equal(assets.size > 0, true, 'Pages not found: build end hook not being called'); + for (const [p, distURL] of assets.entries()) { + for (const url of distURL) { + assert.equal(url instanceof URL, true, `${p.pathname} doesn't include distURL`); + } + } }); }); diff --git a/packages/astro/test/test-adapter.js b/packages/astro/test/test-adapter.js index e872ef35213c..0dc4cb55bf6b 100644 --- a/packages/astro/test/test-adapter.js +++ b/packages/astro/test/test-adapter.js @@ -3,7 +3,6 @@ import { viteID } from '../dist/core/util.js'; /** * @typedef {import('../src/types/public/integrations.js').AstroAdapter} AstroAdapter * @typedef {import('../src/types/public/integrations.js').AstroIntegration} AstroIntegration - * @typedef {import('../src/types/public/integrations.js').HookParameters<"astro:build:ssr">['entryPoints']} EntryPoints * @typedef {import('../src/types/public/integrations.js').HookParameters<"astro:build:ssr">['middlewareEntryPoint']} MiddlewareEntryPoint * @typedef {import('../src/types/public/integrations.js').HookParameters<"astro:build:done">['routes']} Routes */ @@ -13,9 +12,7 @@ import { viteID } from '../dist/core/util.js'; * @param {{ * provideAddress?: boolean; * extendAdapter?: AstroAdapter; - * setEntryPoints?: (entryPoints: EntryPoints) => void; * setMiddlewareEntryPoint?: (middlewareEntryPoint: MiddlewareEntryPoint) => void; - * setRoutes?: (routes: Routes) => void; * env: Record; * }} param0 * @returns {AstroIntegration} @@ -24,9 +21,7 @@ export default function ({ provideAddress = true, staticHeaders = false, extendAdapter, - setEntryPoints, setMiddlewareEntryPoint, - setRoutes, setManifest, setRouteToHeaders, env, @@ -126,10 +121,7 @@ export default function ({ ...extendAdapter, }); }, - 'astro:build:ssr': ({ entryPoints, middlewareEntryPoint, manifest }) => { - if (setEntryPoints) { - setEntryPoints(entryPoints); - } + 'astro:build:ssr': ({ middlewareEntryPoint, manifest }) => { if (setMiddlewareEntryPoint) { setMiddlewareEntryPoint(middlewareEntryPoint); } @@ -137,11 +129,6 @@ export default function ({ setManifest(manifest); } }, - 'astro:build:done': ({ routes }) => { - if (setRoutes) { - setRoutes(routes); - } - }, 'astro:build:generated': ({ experimentalRouteToHeaders }) => { if (setRouteToHeaders) { setRouteToHeaders(experimentalRouteToHeaders); diff --git a/packages/integrations/sitemap/src/index.ts b/packages/integrations/sitemap/src/index.ts index e7b87ddc9b97..c59c7f6eb24b 100644 --- a/packages/integrations/sitemap/src/index.ts +++ b/packages/integrations/sitemap/src/index.ts @@ -1,6 +1,6 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; -import type { AstroConfig, AstroIntegration } from 'astro'; +import type { AstroConfig, AstroIntegration, IntegrationResolvedRoute } from 'astro'; import type { EnumChangefreq, LinkItem as LinkItemBase, SitemapItemLoose } from 'sitemap'; import { ZodError } from 'zod'; @@ -76,17 +76,22 @@ const isStatusCodePage = (locales: string[]) => { }; }; const createPlugin = (options?: SitemapOptions): AstroIntegration => { + let _routes: Array let config: AstroConfig; return { name: PKG_NAME, hooks: { + 'astro:routes:resolved': ({ routes }) => { + _routes = routes; + }, + 'astro:config:done': async ({ config: cfg }) => { config = cfg; }, - 'astro:build:done': async ({ dir, routes, pages, logger }) => { + 'astro:build:done': async ({ dir, pages, logger }) => { try { if (!config.site) { logger.warn( @@ -112,7 +117,7 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => { return new URL(fullPath, finalSiteUrl).href; }); - const routeUrls = routes.reduce((urls, r) => { + const routeUrls = _routes.reduce((urls, r) => { // Only expose pages, not endpoints or redirects if (r.type !== 'page') return urls; @@ -120,7 +125,7 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => { * Dynamic URLs have entries with `undefined` pathnames */ if (r.pathname) { - if (shouldIgnoreStatus(r.pathname ?? r.route)) return urls; + if (shouldIgnoreStatus(r.pathname ?? r.pattern)) return urls; // `finalSiteUrl` may end with a trailing slash // or not because of base paths. diff --git a/packages/integrations/vercel/src/index.ts b/packages/integrations/vercel/src/index.ts index 9234e051aaf6..617eca0674f0 100644 --- a/packages/integrations/vercel/src/index.ts +++ b/packages/integrations/vercel/src/index.ts @@ -1,5 +1,4 @@ import { cpSync, existsSync, mkdirSync, readFileSync } from 'node:fs'; -import { basename } from 'node:path'; import { pathToFileURL } from 'node:url'; import { emptyDir, removeDir, writeJson } from '@astrojs/internal-helpers/fs'; import { @@ -230,7 +229,6 @@ export default function vercelAdapter({ let _config: AstroConfig; let _buildTempFolder: URL; let _serverEntry: string; - let _entryPoints: Map, URL>; let _middlewareEntryPoint: URL | undefined; let _routeToHeaders: RouteToHeaders | undefined = undefined; // Extra files to be merged with `includeFiles` during build @@ -358,18 +356,7 @@ export default function vercelAdapter({ // Ensure to have `.vercel/output` empty. await emptyDir(new URL('./.vercel/output/', _config.root)); }, - 'astro:build:ssr': async ({ entryPoints, middlewareEntryPoint }) => { - _entryPoints = new Map( - Array.from(entryPoints) - .filter(([routeData]) => !routeData.prerender) - .map(([routeData, url]) => [ - { - entrypoint: routeData.component, - patternRegex: routeData.pattern, - }, - url, - ]), - ); + 'astro:build:ssr': async ({ middlewareEntryPoint }) => { _middlewareEntryPoint = middlewareEntryPoint; }, @@ -432,92 +419,69 @@ export default function vercelAdapter({ maxDuration, ); - // Multiple entrypoint support - if (_entryPoints.size) { - const getRouteFuncName = (route: Pick) => - route.entrypoint.replace('src/pages/', ''); - - const getFallbackFuncName = (entryFile: URL) => - basename(entryFile.toString()) - .replace('entry.', '') - .replace(/\.mjs$/, ''); - - for (const [route, entryFile] of _entryPoints) { - const func = route.entrypoint.startsWith('src/pages/') - ? getRouteFuncName(route) - : getFallbackFuncName(entryFile); + const entryFile = new URL(_serverEntry, _buildTempFolder); + if (isr) { + const isrConfig = typeof isr === 'object' ? isr : {}; + await builder.buildServerlessFolder(entryFile, NODE_PATH, _config.root); + if (isrConfig.exclude?.length) { + const expandedExclusions = isrConfig.exclude.reduce((acc, exclusion) => { + if (exclusion instanceof RegExp) { + return [ + ...acc, + ...routes + .filter((route) => exclusion.test(route.pattern)) + .map((route) => route.pattern), + ]; + } - await builder.buildServerlessFolder(entryFile, func, _config.root); + return [...acc, exclusion]; + }, []); - routeDefinitions.push({ - src: route.patternRegex.source, - dest: func, - }); + const dest = _middlewareEntryPoint ? MIDDLEWARE_PATH : NODE_PATH; + for (const route of expandedExclusions) { + // vercel interprets src as a regex pattern, so we need to escape it + routeDefinitions.push({ + src: escapeRegex(route), + dest, + }); + } } - } else { - const entryFile = new URL(_serverEntry, _buildTempFolder); - if (isr) { - const isrConfig = typeof isr === 'object' ? isr : {}; - await builder.buildServerlessFolder(entryFile, NODE_PATH, _config.root); - if (isrConfig.exclude?.length) { - const expandedExclusions = isrConfig.exclude.reduce((acc, exclusion) => { - if (exclusion instanceof RegExp) { - return [ - ...acc, - ...routes - .filter((route) => exclusion.test(route.pattern)) - .map((route) => route.pattern), - ]; - } - - return [...acc, exclusion]; - }, []); - - const dest = _middlewareEntryPoint ? MIDDLEWARE_PATH : NODE_PATH; - for (const route of expandedExclusions) { - // vercel interprets src as a regex pattern, so we need to escape it - routeDefinitions.push({ - src: escapeRegex(route), - dest, - }); + await builder.buildISRFolder(entryFile, '_isr', isrConfig, _config.root); + for (const route of routes) { + // Do not create _isr route entries for excluded routes + const excludeRouteFromIsr = isrConfig.exclude?.some((exclusion) => { + if (exclusion instanceof RegExp) { + return exclusion.test(route.pattern); } - } - await builder.buildISRFolder(entryFile, '_isr', isrConfig, _config.root); - for (const route of routes) { - // Do not create _isr route entries for excluded routes - const excludeRouteFromIsr = isrConfig.exclude?.some((exclusion) => { - if (exclusion instanceof RegExp) { - return exclusion.test(route.pattern); - } - - return exclusion === route.pattern; - }); - if (!excludeRouteFromIsr) { - const src = route.patternRegex.source; - const dest = - src.startsWith('^\\/_image') || src.startsWith('^\\/_server-islands') - ? NODE_PATH - : ISR_PATH; - if (!route.isPrerendered) - routeDefinitions.push({ - src, - dest, - }); - } - } - } else { - await builder.buildServerlessFolder(entryFile, NODE_PATH, _config.root); - const dest = _middlewareEntryPoint ? MIDDLEWARE_PATH : NODE_PATH; - for (const route of routes) { + return exclusion === route.pattern; + }); + + if (!excludeRouteFromIsr) { + const src = route.patternRegex.source; + const dest = + src.startsWith('^\\/_image') || src.startsWith('^\\/_server-islands') + ? NODE_PATH + : ISR_PATH; if (!route.isPrerendered) routeDefinitions.push({ - src: route.patternRegex.source, + src, dest, }); } } + } else { + await builder.buildServerlessFolder(entryFile, NODE_PATH, _config.root); + const dest = _middlewareEntryPoint ? MIDDLEWARE_PATH : NODE_PATH; + for (const route of routes) { + if (!route.isPrerendered) + routeDefinitions.push({ + src: route.patternRegex.source, + dest, + }); + } } + if (_middlewareEntryPoint) { await builder.buildMiddlewareFolder( _middlewareEntryPoint,