Skip to content
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
66 changes: 66 additions & 0 deletions packages/router-core/src/path.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isServer } from '@tanstack/router-core/isServer'
import { last } from './utils'
import {
SEGMENT_TYPE_OPTIONAL_PARAM,
Expand Down Expand Up @@ -224,6 +225,11 @@ interface InterpolatePathOptions {
* Obtained from `compileDecodeCharMap(pathParamsAllowedCharacters)`.
*/
decoder?: (encoded: string) => string
/**
* @internal
* For testing only, in development mode we use the router.isServer value
*/
server?: boolean
}

type InterPolatePathResult = {
Expand Down Expand Up @@ -258,6 +264,7 @@ export function interpolatePath({
path,
params,
decoder,
server,
}: InterpolatePathOptions): InterPolatePathResult {
// Tracking if any params are missing in the `params` object
// when interpolating the path
Expand All @@ -269,6 +276,65 @@ export function interpolatePath({
if (!path.includes('$'))
return { interpolatedPath: path, usedParams, isMissingParams }

if (isServer ?? server) {
// Fast path for common templates like `/posts/$id` or `/files/$`.
// Braced segments (`{...}`) are more complex (prefix/suffix/optional) and are
// handled by the general parser below.
if (path.indexOf('{') === -1) {
const length = path.length
let cursor = 0
let joined = ''

while (cursor < length) {
// Skip slashes between segments. '/' code is 47
while (cursor < length && path.charCodeAt(cursor) === 47) cursor++
if (cursor >= length) break

const start = cursor
let end = path.indexOf('/', cursor)
if (end === -1) end = length
cursor = end

const part = path.substring(start, end)
if (!part) continue

// `$id` or `$` (splat). '$' code is 36
if (part.charCodeAt(0) === 36) {
if (part.length === 1) {
const splat = params._splat
usedParams._splat = splat
// TODO: Deprecate *
usedParams['*'] = splat

if (!splat) {
isMissingParams = true
continue
}

const value = encodeParam('_splat', params, decoder)
joined += '/' + value
} else {
const key = part.substring(1)
if (!isMissingParams && !(key in params)) {
isMissingParams = true
}
usedParams[key] = params[key]

const value = encodeParam(key, params, decoder) ?? 'undefined'
joined += '/' + value
}
} else {
joined += '/' + part
}
}

if (path.endsWith('/')) joined += '/'

const interpolatedPath = joined || '/'
return { usedParams, interpolatedPath, isMissingParams }
}
}

const length = path.length
let cursor = 0
let segment
Expand Down
3 changes: 3 additions & 0 deletions packages/router-core/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1440,6 +1440,7 @@ export class RouterCore<
path: route.fullPath,
params: routeParams,
decoder: this.pathParamsDecoder,
server: this.isServer,
})

// Waste not, want not. If we already have a match for this route,
Expand Down Expand Up @@ -1804,6 +1805,7 @@ export class RouterCore<
const interpolatedNextTo = interpolatePath({
path: nextTo,
params: nextParams,
server: this.isServer,
}).interpolatedPath

// Use lightweight getMatchedRoutes instead of matchRoutesInternal
Expand Down Expand Up @@ -1850,6 +1852,7 @@ export class RouterCore<
path: nextTo,
params: nextParams,
decoder: this.pathParamsDecoder,
server: this.isServer,
}).interpolatedPath,
)

Expand Down
Loading
Loading