diff --git a/package.json b/package.json index 1037a22..6aace04 100644 --- a/package.json +++ b/package.json @@ -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" }, @@ -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" }, diff --git a/src/react/enhanceNextRouter.ts b/src/react/enhanceNextRouter.ts index 7f6dc2e..a85154c 100644 --- a/src/react/enhanceNextRouter.ts +++ b/src/react/enhanceNextRouter.ts @@ -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' @@ -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 = (router: R) => { diff --git a/src/react/fileUrlToFileUrlObject.ts b/src/react/fileUrlToFileUrlObject.ts index 1f40cf8..f030117 100644 --- a/src/react/fileUrlToFileUrlObject.ts +++ b/src/react/fileUrlToFileUrlObject.ts @@ -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 diff --git a/src/react/parseUrl.ts b/src/react/parseUrl.ts index 66bfb6f..7807c93 100644 --- a/src/react/parseUrl.ts +++ b/src/react/parseUrl.ts @@ -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 || '', + } diff --git a/src/react/translateUrl.ts b/src/react/translateUrl.ts index 6da9bcc..ccf28e3 100644 --- a/src/react/translateUrl.ts +++ b/src/react/translateUrl.ts @@ -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' @@ -80,7 +82,7 @@ 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)) @@ -88,7 +90,7 @@ const translatePathParts = ({ // 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 })], @@ -144,11 +146,14 @@ export function translatePath { 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 } } @@ -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 diff --git a/src/react/urlToFileUrl.ts b/src/react/urlToFileUrl.ts index 9c049af..f09815c 100644 --- a/src/react/urlToFileUrl.ts +++ b/src/react/urlToFileUrl.ts @@ -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' @@ -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) { @@ -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, } } @@ -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)) ) { @@ -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(path)(currentPathPart) + const match = ptrMatch>(path)(currentPathPart) if (match) { // It matches! But does its children match too? const childrenParsedPathParts = parsePathParts({ locale, pathParts: nextPathParts, routeBranch: candidate }) @@ -209,7 +213,7 @@ export const parsePathParts = ({ if (matchAllCandidate) { // Yes. const path = getLocalePathFromPaths({ paths: matchAllCandidate.paths, locale }) - const match = ptrMatch('/' + path)('/' + pathParts.join('/')) + const match = ptrMatch>('/' + path)('/' + pathParts.join('/')) if (match) { // It matches! And it does not have children (or should not). return { @@ -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({ @@ -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 }), diff --git a/yarn.lock b/yarn.lock index 4870db9..ec25014 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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 @@ -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" @@ -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": - version: 4.9.5 - resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=289587" +"typescript@patch:typescript@^5.3.2#~builtin": + version: 5.3.2 + resolution: "typescript@patch:typescript@npm%3A5.3.2#~builtin::version=5.3.2&hash=29ae49" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 1f8f3b6aaea19f0f67cba79057674ba580438a7db55057eb89cc06950483c5d632115c14077f6663ea76fd09fce3c190e6414bb98582ec80aa5a4eaf345d5b68 + checksum: c034461079fbfde3cb584ddee52afccb15b6e32a0ce186d0b2719968786f7ca73e1b07f71fac4163088790b16811c6ccf79680de190664ef66ff0ba9c1fe4a23 languageName: node linkType: hard