rsnext/packages/next/next-server/lib/router/utils/prepare-destination.ts
JJ Kasper 1b4d463cc8
Update rewrite params query appending (#16189)
This updates to not automatically append params to the query for rewrites if one or more of the params are already used in the destination's path. No other behavior is being changed and if the user still wants the params in the query after using them in the destination's path they can manually add them like with redirects.

Closes: https://github.com/vercel/next.js/issues/15626
2020-08-14 18:51:58 +00:00

134 lines
3.7 KiB
TypeScript

import { ParsedUrlQuery } from 'querystring'
import { searchParamsToUrlQuery } from './querystring'
import { parseRelativeUrl } from './parse-relative-url'
import * as pathToRegexp from 'next/dist/compiled/path-to-regexp'
type Params = { [param: string]: any }
export default function prepareDestination(
destination: string,
params: Params,
query: ParsedUrlQuery,
appendParamsToQuery: boolean,
basePath: string
) {
let parsedDestination: {
query?: ParsedUrlQuery
protocol?: string
hostname?: string
port?: string
} & ReturnType<typeof parseRelativeUrl> = {} as any
if (destination.startsWith('/')) {
parsedDestination = parseRelativeUrl(destination)
} else {
const {
pathname,
searchParams,
hash,
hostname,
port,
protocol,
search,
href,
} = new URL(destination)
parsedDestination = {
pathname,
searchParams,
hash,
protocol,
hostname,
port,
search,
href,
}
}
parsedDestination.query = searchParamsToUrlQuery(
parsedDestination.searchParams
)
const destQuery = parsedDestination.query
const destPath = `${parsedDestination.pathname!}${
parsedDestination.hash || ''
}`
const destPathParamKeys: pathToRegexp.Key[] = []
pathToRegexp.pathToRegexp(destPath, destPathParamKeys)
const destPathParams = destPathParamKeys.map((key) => key.name)
let destinationCompiler = pathToRegexp.compile(
destPath,
// we don't validate while compiling the destination since we should
// have already validated before we got to this point and validating
// breaks compiling destinations with named pattern params from the source
// e.g. /something:hello(.*) -> /another/:hello is broken with validation
// since compile validation is meant for reversing and not for inserting
// params from a separate path-regex into another
{ validate: false }
)
let newUrl
// update any params in query values
for (const [key, strOrArray] of Object.entries(destQuery)) {
let value = Array.isArray(strOrArray) ? strOrArray[0] : strOrArray
if (value) {
// the value needs to start with a forward-slash to be compiled
// correctly
value = `/${value}`
const queryCompiler = pathToRegexp.compile(value, { validate: false })
value = queryCompiler(params).substr(1)
}
destQuery[key] = value
}
// add path params to query if it's not a redirect and not
// already defined in destination query or path
const paramKeys = Object.keys(params)
if (
appendParamsToQuery &&
!paramKeys.some((key) => destPathParams.includes(key))
) {
for (const key of paramKeys) {
if (!(key in destQuery)) {
destQuery[key] = params[key]
}
}
}
const shouldAddBasePath = destination.startsWith('/') && basePath
try {
newUrl = `${shouldAddBasePath ? basePath : ''}${encodeURI(
destinationCompiler(params)
)}`
const [pathname, hash] = newUrl.split('#')
parsedDestination.pathname = pathname
parsedDestination.hash = `${hash ? '#' : ''}${hash || ''}`
delete parsedDestination.search
delete parsedDestination.searchParams
} catch (err) {
if (err.message.match(/Expected .*? to not repeat, but got an array/)) {
throw new Error(
`To use a multi-match in the destination you must add \`*\` at the end of the param name to signify it should repeat. https://err.sh/vercel/next.js/invalid-multi-match`
)
}
throw err
}
// Query merge order lowest priority to highest
// 1. initial URL query values
// 2. path segment values
// 3. destination specified query values
parsedDestination.query = {
...query,
...parsedDestination.query,
}
return {
newUrl,
parsedDestination,
}
}