Skip to content

Commit

Permalink
fix(deps): adds support for path-to-regexp v8
Browse files Browse the repository at this point in the history
Adds support for new path-to-regexp params syntax.

BREAKING CHANGE: Catch all params like [...slug] must now be provided as array of strings e.g.
["first", "second"]. Passing them as a string like "first/second" is not supported anymore

fix #294
  • Loading branch information
svobik7 committed Nov 7, 2024
1 parent 1254eeb commit 5f21593
Show file tree
Hide file tree
Showing 16 changed files with 131 additions and 106 deletions.
4 changes: 2 additions & 2 deletions examples/basic-esm/src/server/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function getProductsHref(input: GetProductsHrefProps) {
const product = 'product' in input ? input.product : undefined

return router.getHref('/products/[[...slugs]]', {
slugs: product?.slug,
slugs: product?.slug ? [product.slug] : undefined,
locale: locale || getPageLocale(),
})
}
Expand All @@ -61,7 +61,7 @@ export function getBooksHref(locale: string) {

export function getBooksDetailHref(book: BookTranslation) {
return router.getHref('/books/[...slugs]', {
slugs: book?.slug,
slugs: [book.slug],
locale: book.locale || getPageLocale(),
})
}
17 changes: 6 additions & 11 deletions examples/basic-esm/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2428,13 +2428,8 @@ natural-compare@^1.4.0:
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==

"next-roots@link:../..":
version "3.11.3"
dependencies:
esbuild "^0.23.0"
esbuild-node-externals "^1.7.0"
node-watch "^0.7.4"
parse-typed-args "^0.2.0"
path-to-regexp "^6.2.1"
version "0.0.0"
uid ""

next@^14.2.5:
version "14.2.5"
Expand Down Expand Up @@ -2648,10 +2643,10 @@ path-parse@^1.0.7:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==

path-to-regexp@^6.2.1:
version "6.3.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4"
integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==
path-to-regexp@^8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4"
integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==

path-type@^4.0.0:
version "4.0.0"
Expand Down
4 changes: 2 additions & 2 deletions examples/basic/src/server/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function getProductsHref(input: GetProductsHrefProps) {
const product = 'product' in input ? input.product : undefined

return router.getHref('/products/[[...slugs]]', {
slugs: product?.slug,
slugs: product?.slug ? [product?.slug] : undefined,
locale: locale || getPageLocale(),
})
}
Expand All @@ -61,7 +61,7 @@ export function getBooksHref(locale: string) {

export function getBooksDetailHref(book: BookTranslation) {
return router.getHref('/books/[...slugs]', {
slugs: book?.slug,
slugs: [book?.slug],
locale: book.locale || getPageLocale(),
})
}
17 changes: 6 additions & 11 deletions examples/basic/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2409,13 +2409,8 @@ natural-compare@^1.4.0:
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==

"next-roots@link:../..":
version "3.11.0"
dependencies:
esbuild "^0.23.0"
esbuild-node-externals "^1.7.0"
node-watch "^0.7.4"
parse-typed-args "^0.2.0"
path-to-regexp "^6.2.1"
version "0.0.0"
uid ""

next@^14.2.5:
version "14.2.5"
Expand Down Expand Up @@ -2629,10 +2624,10 @@ path-parse@^1.0.7:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==

path-to-regexp@^6.2.1:
version "6.2.2"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz#324377a83e5049cbecadc5554d6a63a9a4866b36"
integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==
path-to-regexp@^8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4"
integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==

path-type@^4.0.0:
version "4.0.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import acceptLanguage from 'accept-language'
import { type Route, type Router, type RouterSchema } from 'next-roots'
import { headers } from 'next/headers'
import { pathToRegexp, type Key } from 'path-to-regexp'
import { pathToRegexp } from 'path-to-regexp'

export function findLocalizedHrefFactory(router: Router, schema: RouterSchema) {
return (url: string) => {
Expand Down Expand Up @@ -32,23 +32,26 @@ function getRouteMatchFactory(router: Router, schema: RouterSchema) {
return (url: string): RouteMatch => {
const pathWithoutLocale = removeLocalePrefix(url, schema.locales)

const matchedRoute = schema.locales.reduce((match, locale) => {
if (match) {
return match
}

const localizedPath = `/${locale}/${pathWithoutLocale}`
const route = router.getRouteFromHref(localizedPath)

if (!route) {
return undefined
}

return {
route,
params: extractNamedParams(route.href, localizedPath),
}
}, undefined as undefined | RouteMatch)
const matchedRoute = schema.locales.reduce(
(match, locale) => {
if (match) {
return match
}

const localizedPath = `/${locale}/${pathWithoutLocale}`
const route = router.getRouteFromHref(localizedPath)

if (!route) {
return undefined
}

return {
route,
params: extractNamedParams(route.href, localizedPath),
}
},
undefined as undefined | RouteMatch
)

return matchedRoute
}
Expand All @@ -58,9 +61,8 @@ function extractNamedParams(
pathPattern: string,
href: string
): Record<string, string> {
const keys: Key[] = []
const pattern = pathToRegexp(pathPattern, keys)
const match = pattern.exec(href)
const { keys = [], regexp } = pathToRegexp(pathPattern)
const match = regexp.exec(href)

if (!match) {
return {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function getProductsHref(input: GetProductsHrefProps) {
const product = 'product' in input ? input.product : undefined

return router.getHref('/products/[[...slugs]]', {
slugs: product?.slug,
slugs: product?.slug ? [product.slug] : undefined,
locale: locale || getPageLocale(),
})
}
Expand All @@ -61,7 +61,7 @@ export function getBooksHref(locale: string) {

export function getBooksDetailHref(book: BookTranslation) {
return router.getHref('/books/[...slugs]', {
slugs: book?.slug,
slugs: [book?.slug],
locale: book.locale || getPageLocale(),
})
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"esbuild-node-externals": "^1.7.0",
"node-watch": "^0.7.4",
"parse-typed-args": "^0.2.0",
"path-to-regexp": "^6.2.1"
"path-to-regexp": "^8.2.0"
},
"devDependencies": {
"@semantic-release/changelog": "^6.0.3",
Expand Down
20 changes: 14 additions & 6 deletions src/cli/templates/lib-declaration-tpl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,15 @@ describe('with dynamic routes', () => {
},
{
name: '/books/[...slug]',
href: '/books-cs/:slug+',
href: '/books-cs/*slug',
},
{
name: '/products/[[...slug]]',
href: '/products-cs/:slug*',
href: '/products-cs{/*slug}',
},
{
name: '/products/[[slug]]',
href: '/products-cs{/:slug}',
},
],
es: [
Expand All @@ -59,11 +63,15 @@ describe('with dynamic routes', () => {
},
{
name: '/books/[...slug]',
href: '/books-es/:slug+',
href: '/books-es/*slug',
},
{
name: '/products/[[...slug]]',
href: '/products-es/:slug*',
href: '/products-es{/*slug}',
},
{
name: '/products/[[slug]]',
href: '/products-es{/:slug}',
},
],
},
Expand All @@ -72,12 +80,12 @@ describe('with dynamic routes', () => {
const expectedOutput = `
export type RouteLocale = 'cs' | 'es';
export type RouteNameStatic = '/account' | '/(auth)/login' | '/books';
export type RouteNameDynamic = '/blog/articles/[articleId]' | '/blog/authors/[authorId]' | '/books/[...slug]' | '/products/[[...slug]]';
export type RouteNameDynamic = '/blog/articles/[articleId]' | '/blog/authors/[authorId]' | '/books/[...slug]' | '/products/[[...slug]]' | '/products/[[slug]]';
export type RouteName = RouteNameStatic | RouteNameDynamic;
export type Route = { name: RouteName; href: \`/\${string}\` };
export type RouteParamsStatic<T extends object = object> = T & { locale?: string };
export type RouteParamsDynamic<T extends RouteName> = T extends '/blog/articles/[articleId]' ? RouteParamsStatic<{articleId:string}> : T extends '/blog/authors/[authorId]' ? RouteParamsStatic<{authorId:string}> : T extends '/books/[...slug]' ? RouteParamsStatic<{slug:string}> : T extends '/products/[[...slug]]' ? RouteParamsStatic<{slug?:string}> : RouteParamsStatic;
export type RouteParamsDynamic<T extends RouteName> = T extends '/blog/articles/[articleId]' ? RouteParamsStatic<{articleId:string}> : T extends '/blog/authors/[authorId]' ? RouteParamsStatic<{authorId:string}> : T extends '/books/[...slug]' ? RouteParamsStatic<{slug:string[]}> : T extends '/products/[[...slug]]' ? RouteParamsStatic<{slug?:string[]}> : T extends '/products/[[slug]]' ? RouteParamsStatic<{slug?:string}> : RouteParamsStatic;
export type RouterSchema = { defaultLocale: string, locales: string[], routes: Record<RouteLocale, Route[]> };
Expand Down
33 changes: 24 additions & 9 deletions src/cli/templates/lib-declaration-tpl.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Key, pathToRegexp } from 'path-to-regexp'
import { pathToRegexp } from 'path-to-regexp'
import type { Route, RouterSchema } from '~/types'
import {
type CompileParams,
Expand Down Expand Up @@ -137,8 +137,17 @@ function isDynamicCatchAllRoute(route: Route) {
)
}

function isDynamicOptionalRoute(route: Route) {
return !!route.name.match(/\[\[\w+\]\]/g)
}

function isDynamicRoute(route: Route) {
return !!route.name.match(/\[\w+\]/g) || isDynamicCatchAllRoute(route)
return (
!!route.name.match(/\[\w+\]/g) ||
isDynamicOptionalRoute(route) ||
isDynamicCatchAllRoute(route) ||
isDynamicOptionalCatchAllRoute(route)
)
}

function getDefaultRoutes(schema: RouterSchema) {
Expand All @@ -160,13 +169,19 @@ function getDynamicRouteParams(schema: RouterSchema) {

return dynamicRoutes.reduce(
(acc: string, item: Route, index: number, array: Route[]) => {
const params: Key[] = []
pathToRegexp(item.href, params)

const nameSuffix = isDynamicOptionalCatchAllRoute(item) ? '?' : ''

acc += `T extends '${item.name}' ? RouteParamsStatic<{${params.map(
(p) => `${p.name}${nameSuffix}:string`
const { keys = [] } = pathToRegexp(item.href)
const nameSuffix =
isDynamicOptionalRoute(item) || isDynamicOptionalCatchAllRoute(item)
? '?'
: ''

const paramType =
isDynamicCatchAllRoute(item) || isDynamicOptionalCatchAllRoute(item)
? 'string[]'
: 'string'

acc += `T extends '${item.name}' ? RouteParamsStatic<{${keys.map(
(p) => `${p.name}${nameSuffix}:${paramType}`
)}}> : `

if (index === array.length - 1) {
Expand Down
2 changes: 1 addition & 1 deletion src/cli/templates/page-tpl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ import ProductsPageOrigin from '../../../../../roots/products/[[...slugs]]/page'
import { Router, compileHref } from 'next-roots'
export default function ProductsPage({ params, ...otherProps }) {
Router.setPageHref(compileHref('/cs/produkty/:slugs*', params))
Router.setPageHref(compileHref('/cs/produkty{/*slugs}', params))
{/* @ts-ignore */}
return <ProductsPageOrigin {...otherProps} params={params} pageHref={Router.getPageHref()} />
}
Expand Down
12 changes: 10 additions & 2 deletions src/cli/utils/getRoute.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ const inputRewrites: Rewrite[] = [
originPath: '/books/[...slugs]/page.ts',
localizedPath: '/en/books/[...slugs]/page.ts',
},
{
originPath: '/books/[[slug]]/page.ts',
localizedPath: '/en/books/[[slug]]/page.ts',
},
{ originPath: '/page.js', localizedPath: '/en/page.js' },
{ originPath: '/page.js', localizedPath: '/(en)/page.js' },
]
Expand Down Expand Up @@ -79,11 +83,15 @@ const expectedSchema: Array<Route | undefined> = [
},
{
name: '/products/[[...slugs]]',
href: '/en/products/:slugs*',
href: '/en/products{/*slugs}',
},
{
name: '/books/[...slugs]',
href: '/en/books/:slugs+',
href: '/en/books/*slugs',
},
{
name: '/books/[[slug]]',
href: '/en/books{/:slug}',
},
{
name: '/',
Expand Down
8 changes: 4 additions & 4 deletions src/cli/utils/getRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ function removeInterceptedSegments(input: string) {

function formatDynamicSegments(input: string) {
return input
.replace(/\/\[\[\.\.\.(\w+)\]\]/g, '/:$1*') // [[...slug]] -> :slug*
.replace(/\/\[\[(\w+)\]\]/g, '/:$1*') // [[slug]] -> :slug*
.replace(/\/\[\.\.\.(\w+)\]/g, '/:$1+') // [...slug] -> :slug+
.replace(/\/\[(\w+)\]/g, '/:$1') // [slug] -> :slug
.replace(/\/\[\[\.\.\.(\w+)\]\]/g, '{/*$1}') // /[[...slug]] -> {/*slug}
.replace(/\/\[\[(\w+)\]\]/g, '{/:$1}') // /[[slug]] -> {/:slug}
.replace(/\/\[\.\.\.(\w+)\]/g, '/*$1') // /[...slug] -> /*slug
.replace(/\/\[(\w+)\]/g, '/:$1') // /[slug] -> /:slug
}

function getRouteName({ originPath }: Rewrite) {
Expand Down
Loading

0 comments on commit 5f21593

Please sign in to comment.