Skip to content
This repository was archived by the owner on Feb 10, 2025. It is now read-only.

Commit 012b31d

Browse files
feat: upgrade underscore-redirects (#501)
1 parent 83cedad commit 012b31d

File tree

8 files changed

+71
-123
lines changed

8 files changed

+71
-123
lines changed

.changeset/sweet-crews-dress.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@astrojs/cloudflare': patch
3+
'@astrojs/netlify': patch
4+
'@astrojs/vercel': patch
5+
---
6+
7+
Refactor of the redirects logic

packages/cloudflare/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
},
3030
"dependencies": {
3131
"@astrojs/internal-helpers": "0.4.2",
32-
"@astrojs/underscore-redirects": "^0.5.0",
32+
"@astrojs/underscore-redirects": "^0.6.0",
3333
"@cloudflare/workers-types": "^4.20241230.0",
3434
"esbuild": "^0.24.0",
3535
"estree-walker": "^3.0.3",

packages/cloudflare/src/index.ts

+8-31
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type {
33
AstroIntegration,
44
HookParameters,
55
IntegrationResolvedRoute,
6-
IntegrationRouteData,
76
} from 'astro';
87
import type { PluginOption } from 'vite';
98

@@ -91,28 +90,6 @@ function setProcessEnv(config: AstroConfig, env: Record<string, unknown>) {
9190
}
9291
}
9392

94-
function resolvedRouteToRouteData(
95-
assets: HookParameters<'astro:build:done'>['assets'],
96-
route: IntegrationResolvedRoute
97-
): IntegrationRouteData {
98-
return {
99-
pattern: route.patternRegex,
100-
component: route.entrypoint,
101-
prerender: route.isPrerendered,
102-
route: route.pattern,
103-
generate: route.generate,
104-
params: route.params,
105-
segments: route.segments,
106-
type: route.type,
107-
pathname: route.pathname,
108-
redirect: route.redirect,
109-
distURL: assets.get(route.pattern),
110-
redirectRoute: route.redirectRoute
111-
? resolvedRouteToRouteData(assets, route.redirectRoute)
112-
: undefined,
113-
};
114-
}
115-
11693
export default function createIntegration(args?: Options): AstroIntegration {
11794
let _config: AstroConfig;
11895
let finalBuildOutput: HookParameters<'astro:config:done'>['buildOutput'];
@@ -367,18 +344,18 @@ export default function createIntegration(args?: Options): AstroIntegration {
367344
);
368345
}
369346

370-
const redirectRoutes: [IntegrationRouteData, string][] = [];
371-
for (const route of _routes) {
372-
// TODO: Replace workaround after upstream @astrojs/underscore-redirects is changed, to support new IntegrationResolvedRoute type
373-
if (route.type === 'redirect')
374-
redirectRoutes.push([resolvedRouteToRouteData(assets, route), '']);
375-
}
376-
377347
const trueRedirects = createRedirectsFromAstroRoutes({
378348
config: _config,
379-
routeToDynamicTargetMap: new Map(Array.from(redirectRoutes)),
349+
routeToDynamicTargetMap: new Map(
350+
Array.from(
351+
_routes
352+
.filter((route) => route.type === 'redirect')
353+
.map((route) => [route, ''] as const)
354+
)
355+
),
380356
dir,
381357
buildOutput: finalBuildOutput,
358+
assets,
382359
});
383360

384361
if (!trueRedirects.empty()) {

packages/netlify/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
},
3333
"dependencies": {
3434
"@astrojs/internal-helpers": "0.4.2",
35-
"@astrojs/underscore-redirects": "^0.5.0",
35+
"@astrojs/underscore-redirects": "^0.6.0",
3636
"@netlify/functions": "^2.8.0",
3737
"@vercel/nft": "^0.29.0",
3838
"esbuild": "^0.24.0",

packages/netlify/src/index.ts

+23-44
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import type {
1111
AstroIntegrationLogger,
1212
HookParameters,
1313
IntegrationResolvedRoute,
14-
IntegrationRouteData,
1514
} from 'astro';
1615
import { build } from 'esbuild';
1716
import { copyDependenciesToFunction } from './lib/nft.js';
@@ -27,9 +26,6 @@ export interface NetlifyLocals {
2726
};
2827
}
2928

30-
const isStaticRedirect = (route: IntegrationRouteData) =>
31-
route.type === 'redirect' && (route.redirect || route.redirectRoute);
32-
3329
type RemotePattern = AstroConfig['image']['remotePatterns'][number];
3430

3531
/**
@@ -213,49 +209,36 @@ export default function netlifyIntegration(
213209
emptyDir(ssrBuildDir()),
214210
]);
215211

216-
function resolvedRouteToRouteData(
217-
assets: HookParameters<'astro:build:done'>['assets'],
218-
route: IntegrationResolvedRoute
219-
): IntegrationRouteData {
220-
return {
221-
pattern: route.patternRegex,
222-
component: route.entrypoint,
223-
prerender: route.isPrerendered,
224-
route: route.pattern,
225-
generate: route.generate,
226-
params: route.params,
227-
segments: route.segments,
228-
type: route.type,
229-
pathname: route.pathname,
230-
redirect: route.redirect,
231-
distURL: assets.get(route.pattern),
232-
redirectRoute: route.redirectRoute
233-
? resolvedRouteToRouteData(assets, route.redirectRoute)
234-
: undefined,
235-
};
236-
}
237-
238212
async function writeRedirects(
239-
routes: IntegrationRouteData[],
213+
routes: IntegrationResolvedRoute[],
240214
dir: URL,
241-
buildOutput: HookParameters<'astro:config:done'>['buildOutput']
215+
buildOutput: HookParameters<'astro:config:done'>['buildOutput'],
216+
assets: HookParameters<'astro:build:done'>['assets']
242217
) {
218+
// all other routes are handled by SSR
219+
const staticRedirects = routes.filter(
220+
(route) => route.type === 'redirect' && (route.redirect || route.redirectRoute)
221+
);
222+
223+
// this is needed to support redirects to dynamic routes
224+
// on static. not sure why this is needed, but it works.
225+
for (const { pattern, redirectRoute } of staticRedirects) {
226+
const distURL = assets.get(pattern);
227+
if (!distURL && redirectRoute) {
228+
const redirectDistURL = assets.get(redirectRoute.pattern);
229+
if (redirectDistURL) {
230+
assets.set(pattern, redirectDistURL);
231+
}
232+
}
233+
}
234+
243235
const fallback = finalBuildOutput === 'static' ? '/.netlify/static' : '/.netlify/functions/ssr';
244236
const redirects = createRedirectsFromAstroRoutes({
245237
config: _config,
246238
dir,
247-
routeToDynamicTargetMap: new Map(
248-
routes
249-
.filter(isStaticRedirect) // all other routes are handled by SSR
250-
.map((route) => {
251-
// this is needed to support redirects to dynamic routes
252-
// on static. not sure why this is needed, but it works.
253-
route.distURL ??= route.redirectRoute?.distURL;
254-
255-
return [route, fallback];
256-
})
257-
),
239+
routeToDynamicTargetMap: new Map(staticRedirects.map((route) => [route, fallback])),
258240
buildOutput,
241+
assets,
259242
});
260243

261244
if (!redirects.empty()) {
@@ -523,11 +506,7 @@ export default function netlifyIntegration(
523506
astroMiddlewareEntryPoint = middlewareEntryPoint;
524507
},
525508
'astro:build:done': async ({ assets, dir, logger }) => {
526-
await writeRedirects(
527-
routes.map((route) => resolvedRouteToRouteData(assets, route)),
528-
dir,
529-
finalBuildOutput
530-
);
509+
await writeRedirects(routes, dir, finalBuildOutput, assets);
531510
logger.info('Emitted _redirects');
532511

533512
if (finalBuildOutput !== 'static') {

packages/vercel/src/index.ts

+16-34
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import type {
99
AstroIntegrationLogger,
1010
HookParameters,
1111
IntegrationResolvedRoute,
12-
IntegrationRouteData,
1312
} from 'astro';
1413
import glob from 'fast-glob';
1514
import {
@@ -185,7 +184,7 @@ export default function vercelAdapter({
185184
let _config: AstroConfig;
186185
let _buildTempFolder: URL;
187186
let _serverEntry: string;
188-
let _entryPoints: Map<IntegrationRouteData, URL>;
187+
let _entryPoints: Map<Pick<IntegrationResolvedRoute, 'entrypoint' | 'patternRegex'>, URL>;
189188
let _middlewareEntryPoint: URL | undefined;
190189
// Extra files to be merged with `includeFiles` during build
191190
const extraFilesToInclude: URL[] = [];
@@ -291,11 +290,19 @@ export default function vercelAdapter({
291290
},
292291
'astro:build:ssr': async ({ entryPoints, middlewareEntryPoint }) => {
293292
_entryPoints = new Map(
294-
Array.from(entryPoints).filter(([routeData]) => !routeData.prerender)
293+
Array.from(entryPoints)
294+
.filter(([routeData]) => !routeData.prerender)
295+
.map(([routeData, url]) => [
296+
{
297+
entrypoint: routeData.component,
298+
patternRegex: routeData.pattern,
299+
},
300+
url,
301+
])
295302
);
296303
_middlewareEntryPoint = middlewareEntryPoint;
297304
},
298-
'astro:build:done': async ({ assets, logger }: HookParameters<'astro:build:done'>) => {
305+
'astro:build:done': async ({ logger }: HookParameters<'astro:build:done'>) => {
299306
const outDir = new URL('./.vercel/output/', _config.root);
300307
if (staticDir) {
301308
if (existsSync(staticDir)) {
@@ -356,23 +363,23 @@ export default function vercelAdapter({
356363

357364
// Multiple entrypoint support
358365
if (_entryPoints.size) {
359-
const getRouteFuncName = (route: IntegrationRouteData) =>
360-
route.component.replace('src/pages/', '');
366+
const getRouteFuncName = (route: Pick<IntegrationResolvedRoute, 'entrypoint'>) =>
367+
route.entrypoint.replace('src/pages/', '');
361368

362369
const getFallbackFuncName = (entryFile: URL) =>
363370
basename(entryFile.toString())
364371
.replace('entry.', '')
365372
.replace(/\.mjs$/, '');
366373

367374
for (const [route, entryFile] of _entryPoints) {
368-
const func = route.component.startsWith('src/pages/')
375+
const func = route.entrypoint.startsWith('src/pages/')
369376
? getRouteFuncName(route)
370377
: getFallbackFuncName(entryFile);
371378

372379
await builder.buildServerlessFolder(entryFile, func, _config.root);
373380

374381
routeDefinitions.push({
375-
src: route.pattern.source,
382+
src: route.patternRegex.source,
376383
dest: func,
377384
});
378385
}
@@ -417,10 +424,7 @@ export default function vercelAdapter({
417424
const fourOhFourRoute = routes.find((route) => route.pathname === '/404');
418425
const destination = new URL('./.vercel/output/config.json', _config.root);
419426
const finalRoutes = [
420-
...getRedirects(
421-
routes.map((route) => resolvedRouteToRouteData(assets, route)),
422-
_config
423-
),
427+
...getRedirects(routes, _config),
424428
{
425429
src: `^/${_config.build.assets}/(.*)$`,
426430
headers: { 'cache-control': 'public, max-age=31536000, immutable' },
@@ -488,28 +492,6 @@ export default function vercelAdapter({
488492
};
489493
}
490494

491-
function resolvedRouteToRouteData(
492-
assets: HookParameters<'astro:build:done'>['assets'],
493-
route: IntegrationResolvedRoute
494-
): IntegrationRouteData {
495-
return {
496-
pattern: route.patternRegex,
497-
component: route.entrypoint,
498-
prerender: route.isPrerendered,
499-
route: route.pattern,
500-
generate: route.generate,
501-
params: route.params,
502-
segments: route.segments,
503-
type: route.type,
504-
pathname: route.pathname,
505-
redirect: route.redirect,
506-
distURL: assets.get(route.pattern),
507-
redirectRoute: route.redirectRoute
508-
? resolvedRouteToRouteData(assets, route.redirectRoute)
509-
: undefined,
510-
};
511-
}
512-
513495
function isAcceptedPattern(pattern: any): pattern is RemotePattern {
514496
if (pattern == null) {
515497
return false;

packages/vercel/src/lib/redirects.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import nodePath from 'node:path';
22
import { appendForwardSlash, removeLeadingForwardSlash } from '@astrojs/internal-helpers/path';
3-
import type { AstroConfig, IntegrationRouteData, RoutePart } from 'astro';
3+
import type { AstroConfig, IntegrationResolvedRoute, RoutePart } from 'astro';
44

55
const pathJoin = nodePath.posix.join;
66

@@ -91,7 +91,7 @@ function getReplacePattern(segments: RoutePart[][]) {
9191
return result;
9292
}
9393

94-
function getRedirectLocation(route: IntegrationRouteData, config: AstroConfig): string {
94+
function getRedirectLocation(route: IntegrationResolvedRoute, config: AstroConfig): string {
9595
if (route.redirectRoute) {
9696
const pattern = getReplacePattern(route.redirectRoute.segments);
9797
const path = config.trailingSlash === 'always' ? appendForwardSlash(pattern) : pattern;
@@ -105,7 +105,7 @@ function getRedirectLocation(route: IntegrationRouteData, config: AstroConfig):
105105
}
106106
}
107107

108-
function getRedirectStatus(route: IntegrationRouteData): number {
108+
function getRedirectStatus(route: IntegrationResolvedRoute): number {
109109
if (typeof route.redirect === 'object') {
110110
return route.redirect.status;
111111
}
@@ -122,7 +122,10 @@ export function escapeRegex(content: string) {
122122
return `^/${getMatchPattern(segments)}$`;
123123
}
124124

125-
export function getRedirects(routes: IntegrationRouteData[], config: AstroConfig): VercelRoute[] {
125+
export function getRedirects(
126+
routes: IntegrationResolvedRoute[],
127+
config: AstroConfig
128+
): VercelRoute[] {
126129
const redirects: VercelRoute[] = [];
127130

128131
for (const route of routes) {
@@ -132,7 +135,7 @@ export function getRedirects(routes: IntegrationRouteData[], config: AstroConfig
132135
headers: { Location: getRedirectLocation(route, config) },
133136
status: getRedirectStatus(route),
134137
});
135-
} else if (route.type === 'page' && route.route !== '/') {
138+
} else if (route.type === 'page' && route.pattern !== '/') {
136139
if (config.trailingSlash === 'always') {
137140
redirects.push({
138141
src: config.base + getMatchPattern(route.segments),

pnpm-lock.yaml

+7-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)