From 012b31d98ce87c1199eb38b7aba2a28b7c1cf8cc Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Mon, 13 Jan 2025 16:23:26 +0100 Subject: [PATCH] feat: upgrade underscore-redirects (#501) --- .changeset/sweet-crews-dress.md | 7 +++ packages/cloudflare/package.json | 2 +- packages/cloudflare/src/index.ts | 39 ++++------------ packages/netlify/package.json | 2 +- packages/netlify/src/index.ts | 67 ++++++++++------------------ packages/vercel/src/index.ts | 50 +++++++-------------- packages/vercel/src/lib/redirects.ts | 13 +++--- pnpm-lock.yaml | 14 +++--- 8 files changed, 71 insertions(+), 123 deletions(-) create mode 100644 .changeset/sweet-crews-dress.md diff --git a/.changeset/sweet-crews-dress.md b/.changeset/sweet-crews-dress.md new file mode 100644 index 000000000..a5d21983f --- /dev/null +++ b/.changeset/sweet-crews-dress.md @@ -0,0 +1,7 @@ +--- +'@astrojs/cloudflare': patch +'@astrojs/netlify': patch +'@astrojs/vercel': patch +--- + +Refactor of the redirects logic diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index 04eb856bf..121a7024d 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -29,7 +29,7 @@ }, "dependencies": { "@astrojs/internal-helpers": "0.4.2", - "@astrojs/underscore-redirects": "^0.5.0", + "@astrojs/underscore-redirects": "^0.6.0", "@cloudflare/workers-types": "^4.20241230.0", "esbuild": "^0.24.0", "estree-walker": "^3.0.3", diff --git a/packages/cloudflare/src/index.ts b/packages/cloudflare/src/index.ts index fdef2eb28..a21d45fe7 100644 --- a/packages/cloudflare/src/index.ts +++ b/packages/cloudflare/src/index.ts @@ -3,7 +3,6 @@ import type { AstroIntegration, HookParameters, IntegrationResolvedRoute, - IntegrationRouteData, } from 'astro'; import type { PluginOption } from 'vite'; @@ -91,28 +90,6 @@ function setProcessEnv(config: AstroConfig, env: Record) { } } -function resolvedRouteToRouteData( - assets: HookParameters<'astro:build:done'>['assets'], - route: IntegrationResolvedRoute -): IntegrationRouteData { - return { - pattern: route.patternRegex, - component: route.entrypoint, - prerender: route.isPrerendered, - route: route.pattern, - generate: route.generate, - params: route.params, - segments: route.segments, - type: route.type, - pathname: route.pathname, - redirect: route.redirect, - distURL: assets.get(route.pattern), - redirectRoute: route.redirectRoute - ? resolvedRouteToRouteData(assets, route.redirectRoute) - : undefined, - }; -} - export default function createIntegration(args?: Options): AstroIntegration { let _config: AstroConfig; let finalBuildOutput: HookParameters<'astro:config:done'>['buildOutput']; @@ -367,18 +344,18 @@ export default function createIntegration(args?: Options): AstroIntegration { ); } - const redirectRoutes: [IntegrationRouteData, string][] = []; - for (const route of _routes) { - // TODO: Replace workaround after upstream @astrojs/underscore-redirects is changed, to support new IntegrationResolvedRoute type - if (route.type === 'redirect') - redirectRoutes.push([resolvedRouteToRouteData(assets, route), '']); - } - const trueRedirects = createRedirectsFromAstroRoutes({ config: _config, - routeToDynamicTargetMap: new Map(Array.from(redirectRoutes)), + routeToDynamicTargetMap: new Map( + Array.from( + _routes + .filter((route) => route.type === 'redirect') + .map((route) => [route, ''] as const) + ) + ), dir, buildOutput: finalBuildOutput, + assets, }); if (!trueRedirects.empty()) { diff --git a/packages/netlify/package.json b/packages/netlify/package.json index fea59c23c..e43daaea2 100644 --- a/packages/netlify/package.json +++ b/packages/netlify/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@astrojs/internal-helpers": "0.4.2", - "@astrojs/underscore-redirects": "^0.5.0", + "@astrojs/underscore-redirects": "^0.6.0", "@netlify/functions": "^2.8.0", "@vercel/nft": "^0.29.0", "esbuild": "^0.24.0", diff --git a/packages/netlify/src/index.ts b/packages/netlify/src/index.ts index 288ff207e..17d86e41f 100644 --- a/packages/netlify/src/index.ts +++ b/packages/netlify/src/index.ts @@ -11,7 +11,6 @@ import type { AstroIntegrationLogger, HookParameters, IntegrationResolvedRoute, - IntegrationRouteData, } from 'astro'; import { build } from 'esbuild'; import { copyDependenciesToFunction } from './lib/nft.js'; @@ -27,9 +26,6 @@ export interface NetlifyLocals { }; } -const isStaticRedirect = (route: IntegrationRouteData) => - route.type === 'redirect' && (route.redirect || route.redirectRoute); - type RemotePattern = AstroConfig['image']['remotePatterns'][number]; /** @@ -213,49 +209,36 @@ export default function netlifyIntegration( emptyDir(ssrBuildDir()), ]); - function resolvedRouteToRouteData( - assets: HookParameters<'astro:build:done'>['assets'], - route: IntegrationResolvedRoute - ): IntegrationRouteData { - return { - pattern: route.patternRegex, - component: route.entrypoint, - prerender: route.isPrerendered, - route: route.pattern, - generate: route.generate, - params: route.params, - segments: route.segments, - type: route.type, - pathname: route.pathname, - redirect: route.redirect, - distURL: assets.get(route.pattern), - redirectRoute: route.redirectRoute - ? resolvedRouteToRouteData(assets, route.redirectRoute) - : undefined, - }; - } - async function writeRedirects( - routes: IntegrationRouteData[], + routes: IntegrationResolvedRoute[], dir: URL, - buildOutput: HookParameters<'astro:config:done'>['buildOutput'] + buildOutput: HookParameters<'astro:config:done'>['buildOutput'], + assets: HookParameters<'astro:build:done'>['assets'] ) { + // all other routes are handled by SSR + const staticRedirects = routes.filter( + (route) => route.type === 'redirect' && (route.redirect || route.redirectRoute) + ); + + // this is needed to support redirects to dynamic routes + // on static. not sure why this is needed, but it works. + for (const { pattern, redirectRoute } of staticRedirects) { + const distURL = assets.get(pattern); + if (!distURL && redirectRoute) { + const redirectDistURL = assets.get(redirectRoute.pattern); + if (redirectDistURL) { + assets.set(pattern, redirectDistURL); + } + } + } + const fallback = finalBuildOutput === 'static' ? '/.netlify/static' : '/.netlify/functions/ssr'; const redirects = createRedirectsFromAstroRoutes({ config: _config, dir, - routeToDynamicTargetMap: new Map( - routes - .filter(isStaticRedirect) // all other routes are handled by SSR - .map((route) => { - // this is needed to support redirects to dynamic routes - // on static. not sure why this is needed, but it works. - route.distURL ??= route.redirectRoute?.distURL; - - return [route, fallback]; - }) - ), + routeToDynamicTargetMap: new Map(staticRedirects.map((route) => [route, fallback])), buildOutput, + assets, }); if (!redirects.empty()) { @@ -523,11 +506,7 @@ export default function netlifyIntegration( astroMiddlewareEntryPoint = middlewareEntryPoint; }, 'astro:build:done': async ({ assets, dir, logger }) => { - await writeRedirects( - routes.map((route) => resolvedRouteToRouteData(assets, route)), - dir, - finalBuildOutput - ); + await writeRedirects(routes, dir, finalBuildOutput, assets); logger.info('Emitted _redirects'); if (finalBuildOutput !== 'static') { diff --git a/packages/vercel/src/index.ts b/packages/vercel/src/index.ts index 536cd84f4..2360de5e1 100644 --- a/packages/vercel/src/index.ts +++ b/packages/vercel/src/index.ts @@ -9,7 +9,6 @@ import type { AstroIntegrationLogger, HookParameters, IntegrationResolvedRoute, - IntegrationRouteData, } from 'astro'; import glob from 'fast-glob'; import { @@ -185,7 +184,7 @@ export default function vercelAdapter({ let _config: AstroConfig; let _buildTempFolder: URL; let _serverEntry: string; - let _entryPoints: Map; + let _entryPoints: Map, URL>; let _middlewareEntryPoint: URL | undefined; // Extra files to be merged with `includeFiles` during build const extraFilesToInclude: URL[] = []; @@ -291,11 +290,19 @@ export default function vercelAdapter({ }, 'astro:build:ssr': async ({ entryPoints, middlewareEntryPoint }) => { _entryPoints = new Map( - Array.from(entryPoints).filter(([routeData]) => !routeData.prerender) + Array.from(entryPoints) + .filter(([routeData]) => !routeData.prerender) + .map(([routeData, url]) => [ + { + entrypoint: routeData.component, + patternRegex: routeData.pattern, + }, + url, + ]) ); _middlewareEntryPoint = middlewareEntryPoint; }, - 'astro:build:done': async ({ assets, logger }: HookParameters<'astro:build:done'>) => { + 'astro:build:done': async ({ logger }: HookParameters<'astro:build:done'>) => { const outDir = new URL('./.vercel/output/', _config.root); if (staticDir) { if (existsSync(staticDir)) { @@ -356,8 +363,8 @@ export default function vercelAdapter({ // Multiple entrypoint support if (_entryPoints.size) { - const getRouteFuncName = (route: IntegrationRouteData) => - route.component.replace('src/pages/', ''); + const getRouteFuncName = (route: Pick) => + route.entrypoint.replace('src/pages/', ''); const getFallbackFuncName = (entryFile: URL) => basename(entryFile.toString()) @@ -365,14 +372,14 @@ export default function vercelAdapter({ .replace(/\.mjs$/, ''); for (const [route, entryFile] of _entryPoints) { - const func = route.component.startsWith('src/pages/') + const func = route.entrypoint.startsWith('src/pages/') ? getRouteFuncName(route) : getFallbackFuncName(entryFile); await builder.buildServerlessFolder(entryFile, func, _config.root); routeDefinitions.push({ - src: route.pattern.source, + src: route.patternRegex.source, dest: func, }); } @@ -417,10 +424,7 @@ export default function vercelAdapter({ const fourOhFourRoute = routes.find((route) => route.pathname === '/404'); const destination = new URL('./.vercel/output/config.json', _config.root); const finalRoutes = [ - ...getRedirects( - routes.map((route) => resolvedRouteToRouteData(assets, route)), - _config - ), + ...getRedirects(routes, _config), { src: `^/${_config.build.assets}/(.*)$`, headers: { 'cache-control': 'public, max-age=31536000, immutable' }, @@ -488,28 +492,6 @@ export default function vercelAdapter({ }; } -function resolvedRouteToRouteData( - assets: HookParameters<'astro:build:done'>['assets'], - route: IntegrationResolvedRoute -): IntegrationRouteData { - return { - pattern: route.patternRegex, - component: route.entrypoint, - prerender: route.isPrerendered, - route: route.pattern, - generate: route.generate, - params: route.params, - segments: route.segments, - type: route.type, - pathname: route.pathname, - redirect: route.redirect, - distURL: assets.get(route.pattern), - redirectRoute: route.redirectRoute - ? resolvedRouteToRouteData(assets, route.redirectRoute) - : undefined, - }; -} - function isAcceptedPattern(pattern: any): pattern is RemotePattern { if (pattern == null) { return false; diff --git a/packages/vercel/src/lib/redirects.ts b/packages/vercel/src/lib/redirects.ts index 7ef4d5045..a9a3a81b2 100644 --- a/packages/vercel/src/lib/redirects.ts +++ b/packages/vercel/src/lib/redirects.ts @@ -1,6 +1,6 @@ import nodePath from 'node:path'; import { appendForwardSlash, removeLeadingForwardSlash } from '@astrojs/internal-helpers/path'; -import type { AstroConfig, IntegrationRouteData, RoutePart } from 'astro'; +import type { AstroConfig, IntegrationResolvedRoute, RoutePart } from 'astro'; const pathJoin = nodePath.posix.join; @@ -91,7 +91,7 @@ function getReplacePattern(segments: RoutePart[][]) { return result; } -function getRedirectLocation(route: IntegrationRouteData, config: AstroConfig): string { +function getRedirectLocation(route: IntegrationResolvedRoute, config: AstroConfig): string { if (route.redirectRoute) { const pattern = getReplacePattern(route.redirectRoute.segments); const path = config.trailingSlash === 'always' ? appendForwardSlash(pattern) : pattern; @@ -105,7 +105,7 @@ function getRedirectLocation(route: IntegrationRouteData, config: AstroConfig): } } -function getRedirectStatus(route: IntegrationRouteData): number { +function getRedirectStatus(route: IntegrationResolvedRoute): number { if (typeof route.redirect === 'object') { return route.redirect.status; } @@ -122,7 +122,10 @@ export function escapeRegex(content: string) { return `^/${getMatchPattern(segments)}$`; } -export function getRedirects(routes: IntegrationRouteData[], config: AstroConfig): VercelRoute[] { +export function getRedirects( + routes: IntegrationResolvedRoute[], + config: AstroConfig +): VercelRoute[] { const redirects: VercelRoute[] = []; for (const route of routes) { @@ -132,7 +135,7 @@ export function getRedirects(routes: IntegrationRouteData[], config: AstroConfig headers: { Location: getRedirectLocation(route, config) }, status: getRedirectStatus(route), }); - } else if (route.type === 'page' && route.route !== '/') { + } else if (route.type === 'page' && route.pattern !== '/') { if (config.trailingSlash === 'always') { redirects.push({ src: config.base + getMatchPattern(route.segments), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9cff9066a..24dd5baff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,8 +66,8 @@ importers: specifier: 0.4.2 version: 0.4.2 '@astrojs/underscore-redirects': - specifier: ^0.5.0 - version: 0.5.1 + specifier: ^0.6.0 + version: 0.6.0 '@cloudflare/workers-types': specifier: ^4.20241230.0 version: 4.20241230.0 @@ -249,8 +249,8 @@ importers: specifier: 0.4.2 version: 0.4.2 '@astrojs/underscore-redirects': - specifier: ^0.5.0 - version: 0.5.1 + specifier: ^0.6.0 + version: 0.6.0 '@netlify/functions': specifier: ^2.8.0 version: 2.8.2 @@ -779,8 +779,8 @@ packages: resolution: {integrity: sha512-wxhSKRfKugLwLlr4OFfcqovk+LIFtKwLyGPqMsv+9/ibqqnW3Gv7tBhtKEb0gAyUAC4G9BTVQeQahqnQAhd6IQ==} engines: {node: ^18.17.1 || ^20.3.0 || >=22.0.0} - '@astrojs/underscore-redirects@0.5.1': - resolution: {integrity: sha512-tmHt0ikpqSoLXXLG+8eS/orzjjPhggMVbe+sZ2OHuMzQYMfZgvKRM/DDo9GH5HAuXvEDt0d1QfgT9mXs60tYPQ==} + '@astrojs/underscore-redirects@0.6.0': + resolution: {integrity: sha512-dnJgFpaM955IFNIkEEmMaaIdWXRdeZs1ID6mlGBqdjh6NiCXfKmOdq7L4fd9Dd/tr18fkLrOJ25IUJSxRAEhjQ==} '@astrojs/vue@5.0.4': resolution: {integrity: sha512-ONfhcRqLVZ+gIeiZnvNX5ntdGUtPfPmlyDyKP6NyVMkJCZoAElfDYHs0FI/ti1H2Dm8GxgJrEwOoMapXywaGsw==} @@ -5024,7 +5024,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/underscore-redirects@0.5.1': {} + '@astrojs/underscore-redirects@0.6.0': {} '@astrojs/vue@5.0.4(@types/node@22.10.5)(astro@5.1.3(@types/node@22.10.5)(rollup@4.30.1)(typescript@5.7.2)(yaml@2.6.1))(rollup@4.30.1)(vue@3.5.13(typescript@5.7.2))(yaml@2.6.1)': dependencies: