Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add namedRegex and routeKeys to routes manifest #12250

Merged
merged 2 commits into from
Apr 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 38 additions & 16 deletions packages/next/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,15 @@ export default async function build(dir: string, conf = null): Promise<void> {
redirects: redirects.map(r => buildCustomRoute(r, 'redirect')),
rewrites: rewrites.map(r => buildCustomRoute(r, 'rewrite')),
headers: headers.map(r => buildCustomRoute(r, 'header')),
dynamicRoutes: getSortedRoutes(dynamicRoutes).map(page => ({
page,
regex: getRouteRegex(page).re.source,
})),
dynamicRoutes: getSortedRoutes(dynamicRoutes).map(page => {
const routeRegex = getRouteRegex(page)
return {
page,
regex: routeRegex.re.source,
namedRegex: routeRegex.namedRegex,
routeKeys: Object.keys(routeRegex.groups),
}
}),
}

await mkdir(distDir, { recursive: true })
Expand Down Expand Up @@ -633,20 +638,37 @@ export default async function build(dir: string, conf = null): Promise<void> {
`${pagePath}.json`
)

let dataRouteRegex: string
let routeKeys: string[] | undefined
let namedDataRouteRegex: string | undefined

if (isDynamicRoute(page)) {
const routeRegex = getRouteRegex(dataRoute.replace(/\.json$/, ''))

dataRouteRegex = routeRegex.re.source.replace(
/\(\?:\\\/\)\?\$$/,
'\\.json$'
)
namedDataRouteRegex = routeRegex.namedRegex!.replace(
/\(\?:\/\)\?\$$/,
'\\.json$'
)
routeKeys = Object.keys(routeRegex.groups)
} else {
dataRouteRegex = new RegExp(
`^${path.posix.join(
'/_next/data',
escapeStringRegexp(buildId),
`${pagePath}.json`
)}$`
).source
}

return {
page,
dataRouteRegex: isDynamicRoute(page)
? getRouteRegex(dataRoute.replace(/\.json$/, '')).re.source.replace(
/\(\?:\\\/\)\?\$$/,
'\\.json$'
)
: new RegExp(
`^${path.posix.join(
'/_next/data',
escapeStringRegexp(buildId),
`${pagePath}.json`
)}$`
).source,
routeKeys,
dataRouteRegex,
namedDataRouteRegex,
}
})

Expand Down
38 changes: 34 additions & 4 deletions packages/next/next-server/lib/router/utils/route-regex.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
// this isn't importing the escape-string-regex module
// to reduce bytes
function escapeRegex(str: string) {
return str.replace(/[|\\{}()[\]^$+*?.-]/g, '\\$&')
}

export function getRouteRegex(
normalizedRoute: string
): {
re: RegExp
namedRegex?: string
groups: { [groupName: string]: { pos: number; repeat: boolean } }
} {
// Escape all characters that could be considered RegEx
const escapedRoute = (normalizedRoute.replace(/\/$/, '') || '/').replace(
/[|\\{}()[\]^$+*?.-]/g,
'\\$&'
)
const escapedRoute = escapeRegex(normalizedRoute.replace(/\/$/, '') || '/')

const groups: { [groupName: string]: { pos: number; repeat: boolean } } = {}
let groupIndex = 1
Expand All @@ -28,8 +32,34 @@ export function getRouteRegex(
}
)

let namedParameterizedRoute: string | undefined

// dead code eliminate for browser since it's only needed
// while generating routes-manifest
if (typeof window === 'undefined') {
namedParameterizedRoute = escapedRoute.replace(
/\/\\\[([^/]+?)\\\](?=\/|$)/g,
(_, $1) => {
const isCatchAll = /^(\\\.){3}/.test($1)
const key = $1
// Un-escape key
.replace(/\\([|\\{}()[\]^$+*?.-])/g, '$1')
.replace(/^\.{3}/, '')

return isCatchAll
? `/(?<${escapeRegex(key)}>.+?)`
: `/(?<${escapeRegex(key)}>[^/]+?)`
}
)
}

return {
re: new RegExp('^' + parameterizedRoute + '(?:/)?$', 'i'),
groups,
...(namedParameterizedRoute
? {
namedRegex: `^${namedParameterizedRoute}(?:/)?$`,
}
: {}),
}
}
6 changes: 6 additions & 0 deletions test/integration/custom-routes/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -773,16 +773,22 @@ const runTests = (isDev = false) => {
],
dynamicRoutes: [
{
namedRegex: '^/another/(?<id>[^/]+?)(?:/)?$',
page: '/another/[id]',
regex: normalizeRegEx('^\\/another\\/([^\\/]+?)(?:\\/)?$'),
routeKeys: ['id'],
},
{
namedRegex: '^/api/dynamic/(?<slug>[^/]+?)(?:/)?$',
page: '/api/dynamic/[slug]',
regex: normalizeRegEx('^\\/api\\/dynamic\\/([^\\/]+?)(?:\\/)?$'),
routeKeys: ['slug'],
},
{
namedRegex: '^/blog/(?<post>[^/]+?)(?:/)?$',
page: '/blog/[post]',
regex: normalizeRegEx('^\\/blog\\/([^\\/]+?)(?:\\/)?$'),
routeKeys: ['post'],
},
],
})
Expand Down
8 changes: 4 additions & 4 deletions test/integration/dynamic-routing/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@ import Link from 'next/link'
const Page = () => (
<div>
<h3>My blog</h3>
<Link href="/[post]" as="/post-1">
<Link href="/[name]" as="/post-1">
<a id="view-post-1">View post 1</a>
</Link>
<br />
<Link href="/[post]/comments" as="/post-1/comments">
<Link href="/[name]/comments" as="/post-1/comments">
<a id="view-post-1-comments">View post 1 comments</a>
</Link>
<br />
<Link href="/[post]/[comment]" as="/post-1/comment-1">
<Link href="/[name]/[comment]" as="/post-1/comment-1">
<a id="view-post-1-comment-1">View comment 1 on post 1</a>
</Link>
<br />
<Link href="/blog/[post]/comment/[id]" as="/blog/321/comment/123">
<a id="view-nested-dynamic-cmnt">View comment 123 on blog post 321</a>
</Link>
<br />
<Link href="/[post]?fromHome=true" as="/post-1?fromHome=true">
<Link href="/[name]?fromHome=true" as="/post-1?fromHome=true">
<a id="view-post-1-with-query">View post 1 with query</a>
</Link>
<br />
Expand Down
32 changes: 32 additions & 0 deletions test/integration/dynamic-routing/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -532,78 +532,110 @@ function runTests(dev) {
redirects: [],
dataRoutes: [
{
namedDataRouteRegex: `^/_next/data/${escapeRegex(
buildId
)}/p1/p2/all\\-ssg/(?<rest>.+?)\\.json$`,
dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(
buildId
)}\\/p1\\/p2\\/all\\-ssg\\/(.+?)\\.json$`
),
page: '/p1/p2/all-ssg/[...rest]',
routeKeys: ['rest'],
},
{
namedDataRouteRegex: `^/_next/data/${escapeRegex(
buildId
)}/p1/p2/nested\\-all\\-ssg/(?<rest>.+?)\\.json$`,
dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(
buildId
)}\\/p1\\/p2\\/nested\\-all\\-ssg\\/(.+?)\\.json$`
),
page: '/p1/p2/nested-all-ssg/[...rest]',
routeKeys: ['rest'],
},
{
namedDataRouteRegex: `^/_next/data/${escapeRegex(
buildId
)}/p1/p2/predefined\\-ssg/(?<rest>.+?)\\.json$`,
dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(
buildId
)}\\/p1\\/p2\\/predefined\\-ssg\\/(.+?)\\.json$`
),
page: '/p1/p2/predefined-ssg/[...rest]',
routeKeys: ['rest'],
},
],
dynamicRoutes: [
{
namedRegex: `^/blog/(?<name>[^/]+?)/comment/(?<id>[^/]+?)(?:/)?$`,
page: '/blog/[name]/comment/[id]',
regex: normalizeRegEx(
'^\\/blog\\/([^\\/]+?)\\/comment\\/([^\\/]+?)(?:\\/)?$'
),
routeKeys: ['name', 'id'],
},
{
namedRegex: `^/on\\-mount/(?<post>[^/]+?)(?:/)?$`,
page: '/on-mount/[post]',
regex: normalizeRegEx('^\\/on\\-mount\\/([^\\/]+?)(?:\\/)?$'),
routeKeys: ['post'],
},
{
namedRegex: `^/p1/p2/all\\-ssg/(?<rest>.+?)(?:/)?$`,
page: '/p1/p2/all-ssg/[...rest]',
regex: normalizeRegEx('^\\/p1\\/p2\\/all\\-ssg\\/(.+?)(?:\\/)?$'),
routeKeys: ['rest'],
},
{
namedRegex: `^/p1/p2/all\\-ssr/(?<rest>.+?)(?:/)?$`,
page: '/p1/p2/all-ssr/[...rest]',
regex: normalizeRegEx('^\\/p1\\/p2\\/all\\-ssr\\/(.+?)(?:\\/)?$'),
routeKeys: ['rest'],
},
{
namedRegex: `^/p1/p2/nested\\-all\\-ssg/(?<rest>.+?)(?:/)?$`,
page: '/p1/p2/nested-all-ssg/[...rest]',
regex: normalizeRegEx(
'^\\/p1\\/p2\\/nested\\-all\\-ssg\\/(.+?)(?:\\/)?$'
),
routeKeys: ['rest'],
},
{
namedRegex: `^/p1/p2/predefined\\-ssg/(?<rest>.+?)(?:/)?$`,
page: '/p1/p2/predefined-ssg/[...rest]',
regex: normalizeRegEx(
'^\\/p1\\/p2\\/predefined\\-ssg\\/(.+?)(?:\\/)?$'
),
routeKeys: ['rest'],
},
{
namedRegex: `^/(?<name>[^/]+?)(?:/)?$`,
page: '/[name]',
regex: normalizeRegEx('^\\/([^\\/]+?)(?:\\/)?$'),
routeKeys: ['name'],
},
{
namedRegex: `^/(?<name>[^/]+?)/comments(?:/)?$`,
page: '/[name]/comments',
regex: normalizeRegEx('^\\/([^\\/]+?)\\/comments(?:\\/)?$'),
routeKeys: ['name'],
},
{
namedRegex: `^/(?<name>[^/]+?)/on\\-mount\\-redir(?:/)?$`,
page: '/[name]/on-mount-redir',
regex: normalizeRegEx(
'^\\/([^\\/]+?)\\/on\\-mount\\-redir(?:\\/)?$'
),
routeKeys: ['name'],
},
{
namedRegex: `^/(?<name>[^/]+?)/(?<comment>[^/]+?)(?:/)?$`,
page: '/[name]/[comment]',
regex: normalizeRegEx('^\\/([^\\/]+?)\\/([^\\/]+?)(?:\\/)?$'),
routeKeys: ['name', 'comment'],
},
],
})
Expand Down
16 changes: 16 additions & 0 deletions test/integration/getserversideprops/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,36 @@ const expectedManifestRoutes = () => [
page: '/blog',
},
{
namedDataRouteRegex: `^/_next/data/${escapeRegex(
buildId
)}/blog/(?<post>[^/]+?)\\.json$`,
dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(buildId)}\\/blog\\/([^\\/]+?)\\.json$`
),
page: '/blog/[post]',
routeKeys: ['post'],
},
{
namedDataRouteRegex: `^/_next/data/${escapeRegex(
buildId
)}/blog/(?<post>[^/]+?)/(?<comment>[^/]+?)\\.json$`,
dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(
buildId
)}\\/blog\\/([^\\/]+?)\\/([^\\/]+?)\\.json$`
),
page: '/blog/[post]/[comment]',
routeKeys: ['post', 'comment'],
},
{
namedDataRouteRegex: `^/_next/data/${escapeRegex(
buildId
)}/catchall/(?<path>.+?)\\.json$`,
dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(buildId)}\\/catchall\\/(.+?)\\.json$`
),
page: '/catchall/[...path]',
routeKeys: ['path'],
},
{
dataRouteRegex: normalizeRegEx(
Expand Down Expand Up @@ -103,12 +115,16 @@ const expectedManifestRoutes = () => [
page: '/something',
},
{
namedDataRouteRegex: `^/_next/data/${escapeRegex(
buildId
)}/user/(?<user>[^/]+?)/profile\\.json$`,
dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(
buildId
)}\\/user\\/([^\\/]+?)\\/profile\\.json$`
),
page: '/user/[user]/profile',
routeKeys: ['user'],
},
]

Expand Down
Loading