Skip to content

Commit

Permalink
feat: upgrade underscore-redirects (#501)
Browse files Browse the repository at this point in the history
  • Loading branch information
florian-lefebvre authored Jan 13, 2025
1 parent 83cedad commit 012b31d
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 123 deletions.
7 changes: 7 additions & 0 deletions .changeset/sweet-crews-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astrojs/cloudflare': patch
'@astrojs/netlify': patch
'@astrojs/vercel': patch
---

Refactor of the redirects logic
2 changes: 1 addition & 1 deletion packages/cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
39 changes: 8 additions & 31 deletions packages/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type {
AstroIntegration,
HookParameters,
IntegrationResolvedRoute,
IntegrationRouteData,
} from 'astro';
import type { PluginOption } from 'vite';

Expand Down Expand Up @@ -91,28 +90,6 @@ function setProcessEnv(config: AstroConfig, env: Record<string, unknown>) {
}
}

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'];
Expand Down Expand Up @@ -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()) {
Expand Down
2 changes: 1 addition & 1 deletion packages/netlify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
67 changes: 23 additions & 44 deletions packages/netlify/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import type {
AstroIntegrationLogger,
HookParameters,
IntegrationResolvedRoute,
IntegrationRouteData,
} from 'astro';
import { build } from 'esbuild';
import { copyDependenciesToFunction } from './lib/nft.js';
Expand All @@ -27,9 +26,6 @@ export interface NetlifyLocals {
};
}

const isStaticRedirect = (route: IntegrationRouteData) =>
route.type === 'redirect' && (route.redirect || route.redirectRoute);

type RemotePattern = AstroConfig['image']['remotePatterns'][number];

/**
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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') {
Expand Down
50 changes: 16 additions & 34 deletions packages/vercel/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import type {
AstroIntegrationLogger,
HookParameters,
IntegrationResolvedRoute,
IntegrationRouteData,
} from 'astro';
import glob from 'fast-glob';
import {
Expand Down Expand Up @@ -185,7 +184,7 @@ export default function vercelAdapter({
let _config: AstroConfig;
let _buildTempFolder: URL;
let _serverEntry: string;
let _entryPoints: Map<IntegrationRouteData, URL>;
let _entryPoints: Map<Pick<IntegrationResolvedRoute, 'entrypoint' | 'patternRegex'>, URL>;
let _middlewareEntryPoint: URL | undefined;
// Extra files to be merged with `includeFiles` during build
const extraFilesToInclude: URL[] = [];
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -356,23 +363,23 @@ export default function vercelAdapter({

// Multiple entrypoint support
if (_entryPoints.size) {
const getRouteFuncName = (route: IntegrationRouteData) =>
route.component.replace('src/pages/', '');
const getRouteFuncName = (route: Pick<IntegrationResolvedRoute, 'entrypoint'>) =>
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.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,
});
}
Expand Down Expand Up @@ -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' },
Expand Down Expand Up @@ -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;
Expand Down
13 changes: 8 additions & 5 deletions packages/vercel/src/lib/redirects.ts
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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) {
Expand All @@ -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),
Expand Down
14 changes: 7 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 012b31d

Please sign in to comment.