Skip to content
Open
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
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@
},
"dependencies": {
"path-to-regexp": "^6.2.0",
"querystring": "^0.2.1",
"typescript": "^4.7.3",
"url": "^0.11.0",
"yamljs": "^0.3.0"
},
Expand Down Expand Up @@ -67,6 +65,7 @@
"react-dom": "^18.2.0",
"rimraf": "^3.0.2",
"ts-jest": "^27.0.3",
"typescript": "^5.3.2",
"webpack": "^5.65.0",
"yarn-or-npm": "^3.0.1"
},
Expand Down
14 changes: 2 additions & 12 deletions src/react/enhanceNextRouter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { PrefetchOptions } from 'next/dist/shared/lib/router/router'
import { formatUrl } from 'next/dist/shared/lib/router/utils/format-url'
import { NextRouter, SingletonRouter } from 'next/router'
import { stringify as stringifyQuery } from 'querystring'

import { getNtrData } from '../shared/ntrData'
import { ntrMessagePrefix } from '../shared/withNtrPrefix'
Expand Down Expand Up @@ -50,17 +50,7 @@ const enhancePrefetch =
logWithTrace('router.prefetch', { inputUrl, asPath, options, parsedInputUrl, locale })
}

return router.prefetch(
parsedInputUrl
? (parsedInputUrl.pathname || '/') +
(parsedInputUrl.query &&
`?${
typeof parsedInputUrl.query === 'string' ? parsedInputUrl.query : stringifyQuery(parsedInputUrl.query)
}`)
: inputUrl,
asPath,
options,
)
return router.prefetch(parsedInputUrl ? formatUrl(parsedInputUrl) : inputUrl, asPath, options)
}

export const enhanceNextRouter = <R extends NextRouter | SingletonRouter>(router: R) => {
Expand Down
2 changes: 1 addition & 1 deletion src/react/fileUrlToFileUrlObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const getFileUrlObject = ({
pathParts: remainingPathParts,
})

const pathname = `${routeBranch.name ? `/${routeBranch.name}` : ''}${nextPathname}`
const pathname = (routeBranch.name ? `/${routeBranch.name}` : '') + nextPathname
const query =
isExactMatch || !dynamicPathPartKey
? nextQuery
Expand Down
29 changes: 23 additions & 6 deletions src/react/parseUrl.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
import { parse as parseQuery } from 'querystring'
import { parse, UrlObject, UrlWithParsedQuery } from 'url'
import { ParsedUrl, parseUrl as nextParseUrl } from 'next/dist/shared/lib/router/utils/parse-url'
import { searchParamsToUrlQuery, urlQueryToSearchParams } from 'next/dist/shared/lib/router/utils/querystring'
import type { ParsedUrlQuery } from 'querystring'
import type { UrlObject } from 'url'

/** Parse an url and its query to object */
export const parseUrl = (url: UrlObject | URL | string) =>
typeof url === 'string' || url instanceof URL
? parse(url.toString(), true)
: ({ ...url, query: typeof url.query === 'string' ? parseQuery(url.query) : url.query } as UrlWithParsedQuery)
export const parseUrl = (url: UrlObject | URL | string): ParsedUrl =>
typeof url === 'string'
? nextParseUrl(url)
: {
hash: url.hash || '',
hostname: url.hostname,
href: url.href || '',
pathname: url.pathname || '/',
port: url.port?.toString(),
protocol: url.protocol,
query: searchParamsToUrlQuery(
url instanceof URL
? url.searchParams
: typeof url.query === 'object' && url.query
? urlQueryToSearchParams(url.query as ParsedUrlQuery)
: new URLSearchParams(url.query || undefined),
),
search: url.search || '',
}
29 changes: 17 additions & 12 deletions src/react/translateUrl.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { normalizePathTrailingSlash } from 'next/dist/client/normalize-trailing-slash'
import { formatUrl } from 'next/dist/shared/lib/router/utils/format-url'
import { ParsedUrl } from 'next/dist/shared/lib/router/utils/parse-url'
import type { Key } from 'path-to-regexp'
import { compile as compilePath, parse as parsePath } from 'path-to-regexp'
import type { ParsedUrlQuery } from 'querystring'
import { format as formatUrl, parse, UrlObject } from 'url'
import { UrlObject } from 'url'

import { getLocalePathFromPaths } from '../plugin/getPathFromPaths'
import { getNtrData } from '../shared/ntrData'
Expand Down Expand Up @@ -80,15 +82,15 @@ const translatePathParts = ({
// Single dynamic route segment value => store it in the query
currentQuery = {
...currentQuery,
[childRouteBranch.name.replace(/\[|\]|\./g, '')]: pathPart,
[childRouteBranch.name.replace(/[[\].]/g, '')]: pathPart,
}
} else {
childRouteBranch = candidates.find((candidate) => getSpreadFilepathPartKey(candidate.name))
if (childRouteBranch) {
// Catch all route => store it in the query, then return the current data.
currentQuery = {
...currentQuery,
[childRouteBranch.name.replace(/\[|\]|\./g, '')]: pathParts,
[childRouteBranch.name.replace(/[[\].]/g, '')]: pathParts,
}
return {
translatedPathParts: [getLocalePathFromPaths({ paths: childRouteBranch.paths, locale })],
Expand Down Expand Up @@ -144,11 +146,14 @@ export function translatePath<U extends string | UrlObject, F extends 'string' |
export function translatePath(url: Url, locale: string, { format }: Options = {}): Url {
const { routesTree } = getNtrData()
const returnFormat = format || typeof url
const urlObject = parseUrl(url)
const { pathname, query, hash } = urlObject
const parsedUrl = parseUrl(url)
const { pathname, query, hash } = parsedUrl

if (!pathname || !locale) {
return returnFormat === 'object' ? url : formatUrl(url)
if (returnFormat === 'object') {
return typeof url === 'object' ? url : parseUrl(url)
}
return typeof url === 'object' ? formatUrl(url) : url
}

const pathParts = removeLangPrefix(pathname, true)
Expand All @@ -172,10 +177,10 @@ export function translatePath(url: Url, locale: string, { format }: Options = {}
{},
)

const translatedPathname = `${routesTree.paths[locale] ? `/${routesTree.paths[locale]}` : ''}/${compiledPath}`
const translatedPathname = (routesTree.paths[locale] ? `/${routesTree.paths[locale]}` : '') + `/${compiledPath}`

const translatedUrlObject = {
...urlObject,
...parsedUrl,
hash,
pathname: translatedPathname,
query: remainingQuery,
Expand Down Expand Up @@ -203,9 +208,9 @@ export const translateUrl: TTranslateUrl = ((url, locale, options) => {
const { defaultLocale } = getNtrData()

// Handle external urls
const parsedUrl: UrlObject = typeof url === 'string' ? parse(url) : url
if (parsedUrl.host) {
if (typeof window === 'undefined' || parsedUrl.host !== parse(window.location.href).host) {
const parsedUrl: ParsedUrl | UrlObject = typeof url === 'string' ? parseUrl(url) : url
if (parsedUrl.hostname) {
if (typeof window === 'undefined' || parsedUrl.hostname !== parseUrl(window.location.href).hostname) {
return url
}
}
Expand All @@ -218,5 +223,5 @@ export const translateUrl: TTranslateUrl = ((url, locale, options) => {

const prefix = locale === defaultLocale || options?.withoutLangPrefix ? '' : `/${locale}`

return normalizePathTrailingSlash(prefix + translatedPath)
return normalizePathTrailingSlash(prefix + (translatedPath as string))
}) as typeof translatePath
20 changes: 13 additions & 7 deletions src/react/urlToFileUrl.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ParsedUrl } from 'next/dist/shared/lib/router/utils/parse-url'
import { Key as PtrKey, match as ptrMatch, parse as ptrParse } from 'path-to-regexp'
import type { ParsedUrlQuery } from 'querystring'
import type { UrlObject } from 'url'
Expand All @@ -15,7 +16,11 @@ enum MATCH_TYPE {
MATCHALL = 'match-all',
}

type TParsedPathParts = { additionalQuery: ParsedUrlQuery; parsedPathParts: string[]; firstMatchType: MATCH_TYPE }
type TParsedPathParts = {
additionalQuery: ParsedUrlQuery
parsedPathParts: string[]
firstMatchType: MATCH_TYPE
}

const getEndFilepathParts = ({ children = [] }: TRouteBranch, locale: string): TParsedPathParts | undefined => {
for (const child of children) {
Expand All @@ -35,7 +40,7 @@ const getEndFilepathParts = ({ children = [] }: TRouteBranch, locale: string): T
if (optionalMatchAllToken) {
return {
parsedPathParts: [child.name],
additionalQuery: { [optionalMatchAllToken.name]: [] },
additionalQuery: {},
firstMatchType: MATCH_TYPE.MATCHALL,
}
}
Expand Down Expand Up @@ -133,7 +138,6 @@ export const parsePathParts = ({
if (isIgnorePathPattern) {
// It is path-ignored, let's unshift (hight priority) it among the delayedCandidates
delayedCandidates.unshift({ candidate, isPathIgnored: true })
continue
} else if (
ptrParse(path).some((ptrToken) => typeof ptrToken === 'object' && ['+', '*'].includes(ptrToken.modifier))
) {
Expand Down Expand Up @@ -183,7 +187,7 @@ export const parsePathParts = ({
// 3. If we are here, it means that we did not find any static match, even among path-ignored candidates descendants,
// because we sorted the candidates in the delayedCandidates array: first the path-ignored candidates, then the dynamic ones.
const path = getLocalePathFromPaths({ paths: candidate.paths, locale })
const match = ptrMatch<ParsedUrlQuery>(path)(currentPathPart)
const match = ptrMatch<Record<string, string>>(path)(currentPathPart)
if (match) {
// It matches! But does its children match too?
const childrenParsedPathParts = parsePathParts({ locale, pathParts: nextPathParts, routeBranch: candidate })
Expand All @@ -209,7 +213,7 @@ export const parsePathParts = ({
if (matchAllCandidate) {
// Yes.
const path = getLocalePathFromPaths({ paths: matchAllCandidate.paths, locale })
const match = ptrMatch<ParsedUrlQuery>('/' + path)('/' + pathParts.join('/'))
const match = ptrMatch<Record<string, string>>('/' + path)('/' + pathParts.join('/'))
if (match) {
// It matches! And it does not have children (or should not).
return {
Expand Down Expand Up @@ -243,14 +247,15 @@ export const parsePathParts = ({
* @returns The file path based, Next.js format, url in UrlObject format
* if the url successfully matched a file path, and undefined otherwise
*/
export const urlToFileUrl = (url: string | URL | UrlObject, locale?: string) => {
export const urlToFileUrl = (url: string | URL | UrlObject, locale?: string): ParsedUrl | undefined => {
const { routesTree, defaultLocale, locales } = getNtrData()
const parsedUrl = parseUrl(url)
const { pathname, query, hash } = parseUrl(url)

if (pathname && anyDynamicFilepathPartsRegex.exec(pathname)) {
// The given url seems to already be a fileUrl, return it as is.
// Not sure if we should return undefined instead. Or throw?
return { pathname, query, hash }
return parsedUrl
}

const result = parsePathParts({
Expand All @@ -261,6 +266,7 @@ export const urlToFileUrl = (url: string | URL | UrlObject, locale?: string) =>
if (result) {
const { parsedPathParts, additionalQuery } = result
return {
...parsedUrl,
pathname: `/${parsedPathParts.join('/')}`,
query: { ...query, ...additionalQuery },
...(hash && { hash }),
Expand Down
26 changes: 9 additions & 17 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6202,12 +6202,11 @@ __metadata:
np: ^7.7.0
path-to-regexp: ^6.2.0
prettier: ^2.3.2
querystring: ^0.2.1
react: ^18.2.0
react-dom: ^18.2.0
rimraf: ^3.0.2
ts-jest: ^27.0.3
typescript: ^4.7.3
typescript: ^5.3.2
url: ^0.11.0
webpack: ^5.65.0
yamljs: ^0.3.0
Expand Down Expand Up @@ -7065,13 +7064,6 @@ __metadata:
languageName: node
linkType: hard

"querystring@npm:^0.2.1":
version: 0.2.1
resolution: "querystring@npm:0.2.1"
checksum: 7b83b45d641e75fd39cd6625ddfd44e7618e741c61e95281b57bbae8fde0afcc12cf851924559e5cc1ef9baa3b1e06e22b164ea1397d65dd94b801f678d9c8ce
languageName: node
linkType: hard

"querystringify@npm:^2.1.1":
version: 2.2.0
resolution: "querystringify@npm:2.2.0"
Expand Down Expand Up @@ -8460,23 +8452,23 @@ __metadata:
languageName: node
linkType: hard

"typescript@npm:^4.7.3":
version: 4.9.5
resolution: "typescript@npm:4.9.5"
"typescript@npm:^5.3.2":
version: 5.3.2
resolution: "typescript@npm:5.3.2"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: ee000bc26848147ad423b581bd250075662a354d84f0e06eb76d3b892328d8d4440b7487b5a83e851b12b255f55d71835b008a66cbf8f255a11e4400159237db
checksum: d92534dda639eb825db013203404c1fabca8ac630564283c9e7dc9e64fd9c9346c2de95ecebdf3e6e8c1c32941bca1cfe0da37877611feb9daf8feeaea58d230
languageName: node
linkType: hard

"typescript@patch:typescript@^4.7.3#~builtin<compat/typescript>":
version: 4.9.5
resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin<compat/typescript>::version=4.9.5&hash=289587"
"typescript@patch:typescript@^5.3.2#~builtin<compat/typescript>":
version: 5.3.2
resolution: "typescript@patch:typescript@npm%3A5.3.2#~builtin<compat/typescript>::version=5.3.2&hash=29ae49"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 1f8f3b6aaea19f0f67cba79057674ba580438a7db55057eb89cc06950483c5d632115c14077f6663ea76fd09fce3c190e6414bb98582ec80aa5a4eaf345d5b68
checksum: c034461079fbfde3cb584ddee52afccb15b6e32a0ce186d0b2719968786f7ca73e1b07f71fac4163088790b16811c6ccf79680de190664ef66ff0ba9c1fe4a23
languageName: node
linkType: hard

Expand Down