mirror of
https://github.com/withastro/astro.git
synced 2025-01-22 18:41:55 -05:00
Remove dependency on path-to-regexp (#12001)
Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com>
This commit is contained in:
parent
8d4eb95086
commit
9be3e1bba7
5 changed files with 197 additions and 53 deletions
5
.changeset/shiny-worms-rest.md
Normal file
5
.changeset/shiny-worms-rest.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Remove dependency on path-to-regexp
|
|
@ -168,7 +168,6 @@
|
|||
"ora": "^8.1.0",
|
||||
"p-limit": "^6.1.0",
|
||||
"p-queue": "^8.0.1",
|
||||
"path-to-regexp": "6.2.2",
|
||||
"preferred-pm": "^4.0.0",
|
||||
"prompts": "^2.4.2",
|
||||
"rehype": "^13.0.1",
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
import type { AstroConfig, RoutePart } from '../../../@types/astro.js';
|
||||
|
||||
import { compile } from 'path-to-regexp';
|
||||
|
||||
/**
|
||||
* Sanitizes the parameters object by normalizing string values and replacing certain characters with their URL-encoded equivalents.
|
||||
* @param {Record<string, string | number | undefined>} params - The parameters object to be sanitized.
|
||||
* @returns {Record<string, string | number | undefined>} The sanitized parameters object.
|
||||
* @param {Record<string, string | number>} params - The parameters object to be sanitized.
|
||||
* @returns {Record<string, string | number>} The sanitized parameters object.
|
||||
*/
|
||||
function sanitizeParams(
|
||||
params: Record<string, string | number | undefined>,
|
||||
): Record<string, string | number | undefined> {
|
||||
function sanitizeParams(params: Record<string, string | number>): Record<string, string | number> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(params).map(([key, value]) => {
|
||||
if (typeof value === 'string') {
|
||||
|
@ -20,49 +16,49 @@ function sanitizeParams(
|
|||
);
|
||||
}
|
||||
|
||||
function getParameter(part: RoutePart, params: Record<string, string | number>): string | number {
|
||||
if (part.spread) {
|
||||
return params[part.content.slice(3)] || '';
|
||||
}
|
||||
|
||||
if (part.dynamic) {
|
||||
if (!params[part.content]) {
|
||||
throw new TypeError(`Missing parameter: ${part.content}`);
|
||||
}
|
||||
|
||||
return params[part.content];
|
||||
}
|
||||
|
||||
return part.content
|
||||
.normalize()
|
||||
.replace(/\?/g, '%3F')
|
||||
.replace(/#/g, '%23')
|
||||
.replace(/%5B/g, '[')
|
||||
.replace(/%5D/g, ']');
|
||||
}
|
||||
|
||||
function getSegment(segment: RoutePart[], params: Record<string, string | number>): string {
|
||||
const segmentPath = segment.map((part) => getParameter(part, params)).join('');
|
||||
|
||||
return segmentPath ? '/' + segmentPath : '';
|
||||
}
|
||||
|
||||
export function getRouteGenerator(
|
||||
segments: RoutePart[][],
|
||||
addTrailingSlash: AstroConfig['trailingSlash'],
|
||||
) {
|
||||
const template = segments
|
||||
.map((segment) => {
|
||||
return (
|
||||
'/' +
|
||||
segment
|
||||
.map((part) => {
|
||||
if (part.spread) {
|
||||
return `:${part.content.slice(3)}(.*)?`;
|
||||
} else if (part.dynamic) {
|
||||
return `:${part.content}`;
|
||||
} else {
|
||||
return part.content
|
||||
.normalize()
|
||||
.replace(/\?/g, '%3F')
|
||||
.replace(/#/g, '%23')
|
||||
.replace(/%5B/g, '[')
|
||||
.replace(/%5D/g, ']')
|
||||
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
})
|
||||
.join('')
|
||||
);
|
||||
})
|
||||
.join('');
|
||||
|
||||
// Unless trailingSlash config is set to 'always', don't automatically append it.
|
||||
let trailing: '/' | '' = '';
|
||||
if (addTrailingSlash === 'always' && segments.length) {
|
||||
trailing = '/';
|
||||
}
|
||||
const toPath = compile(template + trailing);
|
||||
return (params: Record<string, string | number | undefined>): string => {
|
||||
return (params: Record<string, string | number>): string => {
|
||||
const sanitizedParams = sanitizeParams(params);
|
||||
const path = toPath(sanitizedParams);
|
||||
|
||||
// When generating an index from a rest parameter route, `path-to-regexp` will return an
|
||||
// empty string instead "/". This causes an inconsistency with static indexes that may result
|
||||
// in the incorrect routes being rendered.
|
||||
// To fix this, we return "/" when the path is empty.
|
||||
// Unless trailingSlash config is set to 'always', don't automatically append it.
|
||||
let trailing: '/' | '' = '';
|
||||
if (addTrailingSlash === 'always' && segments.length) {
|
||||
trailing = '/';
|
||||
}
|
||||
|
||||
const path =
|
||||
segments.map((segment) => getSegment(segment, sanitizedParams)).join('') + trailing;
|
||||
|
||||
return path || '/';
|
||||
};
|
||||
}
|
||||
|
|
152
packages/astro/test/units/routing/generator.test.js
Normal file
152
packages/astro/test/units/routing/generator.test.js
Normal file
|
@ -0,0 +1,152 @@
|
|||
import * as assert from 'node:assert/strict';
|
||||
import { describe, it } from 'node:test';
|
||||
|
||||
import { getRouteGenerator } from '../../../dist/core/routing/manifest/generator.js';
|
||||
|
||||
describe('routing - generator', () => {
|
||||
[
|
||||
{
|
||||
routeData: [],
|
||||
trailingSlash: 'never',
|
||||
params: {},
|
||||
path: '/',
|
||||
},
|
||||
{
|
||||
routeData: [],
|
||||
trailingSlash: 'always',
|
||||
params: {},
|
||||
path: '/',
|
||||
},
|
||||
{
|
||||
routeData: [[{ spread: false, content: 'test', dynamic: false }]],
|
||||
trailingSlash: 'never',
|
||||
params: {},
|
||||
path: '/test',
|
||||
},
|
||||
{
|
||||
routeData: [[{ spread: false, content: 'test', dynamic: false }]],
|
||||
trailingSlash: 'always',
|
||||
params: {},
|
||||
path: '/test/',
|
||||
},
|
||||
{
|
||||
routeData: [[{ spread: false, content: 'test', dynamic: false }]],
|
||||
trailingSlash: 'always',
|
||||
params: { foo: 'bar' },
|
||||
path: '/test/',
|
||||
},
|
||||
{
|
||||
routeData: [[{ spread: false, content: 'foo', dynamic: true }]],
|
||||
trailingSlash: 'always',
|
||||
params: { foo: 'bar' },
|
||||
path: '/bar/',
|
||||
},
|
||||
{
|
||||
routeData: [[{ spread: false, content: 'foo', dynamic: true }]],
|
||||
trailingSlash: 'never',
|
||||
params: { foo: 'bar' },
|
||||
path: '/bar',
|
||||
},
|
||||
{
|
||||
routeData: [[{ spread: true, content: '...foo', dynamic: true }]],
|
||||
trailingSlash: 'never',
|
||||
params: {},
|
||||
path: '/',
|
||||
},
|
||||
{
|
||||
routeData: [
|
||||
[
|
||||
{ spread: true, content: '...foo', dynamic: true },
|
||||
{ spread: false, content: '-', dynamic: false },
|
||||
{ spread: true, content: '...bar', dynamic: true },
|
||||
],
|
||||
],
|
||||
trailingSlash: 'never',
|
||||
params: { foo: 'one', bar: 'two' },
|
||||
path: '/one-two',
|
||||
},
|
||||
{
|
||||
routeData: [
|
||||
[
|
||||
{ spread: true, content: '...foo', dynamic: true },
|
||||
{ spread: false, content: '-', dynamic: false },
|
||||
{ spread: true, content: '...bar', dynamic: true },
|
||||
],
|
||||
],
|
||||
trailingSlash: 'never',
|
||||
params: {},
|
||||
path: '/-',
|
||||
},
|
||||
{
|
||||
routeData: [
|
||||
[{ spread: true, content: '...foo', dynamic: true }],
|
||||
[{ spread: true, content: '...bar', dynamic: true }],
|
||||
],
|
||||
trailingSlash: 'never',
|
||||
params: { foo: 'one' },
|
||||
path: '/one',
|
||||
},
|
||||
{
|
||||
routeData: [
|
||||
[{ spread: false, content: 'fix', dynamic: false }],
|
||||
[{ spread: true, content: '...foo', dynamic: true }],
|
||||
[{ spread: true, content: '...bar', dynamic: true }],
|
||||
],
|
||||
trailingSlash: 'never',
|
||||
params: { foo: 'one' },
|
||||
path: '/fix/one',
|
||||
},
|
||||
{
|
||||
routeData: [
|
||||
[{ spread: false, content: 'fix', dynamic: false }],
|
||||
[{ spread: true, content: '...foo', dynamic: true }],
|
||||
[{ spread: true, content: '...bar', dynamic: true }],
|
||||
],
|
||||
trailingSlash: 'always',
|
||||
params: { foo: 'one' },
|
||||
path: '/fix/one/',
|
||||
},
|
||||
{
|
||||
routeData: [
|
||||
[{ spread: false, content: 'fix', dynamic: false }],
|
||||
[{ spread: true, content: '...foo', dynamic: true }],
|
||||
[{ spread: true, content: '...bar', dynamic: true }],
|
||||
],
|
||||
trailingSlash: 'never',
|
||||
params: { foo: 'one', bar: 'two' },
|
||||
path: '/fix/one/two',
|
||||
},
|
||||
{
|
||||
routeData: [
|
||||
[{ spread: false, content: 'fix', dynamic: false }],
|
||||
[{ spread: true, content: '...foo', dynamic: true }],
|
||||
[{ spread: true, content: '...bar', dynamic: true }],
|
||||
],
|
||||
trailingSlash: 'never',
|
||||
params: { foo: 'one&two' },
|
||||
path: '/fix/one&two',
|
||||
},
|
||||
{
|
||||
routeData: [
|
||||
[{ spread: false, content: 'fix', dynamic: false }],
|
||||
[{ spread: false, content: 'page', dynamic: true }],
|
||||
],
|
||||
trailingSlash: 'never',
|
||||
params: { page: 1 },
|
||||
path: '/fix/1',
|
||||
},
|
||||
].forEach(({ routeData, trailingSlash, params, path }) => {
|
||||
it(`generates ${path}`, () => {
|
||||
const generator = getRouteGenerator(routeData, trailingSlash);
|
||||
assert.equal(generator(params), path);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error when a dynamic parameter is missing', () => {
|
||||
const generator = getRouteGenerator(
|
||||
[[{ spread: false, content: 'foo', dynamic: true }]],
|
||||
'never',
|
||||
);
|
||||
assert.throws(() => generator({}), TypeError);
|
||||
});
|
||||
});
|
|
@ -693,9 +693,6 @@ importers:
|
|||
p-queue:
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1
|
||||
path-to-regexp:
|
||||
specifier: 6.2.2
|
||||
version: 6.2.2
|
||||
preferred-pm:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
|
@ -9552,9 +9549,6 @@ packages:
|
|||
resolution: {integrity: sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
path-to-regexp@6.2.2:
|
||||
resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==}
|
||||
|
||||
path-type@4.0.0:
|
||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -15588,8 +15582,6 @@ snapshots:
|
|||
lru-cache: 10.2.0
|
||||
minipass: 7.1.2
|
||||
|
||||
path-to-regexp@6.2.2: {}
|
||||
|
||||
path-type@4.0.0: {}
|
||||
|
||||
path-type@5.0.0: {}
|
||||
|
|
Loading…
Reference in a new issue