@tanstack/router-core 1.154.14 → 1.156.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/new-process-route-tree.cjs +47 -42
- package/dist/cjs/new-process-route-tree.cjs.map +1 -1
- package/dist/cjs/new-process-route-tree.d.cts +13 -8
- package/dist/cjs/path.cjs +18 -14
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/path.d.cts +11 -2
- package/dist/cjs/router.cjs +122 -57
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +19 -5
- package/dist/esm/new-process-route-tree.d.ts +13 -8
- package/dist/esm/new-process-route-tree.js +47 -42
- package/dist/esm/new-process-route-tree.js.map +1 -1
- package/dist/esm/path.d.ts +11 -2
- package/dist/esm/path.js +18 -14
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/router.d.ts +19 -5
- package/dist/esm/router.js +124 -59
- package/dist/esm/router.js.map +1 -1
- package/package.json +1 -1
- package/src/new-process-route-tree.ts +78 -54
- package/src/path.ts +36 -16
- package/src/router.ts +174 -59
package/dist/esm/path.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.js","sources":["../../src/path.ts"],"sourcesContent":["import { last } from './utils'\nimport {\n SEGMENT_TYPE_OPTIONAL_PARAM,\n SEGMENT_TYPE_PARAM,\n SEGMENT_TYPE_PATHNAME,\n SEGMENT_TYPE_WILDCARD,\n parseSegment,\n} from './new-process-route-tree'\nimport type { LRUCache } from './lru-cache'\n\n/** Join path segments, cleaning duplicate slashes between parts. */\nexport function joinPaths(paths: Array<string | undefined>) {\n return cleanPath(\n paths\n .filter((val) => {\n return val !== undefined\n })\n .join('/'),\n )\n}\n\n/** Remove repeated slashes from a path string. */\nexport function cleanPath(path: string) {\n // remove double slashes\n return path.replace(/\\/{2,}/g, '/')\n}\n\n/** Trim leading slashes (except preserving root '/'). */\nexport function trimPathLeft(path: string) {\n return path === '/' ? path : path.replace(/^\\/{1,}/, '')\n}\n\n/** Trim trailing slashes (except preserving root '/'). */\nexport function trimPathRight(path: string) {\n const len = path.length\n return len > 1 && path[len - 1] === '/' ? path.replace(/\\/{1,}$/, '') : path\n}\n\n/** Trim both leading and trailing slashes. */\nexport function trimPath(path: string) {\n return trimPathRight(trimPathLeft(path))\n}\n\n/** Remove a trailing slash from value when appropriate for comparisons. */\nexport function removeTrailingSlash(value: string, basepath: string): string {\n if (value?.endsWith('/') && value !== '/' && value !== `${basepath}/`) {\n return value.slice(0, -1)\n }\n return value\n}\n\n// intended to only compare path name\n// see the usage in the isActive under useLinkProps\n// /sample/path1 = /sample/path1/\n// /sample/path1/some <> /sample/path1\n/**\n * Compare two pathnames for exact equality after normalizing trailing slashes\n * relative to the provided `basepath`.\n */\nexport function exactPathTest(\n pathName1: string,\n pathName2: string,\n basepath: string,\n): boolean {\n return (\n removeTrailingSlash(pathName1, basepath) ===\n removeTrailingSlash(pathName2, basepath)\n )\n}\n\n// When resolving relative paths, we treat all paths as if they are trailing slash\n// documents. All trailing slashes are removed after the path is resolved.\n// Here are a few examples:\n//\n// /a/b/c + ./d = /a/b/c/d\n// /a/b/c + ../d = /a/b/d\n// /a/b/c + ./d/ = /a/b/c/d\n// /a/b/c + ../d/ = /a/b/d\n// /a/b/c + ./ = /a/b/c\n//\n// Absolute paths that start with `/` short circuit the resolution process to the root\n// path.\n//\n// Here are some examples:\n//\n// /a/b/c + /d = /d\n// /a/b/c + /d/ = /d\n// /a/b/c + / = /\n//\n// Non-.-prefixed paths are still treated as relative paths, resolved like `./`\n//\n// Here are some examples:\n//\n// /a/b/c + d = /a/b/c/d\n// /a/b/c + d/ = /a/b/c/d\n// /a/b/c + d/e = /a/b/c/d/e\ninterface ResolvePathOptions {\n base: string\n to: string\n trailingSlash?: 'always' | 'never' | 'preserve'\n cache?: LRUCache<string, string>\n}\n\n/**\n * Resolve a destination path against a base, honoring trailing-slash policy\n * and supporting relative segments (`.`/`..`) and absolute `to` values.\n */\nexport function resolvePath({\n base,\n to,\n trailingSlash = 'never',\n cache,\n}: ResolvePathOptions) {\n const isAbsolute = to.startsWith('/')\n const isBase = !isAbsolute && to === '.'\n\n let key\n if (cache) {\n // `trailingSlash` is static per router, so it doesn't need to be part of the cache key\n key = isAbsolute ? to : isBase ? base : base + '\\0' + to\n const cached = cache.get(key)\n if (cached) return cached\n }\n\n let baseSegments: Array<string>\n if (isBase) {\n baseSegments = base.split('/')\n } else if (isAbsolute) {\n baseSegments = to.split('/')\n } else {\n baseSegments = base.split('/')\n while (baseSegments.length > 1 && last(baseSegments) === '') {\n baseSegments.pop()\n }\n\n const toSegments = to.split('/')\n for (let index = 0, length = toSegments.length; index < length; index++) {\n const value = toSegments[index]!\n if (value === '') {\n if (!index) {\n // Leading slash\n baseSegments = [value]\n } else if (index === length - 1) {\n // Trailing Slash\n baseSegments.push(value)\n } else {\n // ignore inter-slashes\n }\n } else if (value === '..') {\n baseSegments.pop()\n } else if (value === '.') {\n // ignore\n } else {\n baseSegments.push(value)\n }\n }\n }\n\n if (baseSegments.length > 1) {\n if (last(baseSegments) === '') {\n if (trailingSlash === 'never') {\n baseSegments.pop()\n }\n } else if (trailingSlash === 'always') {\n baseSegments.push('')\n }\n }\n\n let segment\n let joined = ''\n for (let i = 0; i < baseSegments.length; i++) {\n if (i > 0) joined += '/'\n const part = baseSegments[i]!\n if (!part) continue\n segment = parseSegment(part, 0, segment)\n const kind = segment[0]\n if (kind === SEGMENT_TYPE_PATHNAME) {\n joined += part\n continue\n }\n const end = segment[5]\n const prefix = part.substring(0, segment[1])\n const suffix = part.substring(segment[4], end)\n const value = part.substring(segment[2], segment[3])\n if (kind === SEGMENT_TYPE_PARAM) {\n joined += prefix || suffix ? `${prefix}{$${value}}${suffix}` : `$${value}`\n } else if (kind === SEGMENT_TYPE_WILDCARD) {\n joined += prefix || suffix ? `${prefix}{$}${suffix}` : '$'\n } else {\n // SEGMENT_TYPE_OPTIONAL_PARAM\n joined += `${prefix}{-$${value}}${suffix}`\n }\n }\n joined = cleanPath(joined)\n const result = joined || '/'\n if (key && cache) cache.set(key, result)\n return result\n}\n\ninterface InterpolatePathOptions {\n path?: string\n params: Record<string, unknown>\n // Map of encoded chars to decoded chars (e.g. '%40' -> '@') that should remain decoded in path params\n decodeCharMap?: Map<string, string>\n}\n\ntype InterPolatePathResult = {\n interpolatedPath: string\n usedParams: Record<string, unknown>\n isMissingParams: boolean // true if any params were not available when being looked up in the params object\n}\n\nfunction encodeParam(\n key: string,\n params: InterpolatePathOptions['params'],\n decodeCharMap: InterpolatePathOptions['decodeCharMap'],\n): any {\n const value = params[key]\n if (typeof value !== 'string') return value\n\n if (key === '_splat') {\n // the splat/catch-all routes shouldn't have the '/' encoded out\n return encodeURI(value)\n } else {\n return encodePathParam(value, decodeCharMap)\n }\n}\n\n/**\n * Interpolate params and wildcards into a route path template.\n *\n * - Encodes params safely (configurable allowed characters)\n * - Supports `{-$optional}` segments, `{prefix{$id}suffix}` and `{$}` wildcards\n */\nexport function interpolatePath({\n path,\n params,\n decodeCharMap,\n}: InterpolatePathOptions): InterPolatePathResult {\n // Tracking if any params are missing in the `params` object\n // when interpolating the path\n let isMissingParams = false\n const usedParams: Record<string, unknown> = {}\n\n if (!path || path === '/')\n return { interpolatedPath: '/', usedParams, isMissingParams }\n if (!path.includes('$'))\n return { interpolatedPath: path, usedParams, isMissingParams }\n\n const length = path.length\n let cursor = 0\n let segment\n let joined = ''\n while (cursor < length) {\n const start = cursor\n segment = parseSegment(path, start, segment)\n const end = segment[5]\n cursor = end + 1\n\n if (start === end) continue\n\n const kind = segment[0]\n\n if (kind === SEGMENT_TYPE_PATHNAME) {\n joined += '/' + path.substring(start, end)\n continue\n }\n\n if (kind === SEGMENT_TYPE_WILDCARD) {\n const splat = params._splat\n usedParams._splat = splat\n // TODO: Deprecate *\n usedParams['*'] = splat\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n\n // Check if _splat parameter is missing. _splat could be missing if undefined or an empty string or some other falsy value.\n if (!splat) {\n isMissingParams = true\n // For missing splat parameters, just return the prefix and suffix without the wildcard\n // If there is a prefix or suffix, return them joined, otherwise omit the segment\n if (prefix || suffix) {\n joined += '/' + prefix + suffix\n }\n continue\n }\n\n const value = encodeParam('_splat', params, decodeCharMap)\n joined += '/' + prefix + value + suffix\n continue\n }\n\n if (kind === SEGMENT_TYPE_PARAM) {\n const key = path.substring(segment[2], segment[3])\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n const value = encodeParam(key, params, decodeCharMap) ?? 'undefined'\n joined += '/' + prefix + value + suffix\n continue\n }\n\n if (kind === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const key = path.substring(segment[2], segment[3])\n const valueRaw = params[key]\n\n // Check if optional parameter is missing or undefined\n if (valueRaw == null) continue\n\n usedParams[key] = valueRaw\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n const value = encodeParam(key, params, decodeCharMap) ?? ''\n joined += '/' + prefix + value + suffix\n continue\n }\n }\n\n if (path.endsWith('/')) joined += '/'\n\n const interpolatedPath = joined || '/'\n\n return { usedParams, interpolatedPath, isMissingParams }\n}\n\nfunction encodePathParam(value: string, decodeCharMap?: Map<string, string>) {\n let encoded = encodeURIComponent(value)\n if (decodeCharMap) {\n for (const [encodedChar, char] of decodeCharMap) {\n encoded = encoded.replaceAll(encodedChar, char)\n }\n }\n return encoded\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"path.js","sources":["../../src/path.ts"],"sourcesContent":["import { last } from './utils'\nimport {\n SEGMENT_TYPE_OPTIONAL_PARAM,\n SEGMENT_TYPE_PARAM,\n SEGMENT_TYPE_PATHNAME,\n SEGMENT_TYPE_WILDCARD,\n parseSegment,\n} from './new-process-route-tree'\nimport type { LRUCache } from './lru-cache'\n\n/** Join path segments, cleaning duplicate slashes between parts. */\nexport function joinPaths(paths: Array<string | undefined>) {\n return cleanPath(\n paths\n .filter((val) => {\n return val !== undefined\n })\n .join('/'),\n )\n}\n\n/** Remove repeated slashes from a path string. */\nexport function cleanPath(path: string) {\n // remove double slashes\n return path.replace(/\\/{2,}/g, '/')\n}\n\n/** Trim leading slashes (except preserving root '/'). */\nexport function trimPathLeft(path: string) {\n return path === '/' ? path : path.replace(/^\\/{1,}/, '')\n}\n\n/** Trim trailing slashes (except preserving root '/'). */\nexport function trimPathRight(path: string) {\n const len = path.length\n return len > 1 && path[len - 1] === '/' ? path.replace(/\\/{1,}$/, '') : path\n}\n\n/** Trim both leading and trailing slashes. */\nexport function trimPath(path: string) {\n return trimPathRight(trimPathLeft(path))\n}\n\n/** Remove a trailing slash from value when appropriate for comparisons. */\nexport function removeTrailingSlash(value: string, basepath: string): string {\n if (value?.endsWith('/') && value !== '/' && value !== `${basepath}/`) {\n return value.slice(0, -1)\n }\n return value\n}\n\n// intended to only compare path name\n// see the usage in the isActive under useLinkProps\n// /sample/path1 = /sample/path1/\n// /sample/path1/some <> /sample/path1\n/**\n * Compare two pathnames for exact equality after normalizing trailing slashes\n * relative to the provided `basepath`.\n */\nexport function exactPathTest(\n pathName1: string,\n pathName2: string,\n basepath: string,\n): boolean {\n return (\n removeTrailingSlash(pathName1, basepath) ===\n removeTrailingSlash(pathName2, basepath)\n )\n}\n\n// When resolving relative paths, we treat all paths as if they are trailing slash\n// documents. All trailing slashes are removed after the path is resolved.\n// Here are a few examples:\n//\n// /a/b/c + ./d = /a/b/c/d\n// /a/b/c + ../d = /a/b/d\n// /a/b/c + ./d/ = /a/b/c/d\n// /a/b/c + ../d/ = /a/b/d\n// /a/b/c + ./ = /a/b/c\n//\n// Absolute paths that start with `/` short circuit the resolution process to the root\n// path.\n//\n// Here are some examples:\n//\n// /a/b/c + /d = /d\n// /a/b/c + /d/ = /d\n// /a/b/c + / = /\n//\n// Non-.-prefixed paths are still treated as relative paths, resolved like `./`\n//\n// Here are some examples:\n//\n// /a/b/c + d = /a/b/c/d\n// /a/b/c + d/ = /a/b/c/d\n// /a/b/c + d/e = /a/b/c/d/e\ninterface ResolvePathOptions {\n base: string\n to: string\n trailingSlash?: 'always' | 'never' | 'preserve'\n cache?: LRUCache<string, string>\n}\n\n/**\n * Resolve a destination path against a base, honoring trailing-slash policy\n * and supporting relative segments (`.`/`..`) and absolute `to` values.\n */\nexport function resolvePath({\n base,\n to,\n trailingSlash = 'never',\n cache,\n}: ResolvePathOptions) {\n const isAbsolute = to.startsWith('/')\n const isBase = !isAbsolute && to === '.'\n\n let key\n if (cache) {\n // `trailingSlash` is static per router, so it doesn't need to be part of the cache key\n key = isAbsolute ? to : isBase ? base : base + '\\0' + to\n const cached = cache.get(key)\n if (cached) return cached\n }\n\n let baseSegments: Array<string>\n if (isBase) {\n baseSegments = base.split('/')\n } else if (isAbsolute) {\n baseSegments = to.split('/')\n } else {\n baseSegments = base.split('/')\n while (baseSegments.length > 1 && last(baseSegments) === '') {\n baseSegments.pop()\n }\n\n const toSegments = to.split('/')\n for (let index = 0, length = toSegments.length; index < length; index++) {\n const value = toSegments[index]!\n if (value === '') {\n if (!index) {\n // Leading slash\n baseSegments = [value]\n } else if (index === length - 1) {\n // Trailing Slash\n baseSegments.push(value)\n } else {\n // ignore inter-slashes\n }\n } else if (value === '..') {\n baseSegments.pop()\n } else if (value === '.') {\n // ignore\n } else {\n baseSegments.push(value)\n }\n }\n }\n\n if (baseSegments.length > 1) {\n if (last(baseSegments) === '') {\n if (trailingSlash === 'never') {\n baseSegments.pop()\n }\n } else if (trailingSlash === 'always') {\n baseSegments.push('')\n }\n }\n\n let segment\n let joined = ''\n for (let i = 0; i < baseSegments.length; i++) {\n if (i > 0) joined += '/'\n const part = baseSegments[i]!\n if (!part) continue\n segment = parseSegment(part, 0, segment)\n const kind = segment[0]\n if (kind === SEGMENT_TYPE_PATHNAME) {\n joined += part\n continue\n }\n const end = segment[5]\n const prefix = part.substring(0, segment[1])\n const suffix = part.substring(segment[4], end)\n const value = part.substring(segment[2], segment[3])\n if (kind === SEGMENT_TYPE_PARAM) {\n joined += prefix || suffix ? `${prefix}{$${value}}${suffix}` : `$${value}`\n } else if (kind === SEGMENT_TYPE_WILDCARD) {\n joined += prefix || suffix ? `${prefix}{$}${suffix}` : '$'\n } else {\n // SEGMENT_TYPE_OPTIONAL_PARAM\n joined += `${prefix}{-$${value}}${suffix}`\n }\n }\n joined = cleanPath(joined)\n const result = joined || '/'\n if (key && cache) cache.set(key, result)\n return result\n}\n\n/**\n * Create a pre-compiled decode config from allowed characters.\n * This should be called once at router initialization.\n */\nexport function compileDecodeCharMap(\n pathParamsAllowedCharacters: ReadonlyArray<string>,\n) {\n const charMap = new Map(\n pathParamsAllowedCharacters.map((char) => [encodeURIComponent(char), char]),\n )\n // Escape special regex characters and join with |\n const pattern = Array.from(charMap.keys())\n .map((key) => key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n .join('|')\n const regex = new RegExp(pattern, 'g')\n return (encoded: string) =>\n encoded.replace(regex, (match) => charMap.get(match) ?? match)\n}\n\ninterface InterpolatePathOptions {\n path?: string\n params: Record<string, unknown>\n /**\n * A function that decodes a path parameter value.\n * Obtained from `compileDecodeCharMap(pathParamsAllowedCharacters)`.\n */\n decoder?: (encoded: string) => string\n}\n\ntype InterPolatePathResult = {\n interpolatedPath: string\n usedParams: Record<string, unknown>\n isMissingParams: boolean // true if any params were not available when being looked up in the params object\n}\n\nfunction encodeParam(\n key: string,\n params: InterpolatePathOptions['params'],\n decoder: InterpolatePathOptions['decoder'],\n): any {\n const value = params[key]\n if (typeof value !== 'string') return value\n\n if (key === '_splat') {\n // the splat/catch-all routes shouldn't have the '/' encoded out\n return encodeURI(value)\n } else {\n return encodePathParam(value, decoder)\n }\n}\n\n/**\n * Interpolate params and wildcards into a route path template.\n *\n * - Encodes params safely (configurable allowed characters)\n * - Supports `{-$optional}` segments, `{prefix{$id}suffix}` and `{$}` wildcards\n */\nexport function interpolatePath({\n path,\n params,\n decoder,\n}: InterpolatePathOptions): InterPolatePathResult {\n // Tracking if any params are missing in the `params` object\n // when interpolating the path\n let isMissingParams = false\n const usedParams: Record<string, unknown> = {}\n\n if (!path || path === '/')\n return { interpolatedPath: '/', usedParams, isMissingParams }\n if (!path.includes('$'))\n return { interpolatedPath: path, usedParams, isMissingParams }\n\n const length = path.length\n let cursor = 0\n let segment\n let joined = ''\n while (cursor < length) {\n const start = cursor\n segment = parseSegment(path, start, segment)\n const end = segment[5]\n cursor = end + 1\n\n if (start === end) continue\n\n const kind = segment[0]\n\n if (kind === SEGMENT_TYPE_PATHNAME) {\n joined += '/' + path.substring(start, end)\n continue\n }\n\n if (kind === SEGMENT_TYPE_WILDCARD) {\n const splat = params._splat\n usedParams._splat = splat\n // TODO: Deprecate *\n usedParams['*'] = splat\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n\n // Check if _splat parameter is missing. _splat could be missing if undefined or an empty string or some other falsy value.\n if (!splat) {\n isMissingParams = true\n // For missing splat parameters, just return the prefix and suffix without the wildcard\n // If there is a prefix or suffix, return them joined, otherwise omit the segment\n if (prefix || suffix) {\n joined += '/' + prefix + suffix\n }\n continue\n }\n\n const value = encodeParam('_splat', params, decoder)\n joined += '/' + prefix + value + suffix\n continue\n }\n\n if (kind === SEGMENT_TYPE_PARAM) {\n const key = path.substring(segment[2], segment[3])\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n const value = encodeParam(key, params, decoder) ?? 'undefined'\n joined += '/' + prefix + value + suffix\n continue\n }\n\n if (kind === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const key = path.substring(segment[2], segment[3])\n const valueRaw = params[key]\n\n // Check if optional parameter is missing or undefined\n if (valueRaw == null) continue\n\n usedParams[key] = valueRaw\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n const value = encodeParam(key, params, decoder) ?? ''\n joined += '/' + prefix + value + suffix\n continue\n }\n }\n\n if (path.endsWith('/')) joined += '/'\n\n const interpolatedPath = joined || '/'\n\n return { usedParams, interpolatedPath, isMissingParams }\n}\n\nfunction encodePathParam(\n value: string,\n decoder?: InterpolatePathOptions['decoder'],\n) {\n const encoded = encodeURIComponent(value)\n return decoder?.(encoded) ?? encoded\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/router.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Store } from '@tanstack/store';
|
|
2
2
|
import { loadRouteChunk } from './load-matches.js';
|
|
3
|
-
import {
|
|
3
|
+
import { LRUCache } from './lru-cache.js';
|
|
4
|
+
import { ProcessRouteTreeResult, ProcessedTree } from './new-process-route-tree.js';
|
|
4
5
|
import { SearchParser, SearchSerializer } from './searchParams.js';
|
|
5
6
|
import { AnyRedirect, ResolvedRedirect } from './redirect.js';
|
|
6
7
|
import { HistoryLocation, HistoryState, ParsedHistoryState, RouterHistory } from '@tanstack/history';
|
|
@@ -439,7 +440,6 @@ export type SubscribeFn = <TType extends keyof RouterEvents>(eventType: TType, f
|
|
|
439
440
|
export interface MatchRoutesOpts {
|
|
440
441
|
preload?: boolean;
|
|
441
442
|
throwOnError?: boolean;
|
|
442
|
-
_buildLocation?: boolean;
|
|
443
443
|
dest?: BuildNextOptions;
|
|
444
444
|
}
|
|
445
445
|
export type InferRouterContext<TRouteTree extends AnyRoute> = TRouteTree['types']['routerContext'];
|
|
@@ -555,6 +555,13 @@ export declare function getLocationChangeInfo(routerState: {
|
|
|
555
555
|
hashChanged: boolean;
|
|
556
556
|
};
|
|
557
557
|
export type CreateRouterFn = <TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption = 'never', TDefaultStructuralSharingOption extends boolean = false, TRouterHistory extends RouterHistory = RouterHistory, TDehydrated extends Record<string, any> = Record<string, any>>(options: undefined extends number ? 'strictNullChecks must be enabled in tsconfig.json' : RouterConstructorOptions<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory, TDehydrated>) => RouterCore<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory, TDehydrated>;
|
|
558
|
+
declare global {
|
|
559
|
+
var __TSR_CACHE__: {
|
|
560
|
+
routeTree: AnyRoute;
|
|
561
|
+
processRouteTreeResult: ProcessRouteTreeResult<AnyRoute>;
|
|
562
|
+
resolvePathCache: LRUCache<string, string>;
|
|
563
|
+
} | undefined;
|
|
564
|
+
}
|
|
558
565
|
/**
|
|
559
566
|
* Core, framework-agnostic router engine that powers TanStack Router.
|
|
560
567
|
*
|
|
@@ -585,8 +592,9 @@ export declare class RouterCore<in out TRouteTree extends AnyRoute, in out TTrai
|
|
|
585
592
|
routesById: RoutesById<TRouteTree>;
|
|
586
593
|
routesByPath: RoutesByPath<TRouteTree>;
|
|
587
594
|
processedTree: ProcessedTree<TRouteTree, any, any>;
|
|
595
|
+
resolvePathCache: LRUCache<string, string>;
|
|
588
596
|
isServer: boolean;
|
|
589
|
-
|
|
597
|
+
pathParamsDecoder?: (encoded: string) => string;
|
|
590
598
|
/**
|
|
591
599
|
* @deprecated Use the `createRouter` function instead
|
|
592
600
|
*/
|
|
@@ -597,7 +605,8 @@ export declare class RouterCore<in out TRouteTree extends AnyRoute, in out TTrai
|
|
|
597
605
|
update: UpdateFn<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory, TDehydrated>;
|
|
598
606
|
get state(): RouterState<TRouteTree>;
|
|
599
607
|
updateLatestLocation: () => void;
|
|
600
|
-
buildRouteTree: () =>
|
|
608
|
+
buildRouteTree: () => ProcessRouteTreeResult<TRouteTree>;
|
|
609
|
+
setRoutes({ routesById, routesByPath, processedTree, }: ProcessRouteTreeResult<TRouteTree>): void;
|
|
601
610
|
/**
|
|
602
611
|
* Subscribe to router lifecycle events like `onBeforeNavigate`, `onLoad`,
|
|
603
612
|
* `onResolved`, etc. Returns an unsubscribe function.
|
|
@@ -611,13 +620,18 @@ export declare class RouterCore<in out TRouteTree extends AnyRoute, in out TTrai
|
|
|
611
620
|
* current router options, rewrite rules and search parser/stringifier.
|
|
612
621
|
*/
|
|
613
622
|
parseLocation: ParseLocationFn<TRouteTree>;
|
|
614
|
-
resolvePathCache: import('./lru-cache.js').LRUCache<string, string>;
|
|
615
623
|
/** Resolve a path against the router basepath and trailing-slash policy. */
|
|
616
624
|
resolvePathWithBase: (from: string, path: string) => string;
|
|
617
625
|
get looseRoutesById(): Record<string, AnyRoute>;
|
|
618
626
|
matchRoutes: MatchRoutesFn;
|
|
619
627
|
private matchRoutesInternal;
|
|
620
628
|
getMatchedRoutes: GetMatchRoutesFn;
|
|
629
|
+
/**
|
|
630
|
+
* Lightweight route matching for buildLocation.
|
|
631
|
+
* Only computes fullPath, accumulated search, and params - skipping expensive
|
|
632
|
+
* operations like AbortController, ControlledPromise, loaderDeps, and full match objects.
|
|
633
|
+
*/
|
|
634
|
+
private matchRoutesLightweight;
|
|
621
635
|
cancelMatch: (id: string) => void;
|
|
622
636
|
cancelMatches: () => void;
|
|
623
637
|
/**
|
package/dist/esm/router.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Store, batch } from "@tanstack/store";
|
|
2
2
|
import { createBrowserHistory, parseHref } from "@tanstack/history";
|
|
3
|
-
import { createControlledPromise, isDangerousProtocol, deepEqual, replaceEqualDeep, decodePath, functionalUpdate,
|
|
3
|
+
import { createControlledPromise, isDangerousProtocol, deepEqual, replaceEqualDeep, last, decodePath, functionalUpdate, findLast } from "./utils.js";
|
|
4
4
|
import { processRouteTree, processRouteMasks, findSingleMatch, findRouteMatch, findFlatMatch } from "./new-process-route-tree.js";
|
|
5
|
-
import { trimPath, resolvePath, cleanPath, trimPathRight, interpolatePath } from "./path.js";
|
|
5
|
+
import { compileDecodeCharMap, trimPath, resolvePath, cleanPath, trimPathRight, interpolatePath } from "./path.js";
|
|
6
6
|
import { createLRUCache } from "./lru-cache.js";
|
|
7
7
|
import { isNotFound } from "./not-found.js";
|
|
8
8
|
import { setupScrollRestoration } from "./scroll-restoration.js";
|
|
@@ -69,12 +69,10 @@ class RouterCore {
|
|
|
69
69
|
...newOptions
|
|
70
70
|
};
|
|
71
71
|
this.isServer = this.options.isServer ?? typeof document === "undefined";
|
|
72
|
-
|
|
73
|
-
this.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
])
|
|
77
|
-
) : void 0;
|
|
72
|
+
if (this.options.pathParamsAllowedCharacters)
|
|
73
|
+
this.pathParamsDecoder = compileDecodeCharMap(
|
|
74
|
+
this.options.pathParamsAllowedCharacters
|
|
75
|
+
);
|
|
78
76
|
if (!this.history || this.options.history && this.options.history !== this.history) {
|
|
79
77
|
if (!this.options.history) {
|
|
80
78
|
if (!this.isServer) {
|
|
@@ -97,7 +95,23 @@ class RouterCore {
|
|
|
97
95
|
}
|
|
98
96
|
if (this.options.routeTree !== this.routeTree) {
|
|
99
97
|
this.routeTree = this.options.routeTree;
|
|
100
|
-
|
|
98
|
+
let processRouteTreeResult;
|
|
99
|
+
if (this.isServer && globalThis.__TSR_CACHE__ && globalThis.__TSR_CACHE__.routeTree === this.routeTree) {
|
|
100
|
+
const cached = globalThis.__TSR_CACHE__;
|
|
101
|
+
this.resolvePathCache = cached.resolvePathCache;
|
|
102
|
+
processRouteTreeResult = cached.processRouteTreeResult;
|
|
103
|
+
} else {
|
|
104
|
+
this.resolvePathCache = createLRUCache(1e3);
|
|
105
|
+
processRouteTreeResult = this.buildRouteTree();
|
|
106
|
+
if (this.isServer && globalThis.__TSR_CACHE__ === void 0) {
|
|
107
|
+
globalThis.__TSR_CACHE__ = {
|
|
108
|
+
routeTree: this.routeTree,
|
|
109
|
+
processRouteTreeResult,
|
|
110
|
+
resolvePathCache: this.resolvePathCache
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
this.setRoutes(processRouteTreeResult);
|
|
101
115
|
}
|
|
102
116
|
if (!this.__store && this.latestLocation) {
|
|
103
117
|
this.__store = new Store(getInitialRouterState(this.latestLocation), {
|
|
@@ -155,7 +169,7 @@ class RouterCore {
|
|
|
155
169
|
);
|
|
156
170
|
};
|
|
157
171
|
this.buildRouteTree = () => {
|
|
158
|
-
const
|
|
172
|
+
const result = processRouteTree(
|
|
159
173
|
this.routeTree,
|
|
160
174
|
this.options.caseSensitive,
|
|
161
175
|
(route, i) => {
|
|
@@ -165,18 +179,9 @@ class RouterCore {
|
|
|
165
179
|
}
|
|
166
180
|
);
|
|
167
181
|
if (this.options.routeMasks) {
|
|
168
|
-
processRouteMasks(this.options.routeMasks, processedTree);
|
|
169
|
-
}
|
|
170
|
-
this.routesById = routesById;
|
|
171
|
-
this.routesByPath = routesByPath;
|
|
172
|
-
this.processedTree = processedTree;
|
|
173
|
-
const notFoundRoute = this.options.notFoundRoute;
|
|
174
|
-
if (notFoundRoute) {
|
|
175
|
-
notFoundRoute.init({
|
|
176
|
-
originalIndex: 99999999999
|
|
177
|
-
});
|
|
178
|
-
this.routesById[notFoundRoute.id] = notFoundRoute;
|
|
182
|
+
processRouteMasks(this.options.routeMasks, result.processedTree);
|
|
179
183
|
}
|
|
184
|
+
return result;
|
|
180
185
|
};
|
|
181
186
|
this.subscribe = (eventType, fn) => {
|
|
182
187
|
const listener = {
|
|
@@ -231,7 +236,6 @@ class RouterCore {
|
|
|
231
236
|
}
|
|
232
237
|
return location;
|
|
233
238
|
};
|
|
234
|
-
this.resolvePathCache = createLRUCache(1e3);
|
|
235
239
|
this.resolvePathWithBase = (from, path) => {
|
|
236
240
|
const resolvedPath = resolvePath({
|
|
237
241
|
base: from,
|
|
@@ -286,26 +290,23 @@ class RouterCore {
|
|
|
286
290
|
this.buildLocation = (opts) => {
|
|
287
291
|
const build = (dest = {}) => {
|
|
288
292
|
const currentLocation = dest._fromLocation || this.pendingBuiltLocation || this.latestLocation;
|
|
289
|
-
const
|
|
290
|
-
_buildLocation: true
|
|
291
|
-
});
|
|
292
|
-
const lastMatch = last(allCurrentLocationMatches);
|
|
293
|
+
const lightweightResult = this.matchRoutesLightweight(currentLocation);
|
|
293
294
|
if (dest.from && process.env.NODE_ENV !== "production" && dest._isNavigate) {
|
|
294
295
|
const allFromMatches = this.getMatchedRoutes(dest.from).matchedRoutes;
|
|
295
|
-
const matchedFrom = findLast(
|
|
296
|
+
const matchedFrom = findLast(lightweightResult.matchedRoutes, (d) => {
|
|
296
297
|
return comparePaths(d.fullPath, dest.from);
|
|
297
298
|
});
|
|
298
299
|
const matchedCurrent = findLast(allFromMatches, (d) => {
|
|
299
|
-
return comparePaths(d.fullPath,
|
|
300
|
+
return comparePaths(d.fullPath, lightweightResult.fullPath);
|
|
300
301
|
});
|
|
301
302
|
if (!matchedFrom && !matchedCurrent) {
|
|
302
303
|
console.warn(`Could not find match for from: ${dest.from}`);
|
|
303
304
|
}
|
|
304
305
|
}
|
|
305
|
-
const defaultedFromPath = dest.unsafeRelative === "path" ? currentLocation.pathname : dest.from ??
|
|
306
|
+
const defaultedFromPath = dest.unsafeRelative === "path" ? currentLocation.pathname : dest.from ?? lightweightResult.fullPath;
|
|
306
307
|
const fromPath = this.resolvePathWithBase(defaultedFromPath, ".");
|
|
307
|
-
const fromSearch =
|
|
308
|
-
const fromParams = { ...
|
|
308
|
+
const fromSearch = lightweightResult.search;
|
|
309
|
+
const fromParams = { ...lightweightResult.params };
|
|
309
310
|
const nextTo = dest.to ? this.resolvePathWithBase(fromPath, `${dest.to}`) : this.resolvePathWithBase(fromPath, ".");
|
|
310
311
|
const nextParams = dest.params === false || dest.params === null ? {} : (dest.params ?? true) === true ? fromParams : Object.assign(
|
|
311
312
|
fromParams,
|
|
@@ -344,7 +345,7 @@ class RouterCore {
|
|
|
344
345
|
interpolatePath({
|
|
345
346
|
path: nextTo,
|
|
346
347
|
params: nextParams,
|
|
347
|
-
|
|
348
|
+
decoder: this.pathParamsDecoder
|
|
348
349
|
}).interpolatedPath
|
|
349
350
|
);
|
|
350
351
|
let nextSearch = fromSearch;
|
|
@@ -1034,6 +1035,22 @@ class RouterCore {
|
|
|
1034
1035
|
get state() {
|
|
1035
1036
|
return this.__store.state;
|
|
1036
1037
|
}
|
|
1038
|
+
setRoutes({
|
|
1039
|
+
routesById,
|
|
1040
|
+
routesByPath,
|
|
1041
|
+
processedTree
|
|
1042
|
+
}) {
|
|
1043
|
+
this.routesById = routesById;
|
|
1044
|
+
this.routesByPath = routesByPath;
|
|
1045
|
+
this.processedTree = processedTree;
|
|
1046
|
+
const notFoundRoute = this.options.notFoundRoute;
|
|
1047
|
+
if (notFoundRoute) {
|
|
1048
|
+
notFoundRoute.init({
|
|
1049
|
+
originalIndex: 99999999999
|
|
1050
|
+
});
|
|
1051
|
+
this.routesById[notFoundRoute.id] = notFoundRoute;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1037
1054
|
get looseRoutesById() {
|
|
1038
1055
|
return this.routesById;
|
|
1039
1056
|
}
|
|
@@ -1097,7 +1114,7 @@ class RouterCore {
|
|
|
1097
1114
|
const { interpolatedPath, usedParams } = interpolatePath({
|
|
1098
1115
|
path: route.fullPath,
|
|
1099
1116
|
params: routeParams,
|
|
1100
|
-
|
|
1117
|
+
decoder: this.pathParamsDecoder
|
|
1101
1118
|
});
|
|
1102
1119
|
const matchId = (
|
|
1103
1120
|
// route.id for disambiguation
|
|
@@ -1112,32 +1129,18 @@ class RouterCore {
|
|
|
1112
1129
|
const strictParams = existingMatch?._strictParams ?? usedParams;
|
|
1113
1130
|
let paramsError = void 0;
|
|
1114
1131
|
if (!existingMatch) {
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1132
|
+
try {
|
|
1133
|
+
extractStrictParams(route, usedParams, parsedParams, strictParams);
|
|
1134
|
+
} catch (err) {
|
|
1135
|
+
if (isNotFound(err) || isRedirect(err)) {
|
|
1136
|
+
paramsError = err;
|
|
1137
|
+
} else {
|
|
1138
|
+
paramsError = new PathParamError(err.message, {
|
|
1139
|
+
cause: err
|
|
1140
|
+
});
|
|
1120
1141
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
if (strictParseParams) {
|
|
1124
|
-
try {
|
|
1125
|
-
Object.assign(
|
|
1126
|
-
strictParams,
|
|
1127
|
-
strictParseParams(strictParams)
|
|
1128
|
-
);
|
|
1129
|
-
} catch (err) {
|
|
1130
|
-
if (isNotFound(err) || isRedirect(err)) {
|
|
1131
|
-
paramsError = err;
|
|
1132
|
-
} else {
|
|
1133
|
-
paramsError = new PathParamError(err.message, {
|
|
1134
|
-
cause: err
|
|
1135
|
-
});
|
|
1136
|
-
}
|
|
1137
|
-
if (opts?.throwOnError) {
|
|
1138
|
-
throw paramsError;
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1142
|
+
if (opts?.throwOnError) {
|
|
1143
|
+
throw paramsError;
|
|
1141
1144
|
}
|
|
1142
1145
|
}
|
|
1143
1146
|
}
|
|
@@ -1206,7 +1209,7 @@ class RouterCore {
|
|
|
1206
1209
|
matches.forEach((match, index) => {
|
|
1207
1210
|
const route = this.looseRoutesById[match.routeId];
|
|
1208
1211
|
const existingMatch = this.getMatch(match.id);
|
|
1209
|
-
if (!existingMatch
|
|
1212
|
+
if (!existingMatch) {
|
|
1210
1213
|
const parentMatch = matches[index - 1];
|
|
1211
1214
|
const parentContext = getParentContext(parentMatch);
|
|
1212
1215
|
if (route.options.context) {
|
|
@@ -1233,6 +1236,53 @@ class RouterCore {
|
|
|
1233
1236
|
});
|
|
1234
1237
|
return matches;
|
|
1235
1238
|
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Lightweight route matching for buildLocation.
|
|
1241
|
+
* Only computes fullPath, accumulated search, and params - skipping expensive
|
|
1242
|
+
* operations like AbortController, ControlledPromise, loaderDeps, and full match objects.
|
|
1243
|
+
*/
|
|
1244
|
+
matchRoutesLightweight(location) {
|
|
1245
|
+
const { matchedRoutes, routeParams, parsedParams } = this.getMatchedRoutes(
|
|
1246
|
+
location.pathname
|
|
1247
|
+
);
|
|
1248
|
+
const lastRoute = last(matchedRoutes);
|
|
1249
|
+
const accumulatedSearch = { ...location.search };
|
|
1250
|
+
for (const route of matchedRoutes) {
|
|
1251
|
+
try {
|
|
1252
|
+
Object.assign(
|
|
1253
|
+
accumulatedSearch,
|
|
1254
|
+
validateSearch(route.options.validateSearch, accumulatedSearch)
|
|
1255
|
+
);
|
|
1256
|
+
} catch {
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
const lastStateMatch = last(this.state.matches);
|
|
1260
|
+
const canReuseParams = lastStateMatch && lastStateMatch.routeId === lastRoute.id && location.pathname === this.state.location.pathname;
|
|
1261
|
+
let params;
|
|
1262
|
+
if (canReuseParams) {
|
|
1263
|
+
params = lastStateMatch.params;
|
|
1264
|
+
} else {
|
|
1265
|
+
const strictParams = { ...routeParams };
|
|
1266
|
+
for (const route of matchedRoutes) {
|
|
1267
|
+
try {
|
|
1268
|
+
extractStrictParams(
|
|
1269
|
+
route,
|
|
1270
|
+
routeParams,
|
|
1271
|
+
parsedParams ?? {},
|
|
1272
|
+
strictParams
|
|
1273
|
+
);
|
|
1274
|
+
} catch {
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
params = strictParams;
|
|
1278
|
+
}
|
|
1279
|
+
return {
|
|
1280
|
+
matchedRoutes,
|
|
1281
|
+
fullPath: lastRoute.fullPath,
|
|
1282
|
+
search: accumulatedSearch,
|
|
1283
|
+
params
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1236
1286
|
}
|
|
1237
1287
|
class SearchParamError extends Error {
|
|
1238
1288
|
}
|
|
@@ -1395,6 +1445,21 @@ function findGlobalNotFoundRouteId(notFoundMode, routes) {
|
|
|
1395
1445
|
}
|
|
1396
1446
|
return rootRouteId;
|
|
1397
1447
|
}
|
|
1448
|
+
function extractStrictParams(route, referenceParams, parsedParams, accumulatedParams) {
|
|
1449
|
+
const parseParams = route.options.params?.parse ?? route.options.parseParams;
|
|
1450
|
+
if (parseParams) {
|
|
1451
|
+
if (route.options.skipRouteOnParseError) {
|
|
1452
|
+
for (const key in referenceParams) {
|
|
1453
|
+
if (key in parsedParams) {
|
|
1454
|
+
accumulatedParams[key] = parsedParams[key];
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
} else {
|
|
1458
|
+
const result = parseParams(accumulatedParams);
|
|
1459
|
+
Object.assign(accumulatedParams, result);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1398
1463
|
export {
|
|
1399
1464
|
PathParamError,
|
|
1400
1465
|
RouterCore,
|