[remix] Support optional static path segments (#9607)

Fixes https://github.com/orgs/vercel/discussions/1707.
This commit is contained in:
Nathan Rajlich
2023-03-06 16:50:37 -08:00
committed by GitHub
parent 2e8423e24e
commit 57e8ec13cf
7 changed files with 235 additions and 9 deletions

View File

@@ -138,12 +138,24 @@ export function getPathFromRoute(
for (const currentRoute of getRouteIterator(route, routes)) { for (const currentRoute of getRouteIterator(route, routes)) {
if (!currentRoute.path) continue; if (!currentRoute.path) continue;
const currentRouteParts = currentRoute.path.split('/').reverse();
pathParts.push( for (const part of currentRouteParts) {
currentRoute.path.replace(/:(.+)\?/g, (_, name) => `(:${name})`) if (part.endsWith('?')) {
); if (part.startsWith(':')) {
// Optional path parameter
rePathParts.push(currentRoute.path); pathParts.push(`(${part.substring(0, part.length - 1)})`);
rePathParts.push(part);
} else {
// Optional static segment
const p = `(${part.substring(0, part.length - 1)})`;
pathParts.push(p);
rePathParts.push(`${p}?`);
}
} else {
pathParts.push(part);
rePathParts.push(part);
}
}
} }
const path = pathParts.reverse().join('/'); const path = pathParts.reverse().join('/');

View File

@@ -0,0 +1,3 @@
export default function () {
return <div>Optional static path</div>;
}

View File

@@ -12,7 +12,8 @@
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
}, },
"devDependencies": { "devDependencies": {
"@remix-run/dev": "^1.14.0" "@remix-run/dev": "^1.14.0",
"@remix-run/serve": "^1.14.0"
} }
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
@@ -2389,6 +2390,21 @@
} }
} }
}, },
"node_modules/@remix-run/express": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/@remix-run/express/-/express-1.14.0.tgz",
"integrity": "sha512-Kj7kVbj3f86TTbwE8kbSqHjLEzHJvs+cvYdlBEDrWsuXj1q5ciHs7Jjun6qSLrrOxCtEMqFXc6s530p67M1fiQ==",
"dev": true,
"dependencies": {
"@remix-run/node": "1.14.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"express": "^4.17.1"
}
},
"node_modules/@remix-run/node": { "node_modules/@remix-run/node": {
"version": "1.14.0", "version": "1.14.0",
"resolved": "https://registry.npmjs.org/@remix-run/node/-/node-1.14.0.tgz", "resolved": "https://registry.npmjs.org/@remix-run/node/-/node-1.14.0.tgz",
@@ -2441,6 +2457,24 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/@remix-run/serve": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/@remix-run/serve/-/serve-1.14.0.tgz",
"integrity": "sha512-lf/hEwogYgxqiNdRdEoAf6u2IJK8o0QmsC2EIqc6sHIHTL1X+kDw39m46ePAFY/wU/ymRUtD+dote+u3PCAczA==",
"dev": true,
"dependencies": {
"@remix-run/express": "1.14.0",
"compression": "^1.7.4",
"express": "^4.17.1",
"morgan": "^1.10.0"
},
"bin": {
"remix-serve": "dist/cli.js"
},
"engines": {
"node": ">=14"
}
},
"node_modules/@remix-run/server-runtime": { "node_modules/@remix-run/server-runtime": {
"version": "1.14.0", "version": "1.14.0",
"resolved": "https://registry.npmjs.org/@remix-run/server-runtime/-/server-runtime-1.14.0.tgz", "resolved": "https://registry.npmjs.org/@remix-run/server-runtime/-/server-runtime-1.14.0.tgz",
@@ -3056,6 +3090,24 @@
} }
] ]
}, },
"node_modules/basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"dev": true,
"dependencies": {
"safe-buffer": "5.1.2"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/basic-auth/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
"node_modules/big.js": { "node_modules/big.js": {
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@@ -3548,6 +3600,66 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/compressible": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
"integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
"dev": true,
"dependencies": {
"mime-db": ">= 1.43.0 < 2"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/compression": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
"integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
"dev": true,
"dependencies": {
"accepts": "~1.3.5",
"bytes": "3.0.0",
"compressible": "~2.0.16",
"debug": "2.6.9",
"on-headers": "~1.0.2",
"safe-buffer": "5.1.2",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/compression/node_modules/bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
"dev": true,
"engines": {
"node": ">= 0.8"
}
},
"node_modules/compression/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/compression/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
},
"node_modules/compression/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -6710,6 +6822,49 @@
"ufo": "^1.1.0" "ufo": "^1.1.0"
} }
}, },
"node_modules/morgan": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
"integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
"dev": true,
"dependencies": {
"basic-auth": "~2.0.1",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-finished": "~2.3.0",
"on-headers": "~1.0.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/morgan/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/morgan/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
},
"node_modules/morgan/node_modules/on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
"dev": true,
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/mri": { "node_modules/mri": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@@ -6856,6 +7011,15 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
"dev": true,
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": { "node_modules/once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",

View File

@@ -12,6 +12,7 @@
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
}, },
"devDependencies": { "devDependencies": {
"@remix-run/dev": "^1.14.0" "@remix-run/dev": "^1.14.0",
"@remix-run/serve": "^1.14.0"
} }
} }

View File

@@ -4,6 +4,19 @@
{ "path": "/foo", "mustContain": "Foo" }, { "path": "/foo", "mustContain": "Foo" },
{ "path": "/nested", "mustContain": "Nested Index" }, { "path": "/nested", "mustContain": "Nested Index" },
{ "path": "/fuzz/buzz", "mustContain": "Fuzz Buzz" }, { "path": "/fuzz/buzz", "mustContain": "Fuzz Buzz" },
{ "path": "/fuzz/buzz-2", "mustContain": "Fuzz Buzz 2" } { "path": "/fuzz/buzz-2", "mustContain": "Fuzz Buzz 2" },
{
"path": "/admin",
"mustContain": "Optional static path"
},
{
"path": "/admin/lol",
"mustContain": "Optional static path"
},
{
"path": "/admin/other",
"status": 404,
"mustContain": "Not Found"
}
] ]
} }

View File

@@ -104,6 +104,14 @@ describe('getPathFromRoute()', () => {
parentId: 'root', parentId: 'root',
file: 'routes/($lang)/$pid.tsx', file: 'routes/($lang)/$pid.tsx',
}, },
'routes/admin.(lol)': {
path: 'admin/lol?',
index: undefined,
caseSensitive: undefined,
id: 'routes/admin.(lol)',
parentId: 'root',
file: 'routes/admin.(lol).tsx',
},
}; };
it.each([ it.each([
@@ -156,6 +164,10 @@ describe('getPathFromRoute()', () => {
id: 'routes/($lang)/$pid', id: 'routes/($lang)/$pid',
expected: { path: '(:lang)/:pid', rePath: '/:lang?/:pid' }, expected: { path: '(:lang)/:pid', rePath: '/:lang?/:pid' },
}, },
{
id: 'routes/admin.(lol)',
expected: { path: 'admin/(lol)', rePath: '/admin/(lol)?' },
},
])('should return `$expected` for "$id" route', ({ id, expected }) => { ])('should return `$expected` for "$id" route', ({ id, expected }) => {
const route = routes[id]; const route = routes[id];
expect(getPathFromRoute(route, routes)).toMatchObject(expected); expect(getPathFromRoute(route, routes)).toMatchObject(expected);

View File

@@ -167,6 +167,27 @@ describe('getRegExpFromPath()', () => {
}, },
], ],
}, },
{
path: '/admin/(lol)?',
urls: [
{
url: '/admin',
expected: true,
},
{
url: '/admin/lol',
expected: true,
},
{
url: '/other',
expected: false,
},
{
url: '/admin/other',
expected: false,
},
],
},
])('with path "$path"', ({ path, urls }) => { ])('with path "$path"', ({ path, urls }) => {
const re = getRegExpFromPath(path) as RegExp; const re = getRegExpFromPath(path) as RegExp;