@tanstack/router-core 1.133.25 → 1.133.27
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/path.cjs +15 -21
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/path.d.cts +1 -3
- package/dist/cjs/process-route-tree.cjs +1 -1
- package/dist/cjs/process-route-tree.cjs.map +1 -1
- package/dist/esm/path.d.ts +1 -3
- package/dist/esm/path.js +15 -21
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/process-route-tree.js +2 -2
- package/dist/esm/process-route-tree.js.map +1 -1
- package/package.json +1 -1
- package/src/path.ts +16 -37
- package/src/process-route-tree.ts +2 -2
package/dist/cjs/path.cjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const root = require("./root.cjs");
|
|
4
3
|
const utils = require("./utils.cjs");
|
|
5
4
|
const SEGMENT_TYPE_PATHNAME = 0;
|
|
6
5
|
const SEGMENT_TYPE_PARAM = 1;
|
|
@@ -78,8 +77,8 @@ function resolvePath({
|
|
|
78
77
|
trailingSlash = "never",
|
|
79
78
|
parseCache
|
|
80
79
|
}) {
|
|
81
|
-
let baseSegments =
|
|
82
|
-
const toSegments =
|
|
80
|
+
let baseSegments = parsePathname(base, parseCache).slice();
|
|
81
|
+
const toSegments = parsePathname(to, parseCache);
|
|
83
82
|
if (baseSegments.length > 1 && utils.last(baseSegments)?.value === "/") {
|
|
84
83
|
baseSegments.pop();
|
|
85
84
|
}
|
|
@@ -112,13 +111,11 @@ function resolvePath({
|
|
|
112
111
|
const joined = joinPaths(segmentValues);
|
|
113
112
|
return joined;
|
|
114
113
|
}
|
|
115
|
-
const
|
|
116
|
-
const parseRoutePathSegments = (pathname, cache) => parsePathname(pathname, cache, false);
|
|
117
|
-
const parsePathname = (pathname, cache, basePathValues) => {
|
|
114
|
+
const parsePathname = (pathname, cache) => {
|
|
118
115
|
if (!pathname) return [];
|
|
119
116
|
const cached = cache?.get(pathname);
|
|
120
117
|
if (cached) return cached;
|
|
121
|
-
const parsed = baseParsePathname(pathname
|
|
118
|
+
const parsed = baseParsePathname(pathname);
|
|
122
119
|
cache?.set(pathname, parsed);
|
|
123
120
|
return parsed;
|
|
124
121
|
};
|
|
@@ -127,7 +124,7 @@ const PARAM_W_CURLY_BRACES_RE = /^(.*?)\{(\$[a-zA-Z_$][a-zA-Z0-9_$]*)\}(.*)$/;
|
|
|
127
124
|
const OPTIONAL_PARAM_W_CURLY_BRACES_RE = /^(.*?)\{-(\$[a-zA-Z_$][a-zA-Z0-9_$]*)\}(.*)$/;
|
|
128
125
|
const WILDCARD_RE = /^\$$/;
|
|
129
126
|
const WILDCARD_W_CURLY_BRACES_RE = /^(.*?)\{\$\}(.*)$/;
|
|
130
|
-
function baseParsePathname(pathname
|
|
127
|
+
function baseParsePathname(pathname) {
|
|
131
128
|
pathname = cleanPath(pathname);
|
|
132
129
|
const segments = [];
|
|
133
130
|
if (pathname.slice(0, 1) === "/") {
|
|
@@ -143,8 +140,7 @@ function baseParsePathname(pathname, basePathValues) {
|
|
|
143
140
|
const split = pathname.split("/").filter(Boolean);
|
|
144
141
|
segments.push(
|
|
145
142
|
...split.map((part) => {
|
|
146
|
-
const
|
|
147
|
-
const wildcardBracesMatch = partToMatch.match(WILDCARD_W_CURLY_BRACES_RE);
|
|
143
|
+
const wildcardBracesMatch = part.match(WILDCARD_W_CURLY_BRACES_RE);
|
|
148
144
|
if (wildcardBracesMatch) {
|
|
149
145
|
const prefix = wildcardBracesMatch[1];
|
|
150
146
|
const suffix = wildcardBracesMatch[2];
|
|
@@ -155,7 +151,7 @@ function baseParsePathname(pathname, basePathValues) {
|
|
|
155
151
|
suffixSegment: suffix || void 0
|
|
156
152
|
};
|
|
157
153
|
}
|
|
158
|
-
const optionalParamBracesMatch =
|
|
154
|
+
const optionalParamBracesMatch = part.match(
|
|
159
155
|
OPTIONAL_PARAM_W_CURLY_BRACES_RE
|
|
160
156
|
);
|
|
161
157
|
if (optionalParamBracesMatch) {
|
|
@@ -170,7 +166,7 @@ function baseParsePathname(pathname, basePathValues) {
|
|
|
170
166
|
suffixSegment: suffix || void 0
|
|
171
167
|
};
|
|
172
168
|
}
|
|
173
|
-
const paramBracesMatch =
|
|
169
|
+
const paramBracesMatch = part.match(PARAM_W_CURLY_BRACES_RE);
|
|
174
170
|
if (paramBracesMatch) {
|
|
175
171
|
const prefix = paramBracesMatch[1];
|
|
176
172
|
const paramName = paramBracesMatch[2];
|
|
@@ -182,8 +178,8 @@ function baseParsePathname(pathname, basePathValues) {
|
|
|
182
178
|
suffixSegment: suffix || void 0
|
|
183
179
|
};
|
|
184
180
|
}
|
|
185
|
-
if (PARAM_RE.test(
|
|
186
|
-
const paramName =
|
|
181
|
+
if (PARAM_RE.test(part)) {
|
|
182
|
+
const paramName = part.substring(1);
|
|
187
183
|
return {
|
|
188
184
|
type: SEGMENT_TYPE_PARAM,
|
|
189
185
|
value: "$" + paramName,
|
|
@@ -191,7 +187,7 @@ function baseParsePathname(pathname, basePathValues) {
|
|
|
191
187
|
suffixSegment: void 0
|
|
192
188
|
};
|
|
193
189
|
}
|
|
194
|
-
if (WILDCARD_RE.test(
|
|
190
|
+
if (WILDCARD_RE.test(part)) {
|
|
195
191
|
return {
|
|
196
192
|
type: SEGMENT_TYPE_WILDCARD,
|
|
197
193
|
value: "$",
|
|
@@ -201,7 +197,7 @@ function baseParsePathname(pathname, basePathValues) {
|
|
|
201
197
|
}
|
|
202
198
|
return {
|
|
203
199
|
type: SEGMENT_TYPE_PATHNAME,
|
|
204
|
-
value:
|
|
200
|
+
value: part.includes("%25") ? part.split("%25").map((segment) => decodeURI(segment)).join("%25") : decodeURI(part)
|
|
205
201
|
};
|
|
206
202
|
})
|
|
207
203
|
);
|
|
@@ -222,7 +218,7 @@ function interpolatePath({
|
|
|
222
218
|
decodeCharMap,
|
|
223
219
|
parseCache
|
|
224
220
|
}) {
|
|
225
|
-
const interpolatedPathSegments =
|
|
221
|
+
const interpolatedPathSegments = parsePathname(path, parseCache);
|
|
226
222
|
function encodeParam(key) {
|
|
227
223
|
const value = params[key];
|
|
228
224
|
const isValueString = typeof value === "string";
|
|
@@ -324,11 +320,11 @@ function matchByPath(from, {
|
|
|
324
320
|
caseSensitive
|
|
325
321
|
}, parseCache) {
|
|
326
322
|
const stringTo = to;
|
|
327
|
-
const baseSegments =
|
|
323
|
+
const baseSegments = parsePathname(
|
|
328
324
|
from.startsWith("/") ? from : `/${from}`,
|
|
329
325
|
parseCache
|
|
330
326
|
);
|
|
331
|
-
const routeSegments =
|
|
327
|
+
const routeSegments = parsePathname(
|
|
332
328
|
stringTo.startsWith("/") ? stringTo : `/${stringTo}`,
|
|
333
329
|
parseCache
|
|
334
330
|
);
|
|
@@ -533,9 +529,7 @@ exports.interpolatePath = interpolatePath;
|
|
|
533
529
|
exports.joinPaths = joinPaths;
|
|
534
530
|
exports.matchByPath = matchByPath;
|
|
535
531
|
exports.matchPathname = matchPathname;
|
|
536
|
-
exports.parseBasePathSegments = parseBasePathSegments;
|
|
537
532
|
exports.parsePathname = parsePathname;
|
|
538
|
-
exports.parseRoutePathSegments = parseRoutePathSegments;
|
|
539
533
|
exports.removeTrailingSlash = removeTrailingSlash;
|
|
540
534
|
exports.resolvePath = resolvePath;
|
|
541
535
|
exports.trimPath = trimPath;
|
package/dist/cjs/path.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.cjs","sources":["../../src/path.ts"],"sourcesContent":["import { rootRouteId } from './root'\nimport { last } from './utils'\nimport type { LRUCache } from './lru-cache'\nimport type { MatchLocation } from './RouterProvider'\nimport type { AnyPathParams } from './route'\n\nexport const SEGMENT_TYPE_PATHNAME = 0\nexport const SEGMENT_TYPE_PARAM = 1\nexport const SEGMENT_TYPE_WILDCARD = 2\nexport const SEGMENT_TYPE_OPTIONAL_PARAM = 3\n\nexport interface Segment {\n readonly type:\n | typeof SEGMENT_TYPE_PATHNAME\n | typeof SEGMENT_TYPE_PARAM\n | typeof SEGMENT_TYPE_WILDCARD\n | typeof SEGMENT_TYPE_OPTIONAL_PARAM\n readonly value: string\n readonly prefixSegment?: string\n readonly suffixSegment?: string\n // Indicates if there is a static segment after this required/optional param\n readonly hasStaticAfter?: boolean\n}\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 return path === '/' ? path : path.replace(/\\/{1,}$/, '')\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 parseCache?: ParsePathnameCache\n}\n\nfunction segmentToString(segment: Segment): string {\n const { type, value } = segment\n if (type === SEGMENT_TYPE_PATHNAME) {\n return value\n }\n\n const { prefixSegment, suffixSegment } = segment\n\n if (type === SEGMENT_TYPE_PARAM) {\n const param = value.substring(1)\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{$${param}}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{$${param}}`\n } else if (suffixSegment) {\n return `{$${param}}${suffixSegment}`\n }\n }\n\n if (type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const param = value.substring(1)\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{-$${param}}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{-$${param}}`\n } else if (suffixSegment) {\n return `{-$${param}}${suffixSegment}`\n }\n return `{-$${param}}`\n }\n\n if (type === SEGMENT_TYPE_WILDCARD) {\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{$}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{$}`\n } else if (suffixSegment) {\n return `{$}${suffixSegment}`\n }\n }\n\n // This case should never happen, should we throw instead?\n return value\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 parseCache,\n}: ResolvePathOptions) {\n let baseSegments = parseBasePathSegments(base, parseCache).slice()\n const toSegments = parseRoutePathSegments(to, parseCache)\n\n if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {\n baseSegments.pop()\n }\n\n for (let index = 0, length = toSegments.length; index < length; index++) {\n const toSegment = toSegments[index]!\n const value = toSegment.value\n if (value === '/') {\n if (!index) {\n // Leading slash\n baseSegments = [toSegment]\n } else if (index === length - 1) {\n // Trailing Slash\n baseSegments.push(toSegment)\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(toSegment)\n }\n }\n\n if (baseSegments.length > 1) {\n if (last(baseSegments)!.value === '/') {\n if (trailingSlash === 'never') {\n baseSegments.pop()\n }\n } else if (trailingSlash === 'always') {\n baseSegments.push({ type: SEGMENT_TYPE_PATHNAME, value: '/' })\n }\n }\n\n const segmentValues = baseSegments.map(segmentToString)\n // const joined = joinPaths([basepath, ...segmentValues])\n const joined = joinPaths(segmentValues)\n return joined\n}\n\nexport type ParsePathnameCache = LRUCache<string, ReadonlyArray<Segment>>\n\nexport const parseBasePathSegments = (\n pathname?: string,\n cache?: ParsePathnameCache,\n): ReadonlyArray<Segment> => parsePathname(pathname, cache, true)\n\nexport const parseRoutePathSegments = (\n pathname?: string,\n cache?: ParsePathnameCache,\n): ReadonlyArray<Segment> => parsePathname(pathname, cache, false)\n\n/**\n * Parse a pathname into an array of typed segments used by the router's\n * matcher. Results are optionally cached via an LRU cache.\n */\nexport const parsePathname = (\n pathname?: string,\n cache?: ParsePathnameCache,\n basePathValues?: boolean,\n): ReadonlyArray<Segment> => {\n if (!pathname) return []\n const cached = cache?.get(pathname)\n if (cached) return cached\n const parsed = baseParsePathname(pathname, basePathValues)\n cache?.set(pathname, parsed)\n return parsed\n}\n\nconst PARAM_RE = /^\\$.{1,}$/ // $paramName\nconst PARAM_W_CURLY_BRACES_RE = /^(.*?)\\{(\\$[a-zA-Z_$][a-zA-Z0-9_$]*)\\}(.*)$/ // prefix{$paramName}suffix\nconst OPTIONAL_PARAM_W_CURLY_BRACES_RE =\n /^(.*?)\\{-(\\$[a-zA-Z_$][a-zA-Z0-9_$]*)\\}(.*)$/ // prefix{-$paramName}suffix\n\nconst WILDCARD_RE = /^\\$$/ // $\nconst WILDCARD_W_CURLY_BRACES_RE = /^(.*?)\\{\\$\\}(.*)$/ // prefix{$}suffix\n\n/**\n * Required: `/foo/$bar` ✅\n * Prefix and Suffix: `/foo/prefix${bar}suffix` ✅\n * Wildcard: `/foo/$` ✅\n * Wildcard with Prefix and Suffix: `/foo/prefix{$}suffix` ✅\n *\n * Optional param: `/foo/{-$bar}`\n * Optional param with Prefix and Suffix: `/foo/prefix{-$bar}suffix`\n\n * Future:\n * Optional named segment: `/foo/{bar}`\n * Optional named segment with Prefix and Suffix: `/foo/prefix{-bar}suffix`\n * Escape special characters:\n * - `/foo/[$]` - Static route\n * - `/foo/[$]{$foo} - Dynamic route with a static prefix of `$`\n * - `/foo/{$foo}[$]` - Dynamic route with a static suffix of `$`\n */\nfunction baseParsePathname(\n pathname: string,\n basePathValues?: boolean,\n): ReadonlyArray<Segment> {\n pathname = cleanPath(pathname)\n\n const segments: Array<Segment> = []\n\n if (pathname.slice(0, 1) === '/') {\n pathname = pathname.substring(1)\n segments.push({\n type: SEGMENT_TYPE_PATHNAME,\n value: '/',\n })\n }\n\n if (!pathname) {\n return segments\n }\n\n // Remove empty segments and '.' segments\n const split = pathname.split('/').filter(Boolean)\n\n segments.push(\n ...split.map((part): Segment => {\n // strip tailing underscore for non-nested paths\n const partToMatch =\n !basePathValues && part !== rootRouteId && part.slice(-1) === '_'\n ? part.slice(0, -1)\n : part\n\n // Check for wildcard with curly braces: prefix{$}suffix\n const wildcardBracesMatch = partToMatch.match(WILDCARD_W_CURLY_BRACES_RE)\n if (wildcardBracesMatch) {\n const prefix = wildcardBracesMatch[1]\n const suffix = wildcardBracesMatch[2]\n return {\n type: SEGMENT_TYPE_WILDCARD,\n value: '$',\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for optional parameter format: prefix{-$paramName}suffix\n const optionalParamBracesMatch = partToMatch.match(\n OPTIONAL_PARAM_W_CURLY_BRACES_RE,\n )\n if (optionalParamBracesMatch) {\n const prefix = optionalParamBracesMatch[1]\n const paramName = optionalParamBracesMatch[2]!\n const suffix = optionalParamBracesMatch[3]\n return {\n type: SEGMENT_TYPE_OPTIONAL_PARAM,\n value: paramName, // Now just $paramName (no prefix)\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for the new parameter format: prefix{$paramName}suffix\n const paramBracesMatch = partToMatch.match(PARAM_W_CURLY_BRACES_RE)\n if (paramBracesMatch) {\n const prefix = paramBracesMatch[1]\n const paramName = paramBracesMatch[2]\n const suffix = paramBracesMatch[3]\n return {\n type: SEGMENT_TYPE_PARAM,\n value: '' + paramName,\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for bare parameter format: $paramName (without curly braces)\n if (PARAM_RE.test(partToMatch)) {\n const paramName = partToMatch.substring(1)\n return {\n type: SEGMENT_TYPE_PARAM,\n value: '$' + paramName,\n prefixSegment: undefined,\n suffixSegment: undefined,\n }\n }\n\n // Check for bare wildcard: $ (without curly braces)\n if (WILDCARD_RE.test(partToMatch)) {\n return {\n type: SEGMENT_TYPE_WILDCARD,\n value: '$',\n prefixSegment: undefined,\n suffixSegment: undefined,\n }\n }\n\n // Handle regular pathname segment\n return {\n type: SEGMENT_TYPE_PATHNAME,\n value: partToMatch.includes('%25')\n ? partToMatch\n .split('%25')\n .map((segment) => decodeURI(segment))\n .join('%25')\n : decodeURI(partToMatch),\n }\n }),\n )\n\n if (pathname.slice(-1) === '/') {\n pathname = pathname.substring(1)\n segments.push({\n type: SEGMENT_TYPE_PATHNAME,\n value: '/',\n })\n }\n\n return segments\n}\n\ninterface InterpolatePathOptions {\n path?: string\n params: Record<string, unknown>\n leaveWildcards?: boolean\n leaveParams?: boolean\n // Map of encoded chars to decoded chars (e.g. '%40' -> '@') that should remain decoded in path params\n decodeCharMap?: Map<string, string>\n parseCache?: ParsePathnameCache\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/**\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 * - Optionally leaves placeholders or wildcards in place\n */\nexport function interpolatePath({\n path,\n params,\n leaveWildcards,\n leaveParams,\n decodeCharMap,\n parseCache,\n}: InterpolatePathOptions): InterPolatePathResult {\n const interpolatedPathSegments = parseRoutePathSegments(path, parseCache)\n\n function encodeParam(key: string): any {\n const value = params[key]\n const isValueString = typeof value === 'string'\n\n if (key === '*' || key === '_splat') {\n // the splat/catch-all routes shouldn't have the '/' encoded out\n return isValueString ? encodeURI(value) : value\n } else {\n return isValueString ? encodePathParam(value, decodeCharMap) : value\n }\n }\n\n // Tracking if any params are missing in the `params` object\n // when interpolating the path\n let isMissingParams = false\n\n const usedParams: Record<string, unknown> = {}\n const interpolatedPath = joinPaths(\n interpolatedPathSegments.map((segment) => {\n if (segment.type === SEGMENT_TYPE_PATHNAME) {\n return segment.value\n }\n\n if (segment.type === SEGMENT_TYPE_WILDCARD) {\n usedParams._splat = params._splat\n\n // TODO: Deprecate *\n usedParams['*'] = params._splat\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\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 (!params._splat) {\n isMissingParams = true\n // For missing splat parameters, just return the prefix and suffix without the wildcard\n if (leaveWildcards) {\n return `${segmentPrefix}${segment.value}${segmentSuffix}`\n }\n // If there is a prefix or suffix, return them joined, otherwise omit the segment\n if (segmentPrefix || segmentSuffix) {\n return `${segmentPrefix}${segmentSuffix}`\n }\n return undefined\n }\n\n const value = encodeParam('_splat')\n if (leaveWildcards) {\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${value}${segmentSuffix}`\n }\n\n if (segment.type === SEGMENT_TYPE_PARAM) {\n const key = segment.value.substring(1)\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\n if (leaveParams) {\n const value = encodeParam(segment.value)\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${encodeParam(key) ?? 'undefined'}${segmentSuffix}`\n }\n\n if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const key = segment.value.substring(1)\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\n\n // Check if optional parameter is missing or undefined\n if (!(key in params) || params[key] == null) {\n if (leaveWildcards) {\n return `${segmentPrefix}${key}${segmentSuffix}`\n }\n // For optional params with prefix/suffix, keep the prefix/suffix but omit the param\n if (segmentPrefix || segmentSuffix) {\n return `${segmentPrefix}${segmentSuffix}`\n }\n // If no prefix/suffix, omit the entire segment\n return undefined\n }\n\n usedParams[key] = params[key]\n\n if (leaveParams) {\n const value = encodeParam(segment.value)\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n if (leaveWildcards) {\n return `${segmentPrefix}${key}${encodeParam(key) ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${encodeParam(key) ?? ''}${segmentSuffix}`\n }\n\n return segment.value\n }),\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\n/**\n * Match a pathname against a route destination and return extracted params\n * or `undefined`. Uses the same parsing as the router for consistency.\n */\nexport function matchPathname(\n currentPathname: string,\n matchLocation: Pick<MatchLocation, 'to' | 'fuzzy' | 'caseSensitive'>,\n parseCache?: ParsePathnameCache,\n): AnyPathParams | undefined {\n const pathParams = matchByPath(currentPathname, matchLocation, parseCache)\n // const searchMatched = matchBySearch(location.search, matchLocation)\n\n if (matchLocation.to && !pathParams) {\n return\n }\n\n return pathParams ?? {}\n}\n\n/** Low-level matcher that compares two path strings and extracts params. */\nexport function matchByPath(\n from: string,\n {\n to,\n fuzzy,\n caseSensitive,\n }: Pick<MatchLocation, 'to' | 'caseSensitive' | 'fuzzy'>,\n parseCache?: ParsePathnameCache,\n): Record<string, string> | undefined {\n const stringTo = to as string\n\n // Parse the from and to\n const baseSegments = parseBasePathSegments(\n from.startsWith('/') ? from : `/${from}`,\n parseCache,\n )\n const routeSegments = parseRoutePathSegments(\n stringTo.startsWith('/') ? stringTo : `/${stringTo}`,\n parseCache,\n )\n\n const params: Record<string, string> = {}\n\n const result = isMatch(\n baseSegments,\n routeSegments,\n params,\n fuzzy,\n caseSensitive,\n )\n\n return result ? params : undefined\n}\n\nfunction isMatch(\n baseSegments: ReadonlyArray<Segment>,\n routeSegments: ReadonlyArray<Segment>,\n params: Record<string, string>,\n fuzzy?: boolean,\n caseSensitive?: boolean,\n): boolean {\n let baseIndex = 0\n let routeIndex = 0\n\n while (baseIndex < baseSegments.length || routeIndex < routeSegments.length) {\n const baseSegment = baseSegments[baseIndex]\n const routeSegment = routeSegments[routeIndex]\n\n if (routeSegment) {\n if (routeSegment.type === SEGMENT_TYPE_WILDCARD) {\n // Capture all remaining segments for a wildcard\n const remainingBaseSegments = baseSegments.slice(baseIndex)\n\n let _splat: string\n\n // If this is a wildcard with prefix/suffix, we need to handle the first segment specially\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n if (!baseSegment) return false\n\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if ('prefixSegment' in routeSegment) {\n if (!baseValue.startsWith(prefix)) {\n return false\n }\n }\n if ('suffixSegment' in routeSegment) {\n if (\n !baseSegments[baseSegments.length - 1]?.value.endsWith(suffix)\n ) {\n return false\n }\n }\n\n let rejoinedSplat = decodeURI(\n joinPaths(remainingBaseSegments.map((d) => d.value)),\n )\n\n // Remove the prefix and suffix from the rejoined splat\n if (prefix && rejoinedSplat.startsWith(prefix)) {\n rejoinedSplat = rejoinedSplat.slice(prefix.length)\n }\n\n if (suffix && rejoinedSplat.endsWith(suffix)) {\n rejoinedSplat = rejoinedSplat.slice(\n 0,\n rejoinedSplat.length - suffix.length,\n )\n }\n\n _splat = rejoinedSplat\n } else {\n // If no prefix/suffix, just rejoin the remaining segments\n _splat = decodeURI(\n joinPaths(remainingBaseSegments.map((d) => d.value)),\n )\n }\n\n // TODO: Deprecate *\n params['*'] = _splat\n params['_splat'] = _splat\n return true\n }\n\n if (routeSegment.type === SEGMENT_TYPE_PATHNAME) {\n if (routeSegment.value === '/' && !baseSegment?.value) {\n routeIndex++\n continue\n }\n\n if (baseSegment) {\n if (caseSensitive) {\n if (routeSegment.value !== baseSegment.value) {\n return false\n }\n } else if (\n routeSegment.value.toLowerCase() !== baseSegment.value.toLowerCase()\n ) {\n return false\n }\n baseIndex++\n routeIndex++\n continue\n } else {\n return false\n }\n }\n\n if (routeSegment.type === SEGMENT_TYPE_PARAM) {\n if (!baseSegment) {\n return false\n }\n\n if (baseSegment.value === '/') {\n return false\n }\n\n let _paramValue = ''\n let matched = false\n\n // If this param has prefix/suffix, we need to extract the actual parameter value\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if (prefix && !baseValue.startsWith(prefix)) {\n return false\n }\n if (suffix && !baseValue.endsWith(suffix)) {\n return false\n }\n\n let paramValue = baseValue\n if (prefix && paramValue.startsWith(prefix)) {\n paramValue = paramValue.slice(prefix.length)\n }\n if (suffix && paramValue.endsWith(suffix)) {\n paramValue = paramValue.slice(0, paramValue.length - suffix.length)\n }\n\n _paramValue = decodeURIComponent(paramValue)\n matched = true\n } else {\n // If no prefix/suffix, just decode the base segment value\n _paramValue = decodeURIComponent(baseSegment.value)\n matched = true\n }\n\n if (matched) {\n params[routeSegment.value.substring(1)] = _paramValue\n baseIndex++\n }\n\n routeIndex++\n continue\n }\n\n if (routeSegment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n // Optional parameters can be missing - don't fail the match\n if (!baseSegment) {\n // No base segment for optional param - skip this route segment\n routeIndex++\n continue\n }\n\n if (baseSegment.value === '/') {\n // Skip slash segments for optional params\n routeIndex++\n continue\n }\n\n let _paramValue = ''\n let matched = false\n\n // If this optional param has prefix/suffix, we need to extract the actual parameter value\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if (\n (!prefix || baseValue.startsWith(prefix)) &&\n (!suffix || baseValue.endsWith(suffix))\n ) {\n let paramValue = baseValue\n if (prefix && paramValue.startsWith(prefix)) {\n paramValue = paramValue.slice(prefix.length)\n }\n if (suffix && paramValue.endsWith(suffix)) {\n paramValue = paramValue.slice(\n 0,\n paramValue.length - suffix.length,\n )\n }\n\n _paramValue = decodeURIComponent(paramValue)\n matched = true\n }\n } else {\n // For optional params without prefix/suffix, we need to check if the current\n // base segment should match this optional param or a later route segment\n\n // Look ahead to see if there's a later route segment that matches the current base segment\n let shouldMatchOptional = true\n for (\n let lookAhead = routeIndex + 1;\n lookAhead < routeSegments.length;\n lookAhead++\n ) {\n const futureRouteSegment = routeSegments[lookAhead]\n if (\n futureRouteSegment?.type === SEGMENT_TYPE_PATHNAME &&\n futureRouteSegment.value === baseSegment.value\n ) {\n // The current base segment matches a future pathname segment,\n // so we should skip this optional parameter\n shouldMatchOptional = false\n break\n }\n\n // If we encounter a required param or wildcard, stop looking ahead\n if (\n futureRouteSegment?.type === SEGMENT_TYPE_PARAM ||\n futureRouteSegment?.type === SEGMENT_TYPE_WILDCARD\n ) {\n if (baseSegments.length < routeSegments.length) {\n shouldMatchOptional = false\n }\n break\n }\n }\n\n if (shouldMatchOptional) {\n // If no prefix/suffix, just decode the base segment value\n _paramValue = decodeURIComponent(baseSegment.value)\n matched = true\n }\n }\n\n if (matched) {\n params[routeSegment.value.substring(1)] = _paramValue\n baseIndex++\n }\n\n routeIndex++\n continue\n }\n }\n\n // If we have base segments left but no route segments, it's a fuzzy match\n if (baseIndex < baseSegments.length && routeIndex >= routeSegments.length) {\n params['**'] = joinPaths(\n baseSegments.slice(baseIndex).map((d) => d.value),\n )\n return !!fuzzy && routeSegments[routeSegments.length - 1]?.value !== '/'\n }\n\n // If we have route segments left but no base segments, check if remaining are optional\n if (routeIndex < routeSegments.length && baseIndex >= baseSegments.length) {\n // Check if all remaining route segments are optional\n for (let i = routeIndex; i < routeSegments.length; i++) {\n if (routeSegments[i]?.type !== SEGMENT_TYPE_OPTIONAL_PARAM) {\n return false\n }\n }\n // All remaining are optional, so we can finish\n break\n }\n\n break\n }\n\n return true\n}\n"],"names":["last","rootRouteId"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"path.cjs","sources":["../../src/path.ts"],"sourcesContent":["import { last } from './utils'\nimport type { LRUCache } from './lru-cache'\nimport type { MatchLocation } from './RouterProvider'\nimport type { AnyPathParams } from './route'\n\nexport const SEGMENT_TYPE_PATHNAME = 0\nexport const SEGMENT_TYPE_PARAM = 1\nexport const SEGMENT_TYPE_WILDCARD = 2\nexport const SEGMENT_TYPE_OPTIONAL_PARAM = 3\n\nexport interface Segment {\n readonly type:\n | typeof SEGMENT_TYPE_PATHNAME\n | typeof SEGMENT_TYPE_PARAM\n | typeof SEGMENT_TYPE_WILDCARD\n | typeof SEGMENT_TYPE_OPTIONAL_PARAM\n readonly value: string\n readonly prefixSegment?: string\n readonly suffixSegment?: string\n // Indicates if there is a static segment after this required/optional param\n readonly hasStaticAfter?: boolean\n}\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 return path === '/' ? path : path.replace(/\\/{1,}$/, '')\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 parseCache?: ParsePathnameCache\n}\n\nfunction segmentToString(segment: Segment): string {\n const { type, value } = segment\n if (type === SEGMENT_TYPE_PATHNAME) {\n return value\n }\n\n const { prefixSegment, suffixSegment } = segment\n\n if (type === SEGMENT_TYPE_PARAM) {\n const param = value.substring(1)\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{$${param}}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{$${param}}`\n } else if (suffixSegment) {\n return `{$${param}}${suffixSegment}`\n }\n }\n\n if (type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const param = value.substring(1)\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{-$${param}}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{-$${param}}`\n } else if (suffixSegment) {\n return `{-$${param}}${suffixSegment}`\n }\n return `{-$${param}}`\n }\n\n if (type === SEGMENT_TYPE_WILDCARD) {\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{$}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{$}`\n } else if (suffixSegment) {\n return `{$}${suffixSegment}`\n }\n }\n\n // This case should never happen, should we throw instead?\n return value\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 parseCache,\n}: ResolvePathOptions) {\n let baseSegments = parsePathname(base, parseCache).slice()\n const toSegments = parsePathname(to, parseCache)\n\n if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {\n baseSegments.pop()\n }\n\n for (let index = 0, length = toSegments.length; index < length; index++) {\n const toSegment = toSegments[index]!\n const value = toSegment.value\n if (value === '/') {\n if (!index) {\n // Leading slash\n baseSegments = [toSegment]\n } else if (index === length - 1) {\n // Trailing Slash\n baseSegments.push(toSegment)\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(toSegment)\n }\n }\n\n if (baseSegments.length > 1) {\n if (last(baseSegments)!.value === '/') {\n if (trailingSlash === 'never') {\n baseSegments.pop()\n }\n } else if (trailingSlash === 'always') {\n baseSegments.push({ type: SEGMENT_TYPE_PATHNAME, value: '/' })\n }\n }\n\n const segmentValues = baseSegments.map(segmentToString)\n // const joined = joinPaths([basepath, ...segmentValues])\n const joined = joinPaths(segmentValues)\n return joined\n}\n\nexport type ParsePathnameCache = LRUCache<string, ReadonlyArray<Segment>>\n\n/**\n * Parse a pathname into an array of typed segments used by the router's\n * matcher. Results are optionally cached via an LRU cache.\n */\nexport const parsePathname = (\n pathname?: string,\n cache?: ParsePathnameCache,\n): ReadonlyArray<Segment> => {\n if (!pathname) return []\n const cached = cache?.get(pathname)\n if (cached) return cached\n const parsed = baseParsePathname(pathname)\n cache?.set(pathname, parsed)\n return parsed\n}\n\nconst PARAM_RE = /^\\$.{1,}$/ // $paramName\nconst PARAM_W_CURLY_BRACES_RE = /^(.*?)\\{(\\$[a-zA-Z_$][a-zA-Z0-9_$]*)\\}(.*)$/ // prefix{$paramName}suffix\nconst OPTIONAL_PARAM_W_CURLY_BRACES_RE =\n /^(.*?)\\{-(\\$[a-zA-Z_$][a-zA-Z0-9_$]*)\\}(.*)$/ // prefix{-$paramName}suffix\n\nconst WILDCARD_RE = /^\\$$/ // $\nconst WILDCARD_W_CURLY_BRACES_RE = /^(.*?)\\{\\$\\}(.*)$/ // prefix{$}suffix\n\n/**\n * Required: `/foo/$bar` ✅\n * Prefix and Suffix: `/foo/prefix${bar}suffix` ✅\n * Wildcard: `/foo/$` ✅\n * Wildcard with Prefix and Suffix: `/foo/prefix{$}suffix` ✅\n *\n * Optional param: `/foo/{-$bar}`\n * Optional param with Prefix and Suffix: `/foo/prefix{-$bar}suffix`\n\n * Future:\n * Optional named segment: `/foo/{bar}`\n * Optional named segment with Prefix and Suffix: `/foo/prefix{-bar}suffix`\n * Escape special characters:\n * - `/foo/[$]` - Static route\n * - `/foo/[$]{$foo} - Dynamic route with a static prefix of `$`\n * - `/foo/{$foo}[$]` - Dynamic route with a static suffix of `$`\n */\nfunction baseParsePathname(pathname: string): ReadonlyArray<Segment> {\n pathname = cleanPath(pathname)\n\n const segments: Array<Segment> = []\n\n if (pathname.slice(0, 1) === '/') {\n pathname = pathname.substring(1)\n segments.push({\n type: SEGMENT_TYPE_PATHNAME,\n value: '/',\n })\n }\n\n if (!pathname) {\n return segments\n }\n\n // Remove empty segments and '.' segments\n const split = pathname.split('/').filter(Boolean)\n\n segments.push(\n ...split.map((part): Segment => {\n // Check for wildcard with curly braces: prefix{$}suffix\n const wildcardBracesMatch = part.match(WILDCARD_W_CURLY_BRACES_RE)\n if (wildcardBracesMatch) {\n const prefix = wildcardBracesMatch[1]\n const suffix = wildcardBracesMatch[2]\n return {\n type: SEGMENT_TYPE_WILDCARD,\n value: '$',\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for optional parameter format: prefix{-$paramName}suffix\n const optionalParamBracesMatch = part.match(\n OPTIONAL_PARAM_W_CURLY_BRACES_RE,\n )\n if (optionalParamBracesMatch) {\n const prefix = optionalParamBracesMatch[1]\n const paramName = optionalParamBracesMatch[2]!\n const suffix = optionalParamBracesMatch[3]\n return {\n type: SEGMENT_TYPE_OPTIONAL_PARAM,\n value: paramName, // Now just $paramName (no prefix)\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for the new parameter format: prefix{$paramName}suffix\n const paramBracesMatch = part.match(PARAM_W_CURLY_BRACES_RE)\n if (paramBracesMatch) {\n const prefix = paramBracesMatch[1]\n const paramName = paramBracesMatch[2]\n const suffix = paramBracesMatch[3]\n return {\n type: SEGMENT_TYPE_PARAM,\n value: '' + paramName,\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for bare parameter format: $paramName (without curly braces)\n if (PARAM_RE.test(part)) {\n const paramName = part.substring(1)\n return {\n type: SEGMENT_TYPE_PARAM,\n value: '$' + paramName,\n prefixSegment: undefined,\n suffixSegment: undefined,\n }\n }\n\n // Check for bare wildcard: $ (without curly braces)\n if (WILDCARD_RE.test(part)) {\n return {\n type: SEGMENT_TYPE_WILDCARD,\n value: '$',\n prefixSegment: undefined,\n suffixSegment: undefined,\n }\n }\n\n // Handle regular pathname segment\n return {\n type: SEGMENT_TYPE_PATHNAME,\n value: part.includes('%25')\n ? part\n .split('%25')\n .map((segment) => decodeURI(segment))\n .join('%25')\n : decodeURI(part),\n }\n }),\n )\n\n if (pathname.slice(-1) === '/') {\n pathname = pathname.substring(1)\n segments.push({\n type: SEGMENT_TYPE_PATHNAME,\n value: '/',\n })\n }\n\n return segments\n}\n\ninterface InterpolatePathOptions {\n path?: string\n params: Record<string, unknown>\n leaveWildcards?: boolean\n leaveParams?: boolean\n // Map of encoded chars to decoded chars (e.g. '%40' -> '@') that should remain decoded in path params\n decodeCharMap?: Map<string, string>\n parseCache?: ParsePathnameCache\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/**\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 * - Optionally leaves placeholders or wildcards in place\n */\nexport function interpolatePath({\n path,\n params,\n leaveWildcards,\n leaveParams,\n decodeCharMap,\n parseCache,\n}: InterpolatePathOptions): InterPolatePathResult {\n const interpolatedPathSegments = parsePathname(path, parseCache)\n\n function encodeParam(key: string): any {\n const value = params[key]\n const isValueString = typeof value === 'string'\n\n if (key === '*' || key === '_splat') {\n // the splat/catch-all routes shouldn't have the '/' encoded out\n return isValueString ? encodeURI(value) : value\n } else {\n return isValueString ? encodePathParam(value, decodeCharMap) : value\n }\n }\n\n // Tracking if any params are missing in the `params` object\n // when interpolating the path\n let isMissingParams = false\n\n const usedParams: Record<string, unknown> = {}\n const interpolatedPath = joinPaths(\n interpolatedPathSegments.map((segment) => {\n if (segment.type === SEGMENT_TYPE_PATHNAME) {\n return segment.value\n }\n\n if (segment.type === SEGMENT_TYPE_WILDCARD) {\n usedParams._splat = params._splat\n\n // TODO: Deprecate *\n usedParams['*'] = params._splat\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\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 (!params._splat) {\n isMissingParams = true\n // For missing splat parameters, just return the prefix and suffix without the wildcard\n if (leaveWildcards) {\n return `${segmentPrefix}${segment.value}${segmentSuffix}`\n }\n // If there is a prefix or suffix, return them joined, otherwise omit the segment\n if (segmentPrefix || segmentSuffix) {\n return `${segmentPrefix}${segmentSuffix}`\n }\n return undefined\n }\n\n const value = encodeParam('_splat')\n if (leaveWildcards) {\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${value}${segmentSuffix}`\n }\n\n if (segment.type === SEGMENT_TYPE_PARAM) {\n const key = segment.value.substring(1)\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\n if (leaveParams) {\n const value = encodeParam(segment.value)\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${encodeParam(key) ?? 'undefined'}${segmentSuffix}`\n }\n\n if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const key = segment.value.substring(1)\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\n\n // Check if optional parameter is missing or undefined\n if (!(key in params) || params[key] == null) {\n if (leaveWildcards) {\n return `${segmentPrefix}${key}${segmentSuffix}`\n }\n // For optional params with prefix/suffix, keep the prefix/suffix but omit the param\n if (segmentPrefix || segmentSuffix) {\n return `${segmentPrefix}${segmentSuffix}`\n }\n // If no prefix/suffix, omit the entire segment\n return undefined\n }\n\n usedParams[key] = params[key]\n\n if (leaveParams) {\n const value = encodeParam(segment.value)\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n if (leaveWildcards) {\n return `${segmentPrefix}${key}${encodeParam(key) ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${encodeParam(key) ?? ''}${segmentSuffix}`\n }\n\n return segment.value\n }),\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\n/**\n * Match a pathname against a route destination and return extracted params\n * or `undefined`. Uses the same parsing as the router for consistency.\n */\nexport function matchPathname(\n currentPathname: string,\n matchLocation: Pick<MatchLocation, 'to' | 'fuzzy' | 'caseSensitive'>,\n parseCache?: ParsePathnameCache,\n): AnyPathParams | undefined {\n const pathParams = matchByPath(currentPathname, matchLocation, parseCache)\n // const searchMatched = matchBySearch(location.search, matchLocation)\n\n if (matchLocation.to && !pathParams) {\n return\n }\n\n return pathParams ?? {}\n}\n\n/** Low-level matcher that compares two path strings and extracts params. */\nexport function matchByPath(\n from: string,\n {\n to,\n fuzzy,\n caseSensitive,\n }: Pick<MatchLocation, 'to' | 'caseSensitive' | 'fuzzy'>,\n parseCache?: ParsePathnameCache,\n): Record<string, string> | undefined {\n const stringTo = to as string\n\n // Parse the from and to\n const baseSegments = parsePathname(\n from.startsWith('/') ? from : `/${from}`,\n parseCache,\n )\n const routeSegments = parsePathname(\n stringTo.startsWith('/') ? stringTo : `/${stringTo}`,\n parseCache,\n )\n\n const params: Record<string, string> = {}\n\n const result = isMatch(\n baseSegments,\n routeSegments,\n params,\n fuzzy,\n caseSensitive,\n )\n\n return result ? params : undefined\n}\n\nfunction isMatch(\n baseSegments: ReadonlyArray<Segment>,\n routeSegments: ReadonlyArray<Segment>,\n params: Record<string, string>,\n fuzzy?: boolean,\n caseSensitive?: boolean,\n): boolean {\n let baseIndex = 0\n let routeIndex = 0\n\n while (baseIndex < baseSegments.length || routeIndex < routeSegments.length) {\n const baseSegment = baseSegments[baseIndex]\n const routeSegment = routeSegments[routeIndex]\n\n if (routeSegment) {\n if (routeSegment.type === SEGMENT_TYPE_WILDCARD) {\n // Capture all remaining segments for a wildcard\n const remainingBaseSegments = baseSegments.slice(baseIndex)\n\n let _splat: string\n\n // If this is a wildcard with prefix/suffix, we need to handle the first segment specially\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n if (!baseSegment) return false\n\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if ('prefixSegment' in routeSegment) {\n if (!baseValue.startsWith(prefix)) {\n return false\n }\n }\n if ('suffixSegment' in routeSegment) {\n if (\n !baseSegments[baseSegments.length - 1]?.value.endsWith(suffix)\n ) {\n return false\n }\n }\n\n let rejoinedSplat = decodeURI(\n joinPaths(remainingBaseSegments.map((d) => d.value)),\n )\n\n // Remove the prefix and suffix from the rejoined splat\n if (prefix && rejoinedSplat.startsWith(prefix)) {\n rejoinedSplat = rejoinedSplat.slice(prefix.length)\n }\n\n if (suffix && rejoinedSplat.endsWith(suffix)) {\n rejoinedSplat = rejoinedSplat.slice(\n 0,\n rejoinedSplat.length - suffix.length,\n )\n }\n\n _splat = rejoinedSplat\n } else {\n // If no prefix/suffix, just rejoin the remaining segments\n _splat = decodeURI(\n joinPaths(remainingBaseSegments.map((d) => d.value)),\n )\n }\n\n // TODO: Deprecate *\n params['*'] = _splat\n params['_splat'] = _splat\n return true\n }\n\n if (routeSegment.type === SEGMENT_TYPE_PATHNAME) {\n if (routeSegment.value === '/' && !baseSegment?.value) {\n routeIndex++\n continue\n }\n\n if (baseSegment) {\n if (caseSensitive) {\n if (routeSegment.value !== baseSegment.value) {\n return false\n }\n } else if (\n routeSegment.value.toLowerCase() !== baseSegment.value.toLowerCase()\n ) {\n return false\n }\n baseIndex++\n routeIndex++\n continue\n } else {\n return false\n }\n }\n\n if (routeSegment.type === SEGMENT_TYPE_PARAM) {\n if (!baseSegment) {\n return false\n }\n\n if (baseSegment.value === '/') {\n return false\n }\n\n let _paramValue = ''\n let matched = false\n\n // If this param has prefix/suffix, we need to extract the actual parameter value\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if (prefix && !baseValue.startsWith(prefix)) {\n return false\n }\n if (suffix && !baseValue.endsWith(suffix)) {\n return false\n }\n\n let paramValue = baseValue\n if (prefix && paramValue.startsWith(prefix)) {\n paramValue = paramValue.slice(prefix.length)\n }\n if (suffix && paramValue.endsWith(suffix)) {\n paramValue = paramValue.slice(0, paramValue.length - suffix.length)\n }\n\n _paramValue = decodeURIComponent(paramValue)\n matched = true\n } else {\n // If no prefix/suffix, just decode the base segment value\n _paramValue = decodeURIComponent(baseSegment.value)\n matched = true\n }\n\n if (matched) {\n params[routeSegment.value.substring(1)] = _paramValue\n baseIndex++\n }\n\n routeIndex++\n continue\n }\n\n if (routeSegment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n // Optional parameters can be missing - don't fail the match\n if (!baseSegment) {\n // No base segment for optional param - skip this route segment\n routeIndex++\n continue\n }\n\n if (baseSegment.value === '/') {\n // Skip slash segments for optional params\n routeIndex++\n continue\n }\n\n let _paramValue = ''\n let matched = false\n\n // If this optional param has prefix/suffix, we need to extract the actual parameter value\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if (\n (!prefix || baseValue.startsWith(prefix)) &&\n (!suffix || baseValue.endsWith(suffix))\n ) {\n let paramValue = baseValue\n if (prefix && paramValue.startsWith(prefix)) {\n paramValue = paramValue.slice(prefix.length)\n }\n if (suffix && paramValue.endsWith(suffix)) {\n paramValue = paramValue.slice(\n 0,\n paramValue.length - suffix.length,\n )\n }\n\n _paramValue = decodeURIComponent(paramValue)\n matched = true\n }\n } else {\n // For optional params without prefix/suffix, we need to check if the current\n // base segment should match this optional param or a later route segment\n\n // Look ahead to see if there's a later route segment that matches the current base segment\n let shouldMatchOptional = true\n for (\n let lookAhead = routeIndex + 1;\n lookAhead < routeSegments.length;\n lookAhead++\n ) {\n const futureRouteSegment = routeSegments[lookAhead]\n if (\n futureRouteSegment?.type === SEGMENT_TYPE_PATHNAME &&\n futureRouteSegment.value === baseSegment.value\n ) {\n // The current base segment matches a future pathname segment,\n // so we should skip this optional parameter\n shouldMatchOptional = false\n break\n }\n\n // If we encounter a required param or wildcard, stop looking ahead\n if (\n futureRouteSegment?.type === SEGMENT_TYPE_PARAM ||\n futureRouteSegment?.type === SEGMENT_TYPE_WILDCARD\n ) {\n if (baseSegments.length < routeSegments.length) {\n shouldMatchOptional = false\n }\n break\n }\n }\n\n if (shouldMatchOptional) {\n // If no prefix/suffix, just decode the base segment value\n _paramValue = decodeURIComponent(baseSegment.value)\n matched = true\n }\n }\n\n if (matched) {\n params[routeSegment.value.substring(1)] = _paramValue\n baseIndex++\n }\n\n routeIndex++\n continue\n }\n }\n\n // If we have base segments left but no route segments, it's a fuzzy match\n if (baseIndex < baseSegments.length && routeIndex >= routeSegments.length) {\n params['**'] = joinPaths(\n baseSegments.slice(baseIndex).map((d) => d.value),\n )\n return !!fuzzy && routeSegments[routeSegments.length - 1]?.value !== '/'\n }\n\n // If we have route segments left but no base segments, check if remaining are optional\n if (routeIndex < routeSegments.length && baseIndex >= baseSegments.length) {\n // Check if all remaining route segments are optional\n for (let i = routeIndex; i < routeSegments.length; i++) {\n if (routeSegments[i]?.type !== SEGMENT_TYPE_OPTIONAL_PARAM) {\n return false\n }\n }\n // All remaining are optional, so we can finish\n break\n }\n\n break\n }\n\n return true\n}\n"],"names":["last"],"mappings":"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"}
|
package/dist/cjs/path.d.cts
CHANGED
|
@@ -41,13 +41,11 @@ interface ResolvePathOptions {
|
|
|
41
41
|
*/
|
|
42
42
|
export declare function resolvePath({ base, to, trailingSlash, parseCache, }: ResolvePathOptions): string;
|
|
43
43
|
export type ParsePathnameCache = LRUCache<string, ReadonlyArray<Segment>>;
|
|
44
|
-
export declare const parseBasePathSegments: (pathname?: string, cache?: ParsePathnameCache) => ReadonlyArray<Segment>;
|
|
45
|
-
export declare const parseRoutePathSegments: (pathname?: string, cache?: ParsePathnameCache) => ReadonlyArray<Segment>;
|
|
46
44
|
/**
|
|
47
45
|
* Parse a pathname into an array of typed segments used by the router's
|
|
48
46
|
* matcher. Results are optionally cached via an LRU cache.
|
|
49
47
|
*/
|
|
50
|
-
export declare const parsePathname: (pathname?: string, cache?: ParsePathnameCache
|
|
48
|
+
export declare const parsePathname: (pathname?: string, cache?: ParsePathnameCache) => ReadonlyArray<Segment>;
|
|
51
49
|
interface InterpolatePathOptions {
|
|
52
50
|
path?: string;
|
|
53
51
|
params: Record<string, unknown>;
|
|
@@ -32,7 +32,7 @@ function sortRoutes(routes) {
|
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
const trimmed = path.trimPathLeft(d.fullPath);
|
|
35
|
-
let parsed = path.
|
|
35
|
+
let parsed = path.parsePathname(trimmed);
|
|
36
36
|
let skip = 0;
|
|
37
37
|
while (parsed.length > skip + 1 && parsed[skip]?.value === "/") {
|
|
38
38
|
skip++;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-route-tree.cjs","sources":["../../src/process-route-tree.ts"],"sourcesContent":["import invariant from 'tiny-invariant'\nimport {\n SEGMENT_TYPE_OPTIONAL_PARAM,\n SEGMENT_TYPE_PARAM,\n SEGMENT_TYPE_PATHNAME,\n parseRoutePathSegments,\n trimPathLeft,\n trimPathRight,\n} from './path'\nimport type { Segment } from './path'\nimport type { RouteLike } from './route'\n\nconst SLASH_SCORE = 0.75\nconst STATIC_SEGMENT_SCORE = 1\nconst REQUIRED_PARAM_BASE_SCORE = 0.5\nconst OPTIONAL_PARAM_BASE_SCORE = 0.4\nconst WILDCARD_PARAM_BASE_SCORE = 0.25\nconst STATIC_AFTER_DYNAMIC_BONUS_SCORE = 0.2\nconst BOTH_PRESENCE_BASE_SCORE = 0.05\nconst PREFIX_PRESENCE_BASE_SCORE = 0.02\nconst SUFFIX_PRESENCE_BASE_SCORE = 0.01\nconst PREFIX_LENGTH_SCORE_MULTIPLIER = 0.0002\nconst SUFFIX_LENGTH_SCORE_MULTIPLIER = 0.0001\n\nfunction handleParam(segment: Segment, baseScore: number) {\n if (segment.prefixSegment && segment.suffixSegment) {\n return (\n baseScore +\n BOTH_PRESENCE_BASE_SCORE +\n PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length +\n SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length\n )\n }\n\n if (segment.prefixSegment) {\n return (\n baseScore +\n PREFIX_PRESENCE_BASE_SCORE +\n PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length\n )\n }\n\n if (segment.suffixSegment) {\n return (\n baseScore +\n SUFFIX_PRESENCE_BASE_SCORE +\n SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length\n )\n }\n\n return baseScore\n}\n\nfunction sortRoutes<TRouteLike extends RouteLike>(\n routes: ReadonlyArray<TRouteLike>,\n): Array<TRouteLike> {\n const scoredRoutes: Array<{\n child: TRouteLike\n trimmed: string\n parsed: ReadonlyArray<Segment>\n index: number\n scores: Array<number>\n hasStaticAfter: boolean\n optionalParamCount: number\n }> = []\n\n routes.forEach((d, i) => {\n if (d.isRoot || !d.path) {\n return\n }\n\n const trimmed = trimPathLeft(d.fullPath)\n let parsed = parseRoutePathSegments(trimmed)\n\n // Removes the leading slash if it is not the only remaining segment\n let skip = 0\n while (parsed.length > skip + 1 && parsed[skip]?.value === '/') {\n skip++\n }\n if (skip > 0) parsed = parsed.slice(skip)\n\n let optionalParamCount = 0\n let hasStaticAfter = false\n const scores = parsed.map((segment, index) => {\n if (segment.value === '/') {\n return SLASH_SCORE\n }\n\n if (segment.type === SEGMENT_TYPE_PATHNAME) {\n return STATIC_SEGMENT_SCORE\n }\n\n let baseScore: number | undefined = undefined\n if (segment.type === SEGMENT_TYPE_PARAM) {\n baseScore = REQUIRED_PARAM_BASE_SCORE\n } else if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n baseScore = OPTIONAL_PARAM_BASE_SCORE\n optionalParamCount++\n } else {\n baseScore = WILDCARD_PARAM_BASE_SCORE\n }\n\n // if there is any static segment (that is not an index) after a required / optional param,\n // we will boost this param so it ranks higher than a required/optional param without a static segment after it\n // JUST FOR SORTING, NOT FOR MATCHING\n for (let i = index + 1; i < parsed.length; i++) {\n const nextSegment = parsed[i]!\n if (\n nextSegment.type === SEGMENT_TYPE_PATHNAME &&\n nextSegment.value !== '/'\n ) {\n hasStaticAfter = true\n return handleParam(\n segment,\n baseScore + STATIC_AFTER_DYNAMIC_BONUS_SCORE,\n )\n }\n }\n\n return handleParam(segment, baseScore)\n })\n\n scoredRoutes.push({\n child: d,\n trimmed,\n parsed,\n index: i,\n scores,\n optionalParamCount,\n hasStaticAfter,\n })\n })\n\n const flatRoutes = scoredRoutes\n .sort((a, b) => {\n const minLength = Math.min(a.scores.length, b.scores.length)\n\n // Sort by segment-by-segment score comparison ONLY for the common prefix\n for (let i = 0; i < minLength; i++) {\n if (a.scores[i] !== b.scores[i]) {\n return b.scores[i]! - a.scores[i]!\n }\n }\n\n // If all common segments have equal scores, then consider length and specificity\n if (a.scores.length !== b.scores.length) {\n // If different number of optional parameters, fewer optional parameters wins (more specific)\n // only if both or none of the routes has static segments after the params\n if (a.optionalParamCount !== b.optionalParamCount) {\n if (a.hasStaticAfter === b.hasStaticAfter) {\n return a.optionalParamCount - b.optionalParamCount\n } else if (a.hasStaticAfter && !b.hasStaticAfter) {\n return -1\n } else if (!a.hasStaticAfter && b.hasStaticAfter) {\n return 1\n }\n }\n\n // If same number of optional parameters, longer path wins (for static segments)\n return b.scores.length - a.scores.length\n }\n\n // Sort by min available parsed value for alphabetical ordering\n for (let i = 0; i < minLength; i++) {\n if (a.parsed[i]!.value !== b.parsed[i]!.value) {\n return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1\n }\n }\n\n // Sort by original index\n return a.index - b.index\n })\n .map((d, i) => {\n d.child.rank = i\n return d.child\n })\n\n return flatRoutes\n}\n\nexport type ProcessRouteTreeResult<TRouteLike extends RouteLike> = {\n routesById: Record<string, TRouteLike>\n routesByPath: Record<string, TRouteLike>\n flatRoutes: Array<TRouteLike>\n}\n\n/**\n * Build lookup maps and a specificity-sorted flat list from a route tree.\n * Returns `routesById`, `routesByPath`, and `flatRoutes`.\n */\n/**\n * Build lookup maps and a specificity-sorted flat list from a route tree.\n * Returns `routesById`, `routesByPath`, and `flatRoutes`.\n */\nexport function processRouteTree<TRouteLike extends RouteLike>({\n routeTree,\n initRoute,\n}: {\n routeTree: TRouteLike\n initRoute?: (route: TRouteLike, index: number) => void\n}): ProcessRouteTreeResult<TRouteLike> {\n const routesById = {} as Record<string, TRouteLike>\n const routesByPath = {} as Record<string, TRouteLike>\n\n const recurseRoutes = (childRoutes: Array<TRouteLike>) => {\n childRoutes.forEach((childRoute, i) => {\n initRoute?.(childRoute, i)\n\n const existingRoute = routesById[childRoute.id]\n\n invariant(\n !existingRoute,\n `Duplicate routes found with id: ${String(childRoute.id)}`,\n )\n\n routesById[childRoute.id] = childRoute\n\n if (!childRoute.isRoot && childRoute.path) {\n const trimmedFullPath = trimPathRight(childRoute.fullPath)\n if (\n !routesByPath[trimmedFullPath] ||\n childRoute.fullPath.endsWith('/')\n ) {\n routesByPath[trimmedFullPath] = childRoute\n }\n }\n\n const children = childRoute.children as Array<TRouteLike>\n\n if (children?.length) {\n recurseRoutes(children)\n }\n })\n }\n\n recurseRoutes([routeTree])\n\n const flatRoutes = sortRoutes(Object.values(routesById))\n\n return { routesById, routesByPath, flatRoutes }\n}\n"],"names":["trimPathLeft","parseRoutePathSegments","SEGMENT_TYPE_PATHNAME","SEGMENT_TYPE_PARAM","SEGMENT_TYPE_OPTIONAL_PARAM","i","trimPathRight"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"process-route-tree.cjs","sources":["../../src/process-route-tree.ts"],"sourcesContent":["import invariant from 'tiny-invariant'\nimport {\n SEGMENT_TYPE_OPTIONAL_PARAM,\n SEGMENT_TYPE_PARAM,\n SEGMENT_TYPE_PATHNAME,\n parsePathname,\n trimPathLeft,\n trimPathRight,\n} from './path'\nimport type { Segment } from './path'\nimport type { RouteLike } from './route'\n\nconst SLASH_SCORE = 0.75\nconst STATIC_SEGMENT_SCORE = 1\nconst REQUIRED_PARAM_BASE_SCORE = 0.5\nconst OPTIONAL_PARAM_BASE_SCORE = 0.4\nconst WILDCARD_PARAM_BASE_SCORE = 0.25\nconst STATIC_AFTER_DYNAMIC_BONUS_SCORE = 0.2\nconst BOTH_PRESENCE_BASE_SCORE = 0.05\nconst PREFIX_PRESENCE_BASE_SCORE = 0.02\nconst SUFFIX_PRESENCE_BASE_SCORE = 0.01\nconst PREFIX_LENGTH_SCORE_MULTIPLIER = 0.0002\nconst SUFFIX_LENGTH_SCORE_MULTIPLIER = 0.0001\n\nfunction handleParam(segment: Segment, baseScore: number) {\n if (segment.prefixSegment && segment.suffixSegment) {\n return (\n baseScore +\n BOTH_PRESENCE_BASE_SCORE +\n PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length +\n SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length\n )\n }\n\n if (segment.prefixSegment) {\n return (\n baseScore +\n PREFIX_PRESENCE_BASE_SCORE +\n PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length\n )\n }\n\n if (segment.suffixSegment) {\n return (\n baseScore +\n SUFFIX_PRESENCE_BASE_SCORE +\n SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length\n )\n }\n\n return baseScore\n}\n\nfunction sortRoutes<TRouteLike extends RouteLike>(\n routes: ReadonlyArray<TRouteLike>,\n): Array<TRouteLike> {\n const scoredRoutes: Array<{\n child: TRouteLike\n trimmed: string\n parsed: ReadonlyArray<Segment>\n index: number\n scores: Array<number>\n hasStaticAfter: boolean\n optionalParamCount: number\n }> = []\n\n routes.forEach((d, i) => {\n if (d.isRoot || !d.path) {\n return\n }\n\n const trimmed = trimPathLeft(d.fullPath)\n let parsed = parsePathname(trimmed)\n\n // Removes the leading slash if it is not the only remaining segment\n let skip = 0\n while (parsed.length > skip + 1 && parsed[skip]?.value === '/') {\n skip++\n }\n if (skip > 0) parsed = parsed.slice(skip)\n\n let optionalParamCount = 0\n let hasStaticAfter = false\n const scores = parsed.map((segment, index) => {\n if (segment.value === '/') {\n return SLASH_SCORE\n }\n\n if (segment.type === SEGMENT_TYPE_PATHNAME) {\n return STATIC_SEGMENT_SCORE\n }\n\n let baseScore: number | undefined = undefined\n if (segment.type === SEGMENT_TYPE_PARAM) {\n baseScore = REQUIRED_PARAM_BASE_SCORE\n } else if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n baseScore = OPTIONAL_PARAM_BASE_SCORE\n optionalParamCount++\n } else {\n baseScore = WILDCARD_PARAM_BASE_SCORE\n }\n\n // if there is any static segment (that is not an index) after a required / optional param,\n // we will boost this param so it ranks higher than a required/optional param without a static segment after it\n // JUST FOR SORTING, NOT FOR MATCHING\n for (let i = index + 1; i < parsed.length; i++) {\n const nextSegment = parsed[i]!\n if (\n nextSegment.type === SEGMENT_TYPE_PATHNAME &&\n nextSegment.value !== '/'\n ) {\n hasStaticAfter = true\n return handleParam(\n segment,\n baseScore + STATIC_AFTER_DYNAMIC_BONUS_SCORE,\n )\n }\n }\n\n return handleParam(segment, baseScore)\n })\n\n scoredRoutes.push({\n child: d,\n trimmed,\n parsed,\n index: i,\n scores,\n optionalParamCount,\n hasStaticAfter,\n })\n })\n\n const flatRoutes = scoredRoutes\n .sort((a, b) => {\n const minLength = Math.min(a.scores.length, b.scores.length)\n\n // Sort by segment-by-segment score comparison ONLY for the common prefix\n for (let i = 0; i < minLength; i++) {\n if (a.scores[i] !== b.scores[i]) {\n return b.scores[i]! - a.scores[i]!\n }\n }\n\n // If all common segments have equal scores, then consider length and specificity\n if (a.scores.length !== b.scores.length) {\n // If different number of optional parameters, fewer optional parameters wins (more specific)\n // only if both or none of the routes has static segments after the params\n if (a.optionalParamCount !== b.optionalParamCount) {\n if (a.hasStaticAfter === b.hasStaticAfter) {\n return a.optionalParamCount - b.optionalParamCount\n } else if (a.hasStaticAfter && !b.hasStaticAfter) {\n return -1\n } else if (!a.hasStaticAfter && b.hasStaticAfter) {\n return 1\n }\n }\n\n // If same number of optional parameters, longer path wins (for static segments)\n return b.scores.length - a.scores.length\n }\n\n // Sort by min available parsed value for alphabetical ordering\n for (let i = 0; i < minLength; i++) {\n if (a.parsed[i]!.value !== b.parsed[i]!.value) {\n return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1\n }\n }\n\n // Sort by original index\n return a.index - b.index\n })\n .map((d, i) => {\n d.child.rank = i\n return d.child\n })\n\n return flatRoutes\n}\n\nexport type ProcessRouteTreeResult<TRouteLike extends RouteLike> = {\n routesById: Record<string, TRouteLike>\n routesByPath: Record<string, TRouteLike>\n flatRoutes: Array<TRouteLike>\n}\n\n/**\n * Build lookup maps and a specificity-sorted flat list from a route tree.\n * Returns `routesById`, `routesByPath`, and `flatRoutes`.\n */\n/**\n * Build lookup maps and a specificity-sorted flat list from a route tree.\n * Returns `routesById`, `routesByPath`, and `flatRoutes`.\n */\nexport function processRouteTree<TRouteLike extends RouteLike>({\n routeTree,\n initRoute,\n}: {\n routeTree: TRouteLike\n initRoute?: (route: TRouteLike, index: number) => void\n}): ProcessRouteTreeResult<TRouteLike> {\n const routesById = {} as Record<string, TRouteLike>\n const routesByPath = {} as Record<string, TRouteLike>\n\n const recurseRoutes = (childRoutes: Array<TRouteLike>) => {\n childRoutes.forEach((childRoute, i) => {\n initRoute?.(childRoute, i)\n\n const existingRoute = routesById[childRoute.id]\n\n invariant(\n !existingRoute,\n `Duplicate routes found with id: ${String(childRoute.id)}`,\n )\n\n routesById[childRoute.id] = childRoute\n\n if (!childRoute.isRoot && childRoute.path) {\n const trimmedFullPath = trimPathRight(childRoute.fullPath)\n if (\n !routesByPath[trimmedFullPath] ||\n childRoute.fullPath.endsWith('/')\n ) {\n routesByPath[trimmedFullPath] = childRoute\n }\n }\n\n const children = childRoute.children as Array<TRouteLike>\n\n if (children?.length) {\n recurseRoutes(children)\n }\n })\n }\n\n recurseRoutes([routeTree])\n\n const flatRoutes = sortRoutes(Object.values(routesById))\n\n return { routesById, routesByPath, flatRoutes }\n}\n"],"names":["trimPathLeft","parsePathname","SEGMENT_TYPE_PATHNAME","SEGMENT_TYPE_PARAM","SEGMENT_TYPE_OPTIONAL_PARAM","i","trimPathRight"],"mappings":"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"}
|
package/dist/esm/path.d.ts
CHANGED
|
@@ -41,13 +41,11 @@ interface ResolvePathOptions {
|
|
|
41
41
|
*/
|
|
42
42
|
export declare function resolvePath({ base, to, trailingSlash, parseCache, }: ResolvePathOptions): string;
|
|
43
43
|
export type ParsePathnameCache = LRUCache<string, ReadonlyArray<Segment>>;
|
|
44
|
-
export declare const parseBasePathSegments: (pathname?: string, cache?: ParsePathnameCache) => ReadonlyArray<Segment>;
|
|
45
|
-
export declare const parseRoutePathSegments: (pathname?: string, cache?: ParsePathnameCache) => ReadonlyArray<Segment>;
|
|
46
44
|
/**
|
|
47
45
|
* Parse a pathname into an array of typed segments used by the router's
|
|
48
46
|
* matcher. Results are optionally cached via an LRU cache.
|
|
49
47
|
*/
|
|
50
|
-
export declare const parsePathname: (pathname?: string, cache?: ParsePathnameCache
|
|
48
|
+
export declare const parsePathname: (pathname?: string, cache?: ParsePathnameCache) => ReadonlyArray<Segment>;
|
|
51
49
|
interface InterpolatePathOptions {
|
|
52
50
|
path?: string;
|
|
53
51
|
params: Record<string, unknown>;
|
package/dist/esm/path.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { rootRouteId } from "./root.js";
|
|
2
1
|
import { last } from "./utils.js";
|
|
3
2
|
const SEGMENT_TYPE_PATHNAME = 0;
|
|
4
3
|
const SEGMENT_TYPE_PARAM = 1;
|
|
@@ -76,8 +75,8 @@ function resolvePath({
|
|
|
76
75
|
trailingSlash = "never",
|
|
77
76
|
parseCache
|
|
78
77
|
}) {
|
|
79
|
-
let baseSegments =
|
|
80
|
-
const toSegments =
|
|
78
|
+
let baseSegments = parsePathname(base, parseCache).slice();
|
|
79
|
+
const toSegments = parsePathname(to, parseCache);
|
|
81
80
|
if (baseSegments.length > 1 && last(baseSegments)?.value === "/") {
|
|
82
81
|
baseSegments.pop();
|
|
83
82
|
}
|
|
@@ -110,13 +109,11 @@ function resolvePath({
|
|
|
110
109
|
const joined = joinPaths(segmentValues);
|
|
111
110
|
return joined;
|
|
112
111
|
}
|
|
113
|
-
const
|
|
114
|
-
const parseRoutePathSegments = (pathname, cache) => parsePathname(pathname, cache, false);
|
|
115
|
-
const parsePathname = (pathname, cache, basePathValues) => {
|
|
112
|
+
const parsePathname = (pathname, cache) => {
|
|
116
113
|
if (!pathname) return [];
|
|
117
114
|
const cached = cache?.get(pathname);
|
|
118
115
|
if (cached) return cached;
|
|
119
|
-
const parsed = baseParsePathname(pathname
|
|
116
|
+
const parsed = baseParsePathname(pathname);
|
|
120
117
|
cache?.set(pathname, parsed);
|
|
121
118
|
return parsed;
|
|
122
119
|
};
|
|
@@ -125,7 +122,7 @@ const PARAM_W_CURLY_BRACES_RE = /^(.*?)\{(\$[a-zA-Z_$][a-zA-Z0-9_$]*)\}(.*)$/;
|
|
|
125
122
|
const OPTIONAL_PARAM_W_CURLY_BRACES_RE = /^(.*?)\{-(\$[a-zA-Z_$][a-zA-Z0-9_$]*)\}(.*)$/;
|
|
126
123
|
const WILDCARD_RE = /^\$$/;
|
|
127
124
|
const WILDCARD_W_CURLY_BRACES_RE = /^(.*?)\{\$\}(.*)$/;
|
|
128
|
-
function baseParsePathname(pathname
|
|
125
|
+
function baseParsePathname(pathname) {
|
|
129
126
|
pathname = cleanPath(pathname);
|
|
130
127
|
const segments = [];
|
|
131
128
|
if (pathname.slice(0, 1) === "/") {
|
|
@@ -141,8 +138,7 @@ function baseParsePathname(pathname, basePathValues) {
|
|
|
141
138
|
const split = pathname.split("/").filter(Boolean);
|
|
142
139
|
segments.push(
|
|
143
140
|
...split.map((part) => {
|
|
144
|
-
const
|
|
145
|
-
const wildcardBracesMatch = partToMatch.match(WILDCARD_W_CURLY_BRACES_RE);
|
|
141
|
+
const wildcardBracesMatch = part.match(WILDCARD_W_CURLY_BRACES_RE);
|
|
146
142
|
if (wildcardBracesMatch) {
|
|
147
143
|
const prefix = wildcardBracesMatch[1];
|
|
148
144
|
const suffix = wildcardBracesMatch[2];
|
|
@@ -153,7 +149,7 @@ function baseParsePathname(pathname, basePathValues) {
|
|
|
153
149
|
suffixSegment: suffix || void 0
|
|
154
150
|
};
|
|
155
151
|
}
|
|
156
|
-
const optionalParamBracesMatch =
|
|
152
|
+
const optionalParamBracesMatch = part.match(
|
|
157
153
|
OPTIONAL_PARAM_W_CURLY_BRACES_RE
|
|
158
154
|
);
|
|
159
155
|
if (optionalParamBracesMatch) {
|
|
@@ -168,7 +164,7 @@ function baseParsePathname(pathname, basePathValues) {
|
|
|
168
164
|
suffixSegment: suffix || void 0
|
|
169
165
|
};
|
|
170
166
|
}
|
|
171
|
-
const paramBracesMatch =
|
|
167
|
+
const paramBracesMatch = part.match(PARAM_W_CURLY_BRACES_RE);
|
|
172
168
|
if (paramBracesMatch) {
|
|
173
169
|
const prefix = paramBracesMatch[1];
|
|
174
170
|
const paramName = paramBracesMatch[2];
|
|
@@ -180,8 +176,8 @@ function baseParsePathname(pathname, basePathValues) {
|
|
|
180
176
|
suffixSegment: suffix || void 0
|
|
181
177
|
};
|
|
182
178
|
}
|
|
183
|
-
if (PARAM_RE.test(
|
|
184
|
-
const paramName =
|
|
179
|
+
if (PARAM_RE.test(part)) {
|
|
180
|
+
const paramName = part.substring(1);
|
|
185
181
|
return {
|
|
186
182
|
type: SEGMENT_TYPE_PARAM,
|
|
187
183
|
value: "$" + paramName,
|
|
@@ -189,7 +185,7 @@ function baseParsePathname(pathname, basePathValues) {
|
|
|
189
185
|
suffixSegment: void 0
|
|
190
186
|
};
|
|
191
187
|
}
|
|
192
|
-
if (WILDCARD_RE.test(
|
|
188
|
+
if (WILDCARD_RE.test(part)) {
|
|
193
189
|
return {
|
|
194
190
|
type: SEGMENT_TYPE_WILDCARD,
|
|
195
191
|
value: "$",
|
|
@@ -199,7 +195,7 @@ function baseParsePathname(pathname, basePathValues) {
|
|
|
199
195
|
}
|
|
200
196
|
return {
|
|
201
197
|
type: SEGMENT_TYPE_PATHNAME,
|
|
202
|
-
value:
|
|
198
|
+
value: part.includes("%25") ? part.split("%25").map((segment) => decodeURI(segment)).join("%25") : decodeURI(part)
|
|
203
199
|
};
|
|
204
200
|
})
|
|
205
201
|
);
|
|
@@ -220,7 +216,7 @@ function interpolatePath({
|
|
|
220
216
|
decodeCharMap,
|
|
221
217
|
parseCache
|
|
222
218
|
}) {
|
|
223
|
-
const interpolatedPathSegments =
|
|
219
|
+
const interpolatedPathSegments = parsePathname(path, parseCache);
|
|
224
220
|
function encodeParam(key) {
|
|
225
221
|
const value = params[key];
|
|
226
222
|
const isValueString = typeof value === "string";
|
|
@@ -322,11 +318,11 @@ function matchByPath(from, {
|
|
|
322
318
|
caseSensitive
|
|
323
319
|
}, parseCache) {
|
|
324
320
|
const stringTo = to;
|
|
325
|
-
const baseSegments =
|
|
321
|
+
const baseSegments = parsePathname(
|
|
326
322
|
from.startsWith("/") ? from : `/${from}`,
|
|
327
323
|
parseCache
|
|
328
324
|
);
|
|
329
|
-
const routeSegments =
|
|
325
|
+
const routeSegments = parsePathname(
|
|
330
326
|
stringTo.startsWith("/") ? stringTo : `/${stringTo}`,
|
|
331
327
|
parseCache
|
|
332
328
|
);
|
|
@@ -532,9 +528,7 @@ export {
|
|
|
532
528
|
joinPaths,
|
|
533
529
|
matchByPath,
|
|
534
530
|
matchPathname,
|
|
535
|
-
parseBasePathSegments,
|
|
536
531
|
parsePathname,
|
|
537
|
-
parseRoutePathSegments,
|
|
538
532
|
removeTrailingSlash,
|
|
539
533
|
resolvePath,
|
|
540
534
|
trimPath,
|
package/dist/esm/path.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.js","sources":["../../src/path.ts"],"sourcesContent":["import { rootRouteId } from './root'\nimport { last } from './utils'\nimport type { LRUCache } from './lru-cache'\nimport type { MatchLocation } from './RouterProvider'\nimport type { AnyPathParams } from './route'\n\nexport const SEGMENT_TYPE_PATHNAME = 0\nexport const SEGMENT_TYPE_PARAM = 1\nexport const SEGMENT_TYPE_WILDCARD = 2\nexport const SEGMENT_TYPE_OPTIONAL_PARAM = 3\n\nexport interface Segment {\n readonly type:\n | typeof SEGMENT_TYPE_PATHNAME\n | typeof SEGMENT_TYPE_PARAM\n | typeof SEGMENT_TYPE_WILDCARD\n | typeof SEGMENT_TYPE_OPTIONAL_PARAM\n readonly value: string\n readonly prefixSegment?: string\n readonly suffixSegment?: string\n // Indicates if there is a static segment after this required/optional param\n readonly hasStaticAfter?: boolean\n}\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 return path === '/' ? path : path.replace(/\\/{1,}$/, '')\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 parseCache?: ParsePathnameCache\n}\n\nfunction segmentToString(segment: Segment): string {\n const { type, value } = segment\n if (type === SEGMENT_TYPE_PATHNAME) {\n return value\n }\n\n const { prefixSegment, suffixSegment } = segment\n\n if (type === SEGMENT_TYPE_PARAM) {\n const param = value.substring(1)\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{$${param}}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{$${param}}`\n } else if (suffixSegment) {\n return `{$${param}}${suffixSegment}`\n }\n }\n\n if (type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const param = value.substring(1)\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{-$${param}}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{-$${param}}`\n } else if (suffixSegment) {\n return `{-$${param}}${suffixSegment}`\n }\n return `{-$${param}}`\n }\n\n if (type === SEGMENT_TYPE_WILDCARD) {\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{$}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{$}`\n } else if (suffixSegment) {\n return `{$}${suffixSegment}`\n }\n }\n\n // This case should never happen, should we throw instead?\n return value\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 parseCache,\n}: ResolvePathOptions) {\n let baseSegments = parseBasePathSegments(base, parseCache).slice()\n const toSegments = parseRoutePathSegments(to, parseCache)\n\n if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {\n baseSegments.pop()\n }\n\n for (let index = 0, length = toSegments.length; index < length; index++) {\n const toSegment = toSegments[index]!\n const value = toSegment.value\n if (value === '/') {\n if (!index) {\n // Leading slash\n baseSegments = [toSegment]\n } else if (index === length - 1) {\n // Trailing Slash\n baseSegments.push(toSegment)\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(toSegment)\n }\n }\n\n if (baseSegments.length > 1) {\n if (last(baseSegments)!.value === '/') {\n if (trailingSlash === 'never') {\n baseSegments.pop()\n }\n } else if (trailingSlash === 'always') {\n baseSegments.push({ type: SEGMENT_TYPE_PATHNAME, value: '/' })\n }\n }\n\n const segmentValues = baseSegments.map(segmentToString)\n // const joined = joinPaths([basepath, ...segmentValues])\n const joined = joinPaths(segmentValues)\n return joined\n}\n\nexport type ParsePathnameCache = LRUCache<string, ReadonlyArray<Segment>>\n\nexport const parseBasePathSegments = (\n pathname?: string,\n cache?: ParsePathnameCache,\n): ReadonlyArray<Segment> => parsePathname(pathname, cache, true)\n\nexport const parseRoutePathSegments = (\n pathname?: string,\n cache?: ParsePathnameCache,\n): ReadonlyArray<Segment> => parsePathname(pathname, cache, false)\n\n/**\n * Parse a pathname into an array of typed segments used by the router's\n * matcher. Results are optionally cached via an LRU cache.\n */\nexport const parsePathname = (\n pathname?: string,\n cache?: ParsePathnameCache,\n basePathValues?: boolean,\n): ReadonlyArray<Segment> => {\n if (!pathname) return []\n const cached = cache?.get(pathname)\n if (cached) return cached\n const parsed = baseParsePathname(pathname, basePathValues)\n cache?.set(pathname, parsed)\n return parsed\n}\n\nconst PARAM_RE = /^\\$.{1,}$/ // $paramName\nconst PARAM_W_CURLY_BRACES_RE = /^(.*?)\\{(\\$[a-zA-Z_$][a-zA-Z0-9_$]*)\\}(.*)$/ // prefix{$paramName}suffix\nconst OPTIONAL_PARAM_W_CURLY_BRACES_RE =\n /^(.*?)\\{-(\\$[a-zA-Z_$][a-zA-Z0-9_$]*)\\}(.*)$/ // prefix{-$paramName}suffix\n\nconst WILDCARD_RE = /^\\$$/ // $\nconst WILDCARD_W_CURLY_BRACES_RE = /^(.*?)\\{\\$\\}(.*)$/ // prefix{$}suffix\n\n/**\n * Required: `/foo/$bar` ✅\n * Prefix and Suffix: `/foo/prefix${bar}suffix` ✅\n * Wildcard: `/foo/$` ✅\n * Wildcard with Prefix and Suffix: `/foo/prefix{$}suffix` ✅\n *\n * Optional param: `/foo/{-$bar}`\n * Optional param with Prefix and Suffix: `/foo/prefix{-$bar}suffix`\n\n * Future:\n * Optional named segment: `/foo/{bar}`\n * Optional named segment with Prefix and Suffix: `/foo/prefix{-bar}suffix`\n * Escape special characters:\n * - `/foo/[$]` - Static route\n * - `/foo/[$]{$foo} - Dynamic route with a static prefix of `$`\n * - `/foo/{$foo}[$]` - Dynamic route with a static suffix of `$`\n */\nfunction baseParsePathname(\n pathname: string,\n basePathValues?: boolean,\n): ReadonlyArray<Segment> {\n pathname = cleanPath(pathname)\n\n const segments: Array<Segment> = []\n\n if (pathname.slice(0, 1) === '/') {\n pathname = pathname.substring(1)\n segments.push({\n type: SEGMENT_TYPE_PATHNAME,\n value: '/',\n })\n }\n\n if (!pathname) {\n return segments\n }\n\n // Remove empty segments and '.' segments\n const split = pathname.split('/').filter(Boolean)\n\n segments.push(\n ...split.map((part): Segment => {\n // strip tailing underscore for non-nested paths\n const partToMatch =\n !basePathValues && part !== rootRouteId && part.slice(-1) === '_'\n ? part.slice(0, -1)\n : part\n\n // Check for wildcard with curly braces: prefix{$}suffix\n const wildcardBracesMatch = partToMatch.match(WILDCARD_W_CURLY_BRACES_RE)\n if (wildcardBracesMatch) {\n const prefix = wildcardBracesMatch[1]\n const suffix = wildcardBracesMatch[2]\n return {\n type: SEGMENT_TYPE_WILDCARD,\n value: '$',\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for optional parameter format: prefix{-$paramName}suffix\n const optionalParamBracesMatch = partToMatch.match(\n OPTIONAL_PARAM_W_CURLY_BRACES_RE,\n )\n if (optionalParamBracesMatch) {\n const prefix = optionalParamBracesMatch[1]\n const paramName = optionalParamBracesMatch[2]!\n const suffix = optionalParamBracesMatch[3]\n return {\n type: SEGMENT_TYPE_OPTIONAL_PARAM,\n value: paramName, // Now just $paramName (no prefix)\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for the new parameter format: prefix{$paramName}suffix\n const paramBracesMatch = partToMatch.match(PARAM_W_CURLY_BRACES_RE)\n if (paramBracesMatch) {\n const prefix = paramBracesMatch[1]\n const paramName = paramBracesMatch[2]\n const suffix = paramBracesMatch[3]\n return {\n type: SEGMENT_TYPE_PARAM,\n value: '' + paramName,\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for bare parameter format: $paramName (without curly braces)\n if (PARAM_RE.test(partToMatch)) {\n const paramName = partToMatch.substring(1)\n return {\n type: SEGMENT_TYPE_PARAM,\n value: '$' + paramName,\n prefixSegment: undefined,\n suffixSegment: undefined,\n }\n }\n\n // Check for bare wildcard: $ (without curly braces)\n if (WILDCARD_RE.test(partToMatch)) {\n return {\n type: SEGMENT_TYPE_WILDCARD,\n value: '$',\n prefixSegment: undefined,\n suffixSegment: undefined,\n }\n }\n\n // Handle regular pathname segment\n return {\n type: SEGMENT_TYPE_PATHNAME,\n value: partToMatch.includes('%25')\n ? partToMatch\n .split('%25')\n .map((segment) => decodeURI(segment))\n .join('%25')\n : decodeURI(partToMatch),\n }\n }),\n )\n\n if (pathname.slice(-1) === '/') {\n pathname = pathname.substring(1)\n segments.push({\n type: SEGMENT_TYPE_PATHNAME,\n value: '/',\n })\n }\n\n return segments\n}\n\ninterface InterpolatePathOptions {\n path?: string\n params: Record<string, unknown>\n leaveWildcards?: boolean\n leaveParams?: boolean\n // Map of encoded chars to decoded chars (e.g. '%40' -> '@') that should remain decoded in path params\n decodeCharMap?: Map<string, string>\n parseCache?: ParsePathnameCache\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/**\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 * - Optionally leaves placeholders or wildcards in place\n */\nexport function interpolatePath({\n path,\n params,\n leaveWildcards,\n leaveParams,\n decodeCharMap,\n parseCache,\n}: InterpolatePathOptions): InterPolatePathResult {\n const interpolatedPathSegments = parseRoutePathSegments(path, parseCache)\n\n function encodeParam(key: string): any {\n const value = params[key]\n const isValueString = typeof value === 'string'\n\n if (key === '*' || key === '_splat') {\n // the splat/catch-all routes shouldn't have the '/' encoded out\n return isValueString ? encodeURI(value) : value\n } else {\n return isValueString ? encodePathParam(value, decodeCharMap) : value\n }\n }\n\n // Tracking if any params are missing in the `params` object\n // when interpolating the path\n let isMissingParams = false\n\n const usedParams: Record<string, unknown> = {}\n const interpolatedPath = joinPaths(\n interpolatedPathSegments.map((segment) => {\n if (segment.type === SEGMENT_TYPE_PATHNAME) {\n return segment.value\n }\n\n if (segment.type === SEGMENT_TYPE_WILDCARD) {\n usedParams._splat = params._splat\n\n // TODO: Deprecate *\n usedParams['*'] = params._splat\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\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 (!params._splat) {\n isMissingParams = true\n // For missing splat parameters, just return the prefix and suffix without the wildcard\n if (leaveWildcards) {\n return `${segmentPrefix}${segment.value}${segmentSuffix}`\n }\n // If there is a prefix or suffix, return them joined, otherwise omit the segment\n if (segmentPrefix || segmentSuffix) {\n return `${segmentPrefix}${segmentSuffix}`\n }\n return undefined\n }\n\n const value = encodeParam('_splat')\n if (leaveWildcards) {\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${value}${segmentSuffix}`\n }\n\n if (segment.type === SEGMENT_TYPE_PARAM) {\n const key = segment.value.substring(1)\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\n if (leaveParams) {\n const value = encodeParam(segment.value)\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${encodeParam(key) ?? 'undefined'}${segmentSuffix}`\n }\n\n if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const key = segment.value.substring(1)\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\n\n // Check if optional parameter is missing or undefined\n if (!(key in params) || params[key] == null) {\n if (leaveWildcards) {\n return `${segmentPrefix}${key}${segmentSuffix}`\n }\n // For optional params with prefix/suffix, keep the prefix/suffix but omit the param\n if (segmentPrefix || segmentSuffix) {\n return `${segmentPrefix}${segmentSuffix}`\n }\n // If no prefix/suffix, omit the entire segment\n return undefined\n }\n\n usedParams[key] = params[key]\n\n if (leaveParams) {\n const value = encodeParam(segment.value)\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n if (leaveWildcards) {\n return `${segmentPrefix}${key}${encodeParam(key) ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${encodeParam(key) ?? ''}${segmentSuffix}`\n }\n\n return segment.value\n }),\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\n/**\n * Match a pathname against a route destination and return extracted params\n * or `undefined`. Uses the same parsing as the router for consistency.\n */\nexport function matchPathname(\n currentPathname: string,\n matchLocation: Pick<MatchLocation, 'to' | 'fuzzy' | 'caseSensitive'>,\n parseCache?: ParsePathnameCache,\n): AnyPathParams | undefined {\n const pathParams = matchByPath(currentPathname, matchLocation, parseCache)\n // const searchMatched = matchBySearch(location.search, matchLocation)\n\n if (matchLocation.to && !pathParams) {\n return\n }\n\n return pathParams ?? {}\n}\n\n/** Low-level matcher that compares two path strings and extracts params. */\nexport function matchByPath(\n from: string,\n {\n to,\n fuzzy,\n caseSensitive,\n }: Pick<MatchLocation, 'to' | 'caseSensitive' | 'fuzzy'>,\n parseCache?: ParsePathnameCache,\n): Record<string, string> | undefined {\n const stringTo = to as string\n\n // Parse the from and to\n const baseSegments = parseBasePathSegments(\n from.startsWith('/') ? from : `/${from}`,\n parseCache,\n )\n const routeSegments = parseRoutePathSegments(\n stringTo.startsWith('/') ? stringTo : `/${stringTo}`,\n parseCache,\n )\n\n const params: Record<string, string> = {}\n\n const result = isMatch(\n baseSegments,\n routeSegments,\n params,\n fuzzy,\n caseSensitive,\n )\n\n return result ? params : undefined\n}\n\nfunction isMatch(\n baseSegments: ReadonlyArray<Segment>,\n routeSegments: ReadonlyArray<Segment>,\n params: Record<string, string>,\n fuzzy?: boolean,\n caseSensitive?: boolean,\n): boolean {\n let baseIndex = 0\n let routeIndex = 0\n\n while (baseIndex < baseSegments.length || routeIndex < routeSegments.length) {\n const baseSegment = baseSegments[baseIndex]\n const routeSegment = routeSegments[routeIndex]\n\n if (routeSegment) {\n if (routeSegment.type === SEGMENT_TYPE_WILDCARD) {\n // Capture all remaining segments for a wildcard\n const remainingBaseSegments = baseSegments.slice(baseIndex)\n\n let _splat: string\n\n // If this is a wildcard with prefix/suffix, we need to handle the first segment specially\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n if (!baseSegment) return false\n\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if ('prefixSegment' in routeSegment) {\n if (!baseValue.startsWith(prefix)) {\n return false\n }\n }\n if ('suffixSegment' in routeSegment) {\n if (\n !baseSegments[baseSegments.length - 1]?.value.endsWith(suffix)\n ) {\n return false\n }\n }\n\n let rejoinedSplat = decodeURI(\n joinPaths(remainingBaseSegments.map((d) => d.value)),\n )\n\n // Remove the prefix and suffix from the rejoined splat\n if (prefix && rejoinedSplat.startsWith(prefix)) {\n rejoinedSplat = rejoinedSplat.slice(prefix.length)\n }\n\n if (suffix && rejoinedSplat.endsWith(suffix)) {\n rejoinedSplat = rejoinedSplat.slice(\n 0,\n rejoinedSplat.length - suffix.length,\n )\n }\n\n _splat = rejoinedSplat\n } else {\n // If no prefix/suffix, just rejoin the remaining segments\n _splat = decodeURI(\n joinPaths(remainingBaseSegments.map((d) => d.value)),\n )\n }\n\n // TODO: Deprecate *\n params['*'] = _splat\n params['_splat'] = _splat\n return true\n }\n\n if (routeSegment.type === SEGMENT_TYPE_PATHNAME) {\n if (routeSegment.value === '/' && !baseSegment?.value) {\n routeIndex++\n continue\n }\n\n if (baseSegment) {\n if (caseSensitive) {\n if (routeSegment.value !== baseSegment.value) {\n return false\n }\n } else if (\n routeSegment.value.toLowerCase() !== baseSegment.value.toLowerCase()\n ) {\n return false\n }\n baseIndex++\n routeIndex++\n continue\n } else {\n return false\n }\n }\n\n if (routeSegment.type === SEGMENT_TYPE_PARAM) {\n if (!baseSegment) {\n return false\n }\n\n if (baseSegment.value === '/') {\n return false\n }\n\n let _paramValue = ''\n let matched = false\n\n // If this param has prefix/suffix, we need to extract the actual parameter value\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if (prefix && !baseValue.startsWith(prefix)) {\n return false\n }\n if (suffix && !baseValue.endsWith(suffix)) {\n return false\n }\n\n let paramValue = baseValue\n if (prefix && paramValue.startsWith(prefix)) {\n paramValue = paramValue.slice(prefix.length)\n }\n if (suffix && paramValue.endsWith(suffix)) {\n paramValue = paramValue.slice(0, paramValue.length - suffix.length)\n }\n\n _paramValue = decodeURIComponent(paramValue)\n matched = true\n } else {\n // If no prefix/suffix, just decode the base segment value\n _paramValue = decodeURIComponent(baseSegment.value)\n matched = true\n }\n\n if (matched) {\n params[routeSegment.value.substring(1)] = _paramValue\n baseIndex++\n }\n\n routeIndex++\n continue\n }\n\n if (routeSegment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n // Optional parameters can be missing - don't fail the match\n if (!baseSegment) {\n // No base segment for optional param - skip this route segment\n routeIndex++\n continue\n }\n\n if (baseSegment.value === '/') {\n // Skip slash segments for optional params\n routeIndex++\n continue\n }\n\n let _paramValue = ''\n let matched = false\n\n // If this optional param has prefix/suffix, we need to extract the actual parameter value\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if (\n (!prefix || baseValue.startsWith(prefix)) &&\n (!suffix || baseValue.endsWith(suffix))\n ) {\n let paramValue = baseValue\n if (prefix && paramValue.startsWith(prefix)) {\n paramValue = paramValue.slice(prefix.length)\n }\n if (suffix && paramValue.endsWith(suffix)) {\n paramValue = paramValue.slice(\n 0,\n paramValue.length - suffix.length,\n )\n }\n\n _paramValue = decodeURIComponent(paramValue)\n matched = true\n }\n } else {\n // For optional params without prefix/suffix, we need to check if the current\n // base segment should match this optional param or a later route segment\n\n // Look ahead to see if there's a later route segment that matches the current base segment\n let shouldMatchOptional = true\n for (\n let lookAhead = routeIndex + 1;\n lookAhead < routeSegments.length;\n lookAhead++\n ) {\n const futureRouteSegment = routeSegments[lookAhead]\n if (\n futureRouteSegment?.type === SEGMENT_TYPE_PATHNAME &&\n futureRouteSegment.value === baseSegment.value\n ) {\n // The current base segment matches a future pathname segment,\n // so we should skip this optional parameter\n shouldMatchOptional = false\n break\n }\n\n // If we encounter a required param or wildcard, stop looking ahead\n if (\n futureRouteSegment?.type === SEGMENT_TYPE_PARAM ||\n futureRouteSegment?.type === SEGMENT_TYPE_WILDCARD\n ) {\n if (baseSegments.length < routeSegments.length) {\n shouldMatchOptional = false\n }\n break\n }\n }\n\n if (shouldMatchOptional) {\n // If no prefix/suffix, just decode the base segment value\n _paramValue = decodeURIComponent(baseSegment.value)\n matched = true\n }\n }\n\n if (matched) {\n params[routeSegment.value.substring(1)] = _paramValue\n baseIndex++\n }\n\n routeIndex++\n continue\n }\n }\n\n // If we have base segments left but no route segments, it's a fuzzy match\n if (baseIndex < baseSegments.length && routeIndex >= routeSegments.length) {\n params['**'] = joinPaths(\n baseSegments.slice(baseIndex).map((d) => d.value),\n )\n return !!fuzzy && routeSegments[routeSegments.length - 1]?.value !== '/'\n }\n\n // If we have route segments left but no base segments, check if remaining are optional\n if (routeIndex < routeSegments.length && baseIndex >= baseSegments.length) {\n // Check if all remaining route segments are optional\n for (let i = routeIndex; i < routeSegments.length; i++) {\n if (routeSegments[i]?.type !== SEGMENT_TYPE_OPTIONAL_PARAM) {\n return false\n }\n }\n // All remaining are optional, so we can finish\n break\n }\n\n break\n }\n\n return true\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"path.js","sources":["../../src/path.ts"],"sourcesContent":["import { last } from './utils'\nimport type { LRUCache } from './lru-cache'\nimport type { MatchLocation } from './RouterProvider'\nimport type { AnyPathParams } from './route'\n\nexport const SEGMENT_TYPE_PATHNAME = 0\nexport const SEGMENT_TYPE_PARAM = 1\nexport const SEGMENT_TYPE_WILDCARD = 2\nexport const SEGMENT_TYPE_OPTIONAL_PARAM = 3\n\nexport interface Segment {\n readonly type:\n | typeof SEGMENT_TYPE_PATHNAME\n | typeof SEGMENT_TYPE_PARAM\n | typeof SEGMENT_TYPE_WILDCARD\n | typeof SEGMENT_TYPE_OPTIONAL_PARAM\n readonly value: string\n readonly prefixSegment?: string\n readonly suffixSegment?: string\n // Indicates if there is a static segment after this required/optional param\n readonly hasStaticAfter?: boolean\n}\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 return path === '/' ? path : path.replace(/\\/{1,}$/, '')\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 parseCache?: ParsePathnameCache\n}\n\nfunction segmentToString(segment: Segment): string {\n const { type, value } = segment\n if (type === SEGMENT_TYPE_PATHNAME) {\n return value\n }\n\n const { prefixSegment, suffixSegment } = segment\n\n if (type === SEGMENT_TYPE_PARAM) {\n const param = value.substring(1)\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{$${param}}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{$${param}}`\n } else if (suffixSegment) {\n return `{$${param}}${suffixSegment}`\n }\n }\n\n if (type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const param = value.substring(1)\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{-$${param}}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{-$${param}}`\n } else if (suffixSegment) {\n return `{-$${param}}${suffixSegment}`\n }\n return `{-$${param}}`\n }\n\n if (type === SEGMENT_TYPE_WILDCARD) {\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{$}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{$}`\n } else if (suffixSegment) {\n return `{$}${suffixSegment}`\n }\n }\n\n // This case should never happen, should we throw instead?\n return value\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 parseCache,\n}: ResolvePathOptions) {\n let baseSegments = parsePathname(base, parseCache).slice()\n const toSegments = parsePathname(to, parseCache)\n\n if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {\n baseSegments.pop()\n }\n\n for (let index = 0, length = toSegments.length; index < length; index++) {\n const toSegment = toSegments[index]!\n const value = toSegment.value\n if (value === '/') {\n if (!index) {\n // Leading slash\n baseSegments = [toSegment]\n } else if (index === length - 1) {\n // Trailing Slash\n baseSegments.push(toSegment)\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(toSegment)\n }\n }\n\n if (baseSegments.length > 1) {\n if (last(baseSegments)!.value === '/') {\n if (trailingSlash === 'never') {\n baseSegments.pop()\n }\n } else if (trailingSlash === 'always') {\n baseSegments.push({ type: SEGMENT_TYPE_PATHNAME, value: '/' })\n }\n }\n\n const segmentValues = baseSegments.map(segmentToString)\n // const joined = joinPaths([basepath, ...segmentValues])\n const joined = joinPaths(segmentValues)\n return joined\n}\n\nexport type ParsePathnameCache = LRUCache<string, ReadonlyArray<Segment>>\n\n/**\n * Parse a pathname into an array of typed segments used by the router's\n * matcher. Results are optionally cached via an LRU cache.\n */\nexport const parsePathname = (\n pathname?: string,\n cache?: ParsePathnameCache,\n): ReadonlyArray<Segment> => {\n if (!pathname) return []\n const cached = cache?.get(pathname)\n if (cached) return cached\n const parsed = baseParsePathname(pathname)\n cache?.set(pathname, parsed)\n return parsed\n}\n\nconst PARAM_RE = /^\\$.{1,}$/ // $paramName\nconst PARAM_W_CURLY_BRACES_RE = /^(.*?)\\{(\\$[a-zA-Z_$][a-zA-Z0-9_$]*)\\}(.*)$/ // prefix{$paramName}suffix\nconst OPTIONAL_PARAM_W_CURLY_BRACES_RE =\n /^(.*?)\\{-(\\$[a-zA-Z_$][a-zA-Z0-9_$]*)\\}(.*)$/ // prefix{-$paramName}suffix\n\nconst WILDCARD_RE = /^\\$$/ // $\nconst WILDCARD_W_CURLY_BRACES_RE = /^(.*?)\\{\\$\\}(.*)$/ // prefix{$}suffix\n\n/**\n * Required: `/foo/$bar` ✅\n * Prefix and Suffix: `/foo/prefix${bar}suffix` ✅\n * Wildcard: `/foo/$` ✅\n * Wildcard with Prefix and Suffix: `/foo/prefix{$}suffix` ✅\n *\n * Optional param: `/foo/{-$bar}`\n * Optional param with Prefix and Suffix: `/foo/prefix{-$bar}suffix`\n\n * Future:\n * Optional named segment: `/foo/{bar}`\n * Optional named segment with Prefix and Suffix: `/foo/prefix{-bar}suffix`\n * Escape special characters:\n * - `/foo/[$]` - Static route\n * - `/foo/[$]{$foo} - Dynamic route with a static prefix of `$`\n * - `/foo/{$foo}[$]` - Dynamic route with a static suffix of `$`\n */\nfunction baseParsePathname(pathname: string): ReadonlyArray<Segment> {\n pathname = cleanPath(pathname)\n\n const segments: Array<Segment> = []\n\n if (pathname.slice(0, 1) === '/') {\n pathname = pathname.substring(1)\n segments.push({\n type: SEGMENT_TYPE_PATHNAME,\n value: '/',\n })\n }\n\n if (!pathname) {\n return segments\n }\n\n // Remove empty segments and '.' segments\n const split = pathname.split('/').filter(Boolean)\n\n segments.push(\n ...split.map((part): Segment => {\n // Check for wildcard with curly braces: prefix{$}suffix\n const wildcardBracesMatch = part.match(WILDCARD_W_CURLY_BRACES_RE)\n if (wildcardBracesMatch) {\n const prefix = wildcardBracesMatch[1]\n const suffix = wildcardBracesMatch[2]\n return {\n type: SEGMENT_TYPE_WILDCARD,\n value: '$',\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for optional parameter format: prefix{-$paramName}suffix\n const optionalParamBracesMatch = part.match(\n OPTIONAL_PARAM_W_CURLY_BRACES_RE,\n )\n if (optionalParamBracesMatch) {\n const prefix = optionalParamBracesMatch[1]\n const paramName = optionalParamBracesMatch[2]!\n const suffix = optionalParamBracesMatch[3]\n return {\n type: SEGMENT_TYPE_OPTIONAL_PARAM,\n value: paramName, // Now just $paramName (no prefix)\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for the new parameter format: prefix{$paramName}suffix\n const paramBracesMatch = part.match(PARAM_W_CURLY_BRACES_RE)\n if (paramBracesMatch) {\n const prefix = paramBracesMatch[1]\n const paramName = paramBracesMatch[2]\n const suffix = paramBracesMatch[3]\n return {\n type: SEGMENT_TYPE_PARAM,\n value: '' + paramName,\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for bare parameter format: $paramName (without curly braces)\n if (PARAM_RE.test(part)) {\n const paramName = part.substring(1)\n return {\n type: SEGMENT_TYPE_PARAM,\n value: '$' + paramName,\n prefixSegment: undefined,\n suffixSegment: undefined,\n }\n }\n\n // Check for bare wildcard: $ (without curly braces)\n if (WILDCARD_RE.test(part)) {\n return {\n type: SEGMENT_TYPE_WILDCARD,\n value: '$',\n prefixSegment: undefined,\n suffixSegment: undefined,\n }\n }\n\n // Handle regular pathname segment\n return {\n type: SEGMENT_TYPE_PATHNAME,\n value: part.includes('%25')\n ? part\n .split('%25')\n .map((segment) => decodeURI(segment))\n .join('%25')\n : decodeURI(part),\n }\n }),\n )\n\n if (pathname.slice(-1) === '/') {\n pathname = pathname.substring(1)\n segments.push({\n type: SEGMENT_TYPE_PATHNAME,\n value: '/',\n })\n }\n\n return segments\n}\n\ninterface InterpolatePathOptions {\n path?: string\n params: Record<string, unknown>\n leaveWildcards?: boolean\n leaveParams?: boolean\n // Map of encoded chars to decoded chars (e.g. '%40' -> '@') that should remain decoded in path params\n decodeCharMap?: Map<string, string>\n parseCache?: ParsePathnameCache\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/**\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 * - Optionally leaves placeholders or wildcards in place\n */\nexport function interpolatePath({\n path,\n params,\n leaveWildcards,\n leaveParams,\n decodeCharMap,\n parseCache,\n}: InterpolatePathOptions): InterPolatePathResult {\n const interpolatedPathSegments = parsePathname(path, parseCache)\n\n function encodeParam(key: string): any {\n const value = params[key]\n const isValueString = typeof value === 'string'\n\n if (key === '*' || key === '_splat') {\n // the splat/catch-all routes shouldn't have the '/' encoded out\n return isValueString ? encodeURI(value) : value\n } else {\n return isValueString ? encodePathParam(value, decodeCharMap) : value\n }\n }\n\n // Tracking if any params are missing in the `params` object\n // when interpolating the path\n let isMissingParams = false\n\n const usedParams: Record<string, unknown> = {}\n const interpolatedPath = joinPaths(\n interpolatedPathSegments.map((segment) => {\n if (segment.type === SEGMENT_TYPE_PATHNAME) {\n return segment.value\n }\n\n if (segment.type === SEGMENT_TYPE_WILDCARD) {\n usedParams._splat = params._splat\n\n // TODO: Deprecate *\n usedParams['*'] = params._splat\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\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 (!params._splat) {\n isMissingParams = true\n // For missing splat parameters, just return the prefix and suffix without the wildcard\n if (leaveWildcards) {\n return `${segmentPrefix}${segment.value}${segmentSuffix}`\n }\n // If there is a prefix or suffix, return them joined, otherwise omit the segment\n if (segmentPrefix || segmentSuffix) {\n return `${segmentPrefix}${segmentSuffix}`\n }\n return undefined\n }\n\n const value = encodeParam('_splat')\n if (leaveWildcards) {\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${value}${segmentSuffix}`\n }\n\n if (segment.type === SEGMENT_TYPE_PARAM) {\n const key = segment.value.substring(1)\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\n if (leaveParams) {\n const value = encodeParam(segment.value)\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${encodeParam(key) ?? 'undefined'}${segmentSuffix}`\n }\n\n if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const key = segment.value.substring(1)\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\n\n // Check if optional parameter is missing or undefined\n if (!(key in params) || params[key] == null) {\n if (leaveWildcards) {\n return `${segmentPrefix}${key}${segmentSuffix}`\n }\n // For optional params with prefix/suffix, keep the prefix/suffix but omit the param\n if (segmentPrefix || segmentSuffix) {\n return `${segmentPrefix}${segmentSuffix}`\n }\n // If no prefix/suffix, omit the entire segment\n return undefined\n }\n\n usedParams[key] = params[key]\n\n if (leaveParams) {\n const value = encodeParam(segment.value)\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n if (leaveWildcards) {\n return `${segmentPrefix}${key}${encodeParam(key) ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${encodeParam(key) ?? ''}${segmentSuffix}`\n }\n\n return segment.value\n }),\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\n/**\n * Match a pathname against a route destination and return extracted params\n * or `undefined`. Uses the same parsing as the router for consistency.\n */\nexport function matchPathname(\n currentPathname: string,\n matchLocation: Pick<MatchLocation, 'to' | 'fuzzy' | 'caseSensitive'>,\n parseCache?: ParsePathnameCache,\n): AnyPathParams | undefined {\n const pathParams = matchByPath(currentPathname, matchLocation, parseCache)\n // const searchMatched = matchBySearch(location.search, matchLocation)\n\n if (matchLocation.to && !pathParams) {\n return\n }\n\n return pathParams ?? {}\n}\n\n/** Low-level matcher that compares two path strings and extracts params. */\nexport function matchByPath(\n from: string,\n {\n to,\n fuzzy,\n caseSensitive,\n }: Pick<MatchLocation, 'to' | 'caseSensitive' | 'fuzzy'>,\n parseCache?: ParsePathnameCache,\n): Record<string, string> | undefined {\n const stringTo = to as string\n\n // Parse the from and to\n const baseSegments = parsePathname(\n from.startsWith('/') ? from : `/${from}`,\n parseCache,\n )\n const routeSegments = parsePathname(\n stringTo.startsWith('/') ? stringTo : `/${stringTo}`,\n parseCache,\n )\n\n const params: Record<string, string> = {}\n\n const result = isMatch(\n baseSegments,\n routeSegments,\n params,\n fuzzy,\n caseSensitive,\n )\n\n return result ? params : undefined\n}\n\nfunction isMatch(\n baseSegments: ReadonlyArray<Segment>,\n routeSegments: ReadonlyArray<Segment>,\n params: Record<string, string>,\n fuzzy?: boolean,\n caseSensitive?: boolean,\n): boolean {\n let baseIndex = 0\n let routeIndex = 0\n\n while (baseIndex < baseSegments.length || routeIndex < routeSegments.length) {\n const baseSegment = baseSegments[baseIndex]\n const routeSegment = routeSegments[routeIndex]\n\n if (routeSegment) {\n if (routeSegment.type === SEGMENT_TYPE_WILDCARD) {\n // Capture all remaining segments for a wildcard\n const remainingBaseSegments = baseSegments.slice(baseIndex)\n\n let _splat: string\n\n // If this is a wildcard with prefix/suffix, we need to handle the first segment specially\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n if (!baseSegment) return false\n\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if ('prefixSegment' in routeSegment) {\n if (!baseValue.startsWith(prefix)) {\n return false\n }\n }\n if ('suffixSegment' in routeSegment) {\n if (\n !baseSegments[baseSegments.length - 1]?.value.endsWith(suffix)\n ) {\n return false\n }\n }\n\n let rejoinedSplat = decodeURI(\n joinPaths(remainingBaseSegments.map((d) => d.value)),\n )\n\n // Remove the prefix and suffix from the rejoined splat\n if (prefix && rejoinedSplat.startsWith(prefix)) {\n rejoinedSplat = rejoinedSplat.slice(prefix.length)\n }\n\n if (suffix && rejoinedSplat.endsWith(suffix)) {\n rejoinedSplat = rejoinedSplat.slice(\n 0,\n rejoinedSplat.length - suffix.length,\n )\n }\n\n _splat = rejoinedSplat\n } else {\n // If no prefix/suffix, just rejoin the remaining segments\n _splat = decodeURI(\n joinPaths(remainingBaseSegments.map((d) => d.value)),\n )\n }\n\n // TODO: Deprecate *\n params['*'] = _splat\n params['_splat'] = _splat\n return true\n }\n\n if (routeSegment.type === SEGMENT_TYPE_PATHNAME) {\n if (routeSegment.value === '/' && !baseSegment?.value) {\n routeIndex++\n continue\n }\n\n if (baseSegment) {\n if (caseSensitive) {\n if (routeSegment.value !== baseSegment.value) {\n return false\n }\n } else if (\n routeSegment.value.toLowerCase() !== baseSegment.value.toLowerCase()\n ) {\n return false\n }\n baseIndex++\n routeIndex++\n continue\n } else {\n return false\n }\n }\n\n if (routeSegment.type === SEGMENT_TYPE_PARAM) {\n if (!baseSegment) {\n return false\n }\n\n if (baseSegment.value === '/') {\n return false\n }\n\n let _paramValue = ''\n let matched = false\n\n // If this param has prefix/suffix, we need to extract the actual parameter value\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if (prefix && !baseValue.startsWith(prefix)) {\n return false\n }\n if (suffix && !baseValue.endsWith(suffix)) {\n return false\n }\n\n let paramValue = baseValue\n if (prefix && paramValue.startsWith(prefix)) {\n paramValue = paramValue.slice(prefix.length)\n }\n if (suffix && paramValue.endsWith(suffix)) {\n paramValue = paramValue.slice(0, paramValue.length - suffix.length)\n }\n\n _paramValue = decodeURIComponent(paramValue)\n matched = true\n } else {\n // If no prefix/suffix, just decode the base segment value\n _paramValue = decodeURIComponent(baseSegment.value)\n matched = true\n }\n\n if (matched) {\n params[routeSegment.value.substring(1)] = _paramValue\n baseIndex++\n }\n\n routeIndex++\n continue\n }\n\n if (routeSegment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n // Optional parameters can be missing - don't fail the match\n if (!baseSegment) {\n // No base segment for optional param - skip this route segment\n routeIndex++\n continue\n }\n\n if (baseSegment.value === '/') {\n // Skip slash segments for optional params\n routeIndex++\n continue\n }\n\n let _paramValue = ''\n let matched = false\n\n // If this optional param has prefix/suffix, we need to extract the actual parameter value\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if (\n (!prefix || baseValue.startsWith(prefix)) &&\n (!suffix || baseValue.endsWith(suffix))\n ) {\n let paramValue = baseValue\n if (prefix && paramValue.startsWith(prefix)) {\n paramValue = paramValue.slice(prefix.length)\n }\n if (suffix && paramValue.endsWith(suffix)) {\n paramValue = paramValue.slice(\n 0,\n paramValue.length - suffix.length,\n )\n }\n\n _paramValue = decodeURIComponent(paramValue)\n matched = true\n }\n } else {\n // For optional params without prefix/suffix, we need to check if the current\n // base segment should match this optional param or a later route segment\n\n // Look ahead to see if there's a later route segment that matches the current base segment\n let shouldMatchOptional = true\n for (\n let lookAhead = routeIndex + 1;\n lookAhead < routeSegments.length;\n lookAhead++\n ) {\n const futureRouteSegment = routeSegments[lookAhead]\n if (\n futureRouteSegment?.type === SEGMENT_TYPE_PATHNAME &&\n futureRouteSegment.value === baseSegment.value\n ) {\n // The current base segment matches a future pathname segment,\n // so we should skip this optional parameter\n shouldMatchOptional = false\n break\n }\n\n // If we encounter a required param or wildcard, stop looking ahead\n if (\n futureRouteSegment?.type === SEGMENT_TYPE_PARAM ||\n futureRouteSegment?.type === SEGMENT_TYPE_WILDCARD\n ) {\n if (baseSegments.length < routeSegments.length) {\n shouldMatchOptional = false\n }\n break\n }\n }\n\n if (shouldMatchOptional) {\n // If no prefix/suffix, just decode the base segment value\n _paramValue = decodeURIComponent(baseSegment.value)\n matched = true\n }\n }\n\n if (matched) {\n params[routeSegment.value.substring(1)] = _paramValue\n baseIndex++\n }\n\n routeIndex++\n continue\n }\n }\n\n // If we have base segments left but no route segments, it's a fuzzy match\n if (baseIndex < baseSegments.length && routeIndex >= routeSegments.length) {\n params['**'] = joinPaths(\n baseSegments.slice(baseIndex).map((d) => d.value),\n )\n return !!fuzzy && routeSegments[routeSegments.length - 1]?.value !== '/'\n }\n\n // If we have route segments left but no base segments, check if remaining are optional\n if (routeIndex < routeSegments.length && baseIndex >= baseSegments.length) {\n // Check if all remaining route segments are optional\n for (let i = routeIndex; i < routeSegments.length; i++) {\n if (routeSegments[i]?.type !== SEGMENT_TYPE_OPTIONAL_PARAM) {\n return false\n }\n }\n // All remaining are optional, so we can finish\n break\n }\n\n break\n }\n\n return true\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import invariant from "tiny-invariant";
|
|
2
|
-
import { trimPathRight, trimPathLeft,
|
|
2
|
+
import { trimPathRight, trimPathLeft, parsePathname, SEGMENT_TYPE_PATHNAME, SEGMENT_TYPE_PARAM, SEGMENT_TYPE_OPTIONAL_PARAM } from "./path.js";
|
|
3
3
|
const SLASH_SCORE = 0.75;
|
|
4
4
|
const STATIC_SEGMENT_SCORE = 1;
|
|
5
5
|
const REQUIRED_PARAM_BASE_SCORE = 0.5;
|
|
@@ -30,7 +30,7 @@ function sortRoutes(routes) {
|
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
const trimmed = trimPathLeft(d.fullPath);
|
|
33
|
-
let parsed =
|
|
33
|
+
let parsed = parsePathname(trimmed);
|
|
34
34
|
let skip = 0;
|
|
35
35
|
while (parsed.length > skip + 1 && parsed[skip]?.value === "/") {
|
|
36
36
|
skip++;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-route-tree.js","sources":["../../src/process-route-tree.ts"],"sourcesContent":["import invariant from 'tiny-invariant'\nimport {\n SEGMENT_TYPE_OPTIONAL_PARAM,\n SEGMENT_TYPE_PARAM,\n SEGMENT_TYPE_PATHNAME,\n parseRoutePathSegments,\n trimPathLeft,\n trimPathRight,\n} from './path'\nimport type { Segment } from './path'\nimport type { RouteLike } from './route'\n\nconst SLASH_SCORE = 0.75\nconst STATIC_SEGMENT_SCORE = 1\nconst REQUIRED_PARAM_BASE_SCORE = 0.5\nconst OPTIONAL_PARAM_BASE_SCORE = 0.4\nconst WILDCARD_PARAM_BASE_SCORE = 0.25\nconst STATIC_AFTER_DYNAMIC_BONUS_SCORE = 0.2\nconst BOTH_PRESENCE_BASE_SCORE = 0.05\nconst PREFIX_PRESENCE_BASE_SCORE = 0.02\nconst SUFFIX_PRESENCE_BASE_SCORE = 0.01\nconst PREFIX_LENGTH_SCORE_MULTIPLIER = 0.0002\nconst SUFFIX_LENGTH_SCORE_MULTIPLIER = 0.0001\n\nfunction handleParam(segment: Segment, baseScore: number) {\n if (segment.prefixSegment && segment.suffixSegment) {\n return (\n baseScore +\n BOTH_PRESENCE_BASE_SCORE +\n PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length +\n SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length\n )\n }\n\n if (segment.prefixSegment) {\n return (\n baseScore +\n PREFIX_PRESENCE_BASE_SCORE +\n PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length\n )\n }\n\n if (segment.suffixSegment) {\n return (\n baseScore +\n SUFFIX_PRESENCE_BASE_SCORE +\n SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length\n )\n }\n\n return baseScore\n}\n\nfunction sortRoutes<TRouteLike extends RouteLike>(\n routes: ReadonlyArray<TRouteLike>,\n): Array<TRouteLike> {\n const scoredRoutes: Array<{\n child: TRouteLike\n trimmed: string\n parsed: ReadonlyArray<Segment>\n index: number\n scores: Array<number>\n hasStaticAfter: boolean\n optionalParamCount: number\n }> = []\n\n routes.forEach((d, i) => {\n if (d.isRoot || !d.path) {\n return\n }\n\n const trimmed = trimPathLeft(d.fullPath)\n let parsed = parseRoutePathSegments(trimmed)\n\n // Removes the leading slash if it is not the only remaining segment\n let skip = 0\n while (parsed.length > skip + 1 && parsed[skip]?.value === '/') {\n skip++\n }\n if (skip > 0) parsed = parsed.slice(skip)\n\n let optionalParamCount = 0\n let hasStaticAfter = false\n const scores = parsed.map((segment, index) => {\n if (segment.value === '/') {\n return SLASH_SCORE\n }\n\n if (segment.type === SEGMENT_TYPE_PATHNAME) {\n return STATIC_SEGMENT_SCORE\n }\n\n let baseScore: number | undefined = undefined\n if (segment.type === SEGMENT_TYPE_PARAM) {\n baseScore = REQUIRED_PARAM_BASE_SCORE\n } else if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n baseScore = OPTIONAL_PARAM_BASE_SCORE\n optionalParamCount++\n } else {\n baseScore = WILDCARD_PARAM_BASE_SCORE\n }\n\n // if there is any static segment (that is not an index) after a required / optional param,\n // we will boost this param so it ranks higher than a required/optional param without a static segment after it\n // JUST FOR SORTING, NOT FOR MATCHING\n for (let i = index + 1; i < parsed.length; i++) {\n const nextSegment = parsed[i]!\n if (\n nextSegment.type === SEGMENT_TYPE_PATHNAME &&\n nextSegment.value !== '/'\n ) {\n hasStaticAfter = true\n return handleParam(\n segment,\n baseScore + STATIC_AFTER_DYNAMIC_BONUS_SCORE,\n )\n }\n }\n\n return handleParam(segment, baseScore)\n })\n\n scoredRoutes.push({\n child: d,\n trimmed,\n parsed,\n index: i,\n scores,\n optionalParamCount,\n hasStaticAfter,\n })\n })\n\n const flatRoutes = scoredRoutes\n .sort((a, b) => {\n const minLength = Math.min(a.scores.length, b.scores.length)\n\n // Sort by segment-by-segment score comparison ONLY for the common prefix\n for (let i = 0; i < minLength; i++) {\n if (a.scores[i] !== b.scores[i]) {\n return b.scores[i]! - a.scores[i]!\n }\n }\n\n // If all common segments have equal scores, then consider length and specificity\n if (a.scores.length !== b.scores.length) {\n // If different number of optional parameters, fewer optional parameters wins (more specific)\n // only if both or none of the routes has static segments after the params\n if (a.optionalParamCount !== b.optionalParamCount) {\n if (a.hasStaticAfter === b.hasStaticAfter) {\n return a.optionalParamCount - b.optionalParamCount\n } else if (a.hasStaticAfter && !b.hasStaticAfter) {\n return -1\n } else if (!a.hasStaticAfter && b.hasStaticAfter) {\n return 1\n }\n }\n\n // If same number of optional parameters, longer path wins (for static segments)\n return b.scores.length - a.scores.length\n }\n\n // Sort by min available parsed value for alphabetical ordering\n for (let i = 0; i < minLength; i++) {\n if (a.parsed[i]!.value !== b.parsed[i]!.value) {\n return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1\n }\n }\n\n // Sort by original index\n return a.index - b.index\n })\n .map((d, i) => {\n d.child.rank = i\n return d.child\n })\n\n return flatRoutes\n}\n\nexport type ProcessRouteTreeResult<TRouteLike extends RouteLike> = {\n routesById: Record<string, TRouteLike>\n routesByPath: Record<string, TRouteLike>\n flatRoutes: Array<TRouteLike>\n}\n\n/**\n * Build lookup maps and a specificity-sorted flat list from a route tree.\n * Returns `routesById`, `routesByPath`, and `flatRoutes`.\n */\n/**\n * Build lookup maps and a specificity-sorted flat list from a route tree.\n * Returns `routesById`, `routesByPath`, and `flatRoutes`.\n */\nexport function processRouteTree<TRouteLike extends RouteLike>({\n routeTree,\n initRoute,\n}: {\n routeTree: TRouteLike\n initRoute?: (route: TRouteLike, index: number) => void\n}): ProcessRouteTreeResult<TRouteLike> {\n const routesById = {} as Record<string, TRouteLike>\n const routesByPath = {} as Record<string, TRouteLike>\n\n const recurseRoutes = (childRoutes: Array<TRouteLike>) => {\n childRoutes.forEach((childRoute, i) => {\n initRoute?.(childRoute, i)\n\n const existingRoute = routesById[childRoute.id]\n\n invariant(\n !existingRoute,\n `Duplicate routes found with id: ${String(childRoute.id)}`,\n )\n\n routesById[childRoute.id] = childRoute\n\n if (!childRoute.isRoot && childRoute.path) {\n const trimmedFullPath = trimPathRight(childRoute.fullPath)\n if (\n !routesByPath[trimmedFullPath] ||\n childRoute.fullPath.endsWith('/')\n ) {\n routesByPath[trimmedFullPath] = childRoute\n }\n }\n\n const children = childRoute.children as Array<TRouteLike>\n\n if (children?.length) {\n recurseRoutes(children)\n }\n })\n }\n\n recurseRoutes([routeTree])\n\n const flatRoutes = sortRoutes(Object.values(routesById))\n\n return { routesById, routesByPath, flatRoutes }\n}\n"],"names":["i"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"process-route-tree.js","sources":["../../src/process-route-tree.ts"],"sourcesContent":["import invariant from 'tiny-invariant'\nimport {\n SEGMENT_TYPE_OPTIONAL_PARAM,\n SEGMENT_TYPE_PARAM,\n SEGMENT_TYPE_PATHNAME,\n parsePathname,\n trimPathLeft,\n trimPathRight,\n} from './path'\nimport type { Segment } from './path'\nimport type { RouteLike } from './route'\n\nconst SLASH_SCORE = 0.75\nconst STATIC_SEGMENT_SCORE = 1\nconst REQUIRED_PARAM_BASE_SCORE = 0.5\nconst OPTIONAL_PARAM_BASE_SCORE = 0.4\nconst WILDCARD_PARAM_BASE_SCORE = 0.25\nconst STATIC_AFTER_DYNAMIC_BONUS_SCORE = 0.2\nconst BOTH_PRESENCE_BASE_SCORE = 0.05\nconst PREFIX_PRESENCE_BASE_SCORE = 0.02\nconst SUFFIX_PRESENCE_BASE_SCORE = 0.01\nconst PREFIX_LENGTH_SCORE_MULTIPLIER = 0.0002\nconst SUFFIX_LENGTH_SCORE_MULTIPLIER = 0.0001\n\nfunction handleParam(segment: Segment, baseScore: number) {\n if (segment.prefixSegment && segment.suffixSegment) {\n return (\n baseScore +\n BOTH_PRESENCE_BASE_SCORE +\n PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length +\n SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length\n )\n }\n\n if (segment.prefixSegment) {\n return (\n baseScore +\n PREFIX_PRESENCE_BASE_SCORE +\n PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length\n )\n }\n\n if (segment.suffixSegment) {\n return (\n baseScore +\n SUFFIX_PRESENCE_BASE_SCORE +\n SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length\n )\n }\n\n return baseScore\n}\n\nfunction sortRoutes<TRouteLike extends RouteLike>(\n routes: ReadonlyArray<TRouteLike>,\n): Array<TRouteLike> {\n const scoredRoutes: Array<{\n child: TRouteLike\n trimmed: string\n parsed: ReadonlyArray<Segment>\n index: number\n scores: Array<number>\n hasStaticAfter: boolean\n optionalParamCount: number\n }> = []\n\n routes.forEach((d, i) => {\n if (d.isRoot || !d.path) {\n return\n }\n\n const trimmed = trimPathLeft(d.fullPath)\n let parsed = parsePathname(trimmed)\n\n // Removes the leading slash if it is not the only remaining segment\n let skip = 0\n while (parsed.length > skip + 1 && parsed[skip]?.value === '/') {\n skip++\n }\n if (skip > 0) parsed = parsed.slice(skip)\n\n let optionalParamCount = 0\n let hasStaticAfter = false\n const scores = parsed.map((segment, index) => {\n if (segment.value === '/') {\n return SLASH_SCORE\n }\n\n if (segment.type === SEGMENT_TYPE_PATHNAME) {\n return STATIC_SEGMENT_SCORE\n }\n\n let baseScore: number | undefined = undefined\n if (segment.type === SEGMENT_TYPE_PARAM) {\n baseScore = REQUIRED_PARAM_BASE_SCORE\n } else if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n baseScore = OPTIONAL_PARAM_BASE_SCORE\n optionalParamCount++\n } else {\n baseScore = WILDCARD_PARAM_BASE_SCORE\n }\n\n // if there is any static segment (that is not an index) after a required / optional param,\n // we will boost this param so it ranks higher than a required/optional param without a static segment after it\n // JUST FOR SORTING, NOT FOR MATCHING\n for (let i = index + 1; i < parsed.length; i++) {\n const nextSegment = parsed[i]!\n if (\n nextSegment.type === SEGMENT_TYPE_PATHNAME &&\n nextSegment.value !== '/'\n ) {\n hasStaticAfter = true\n return handleParam(\n segment,\n baseScore + STATIC_AFTER_DYNAMIC_BONUS_SCORE,\n )\n }\n }\n\n return handleParam(segment, baseScore)\n })\n\n scoredRoutes.push({\n child: d,\n trimmed,\n parsed,\n index: i,\n scores,\n optionalParamCount,\n hasStaticAfter,\n })\n })\n\n const flatRoutes = scoredRoutes\n .sort((a, b) => {\n const minLength = Math.min(a.scores.length, b.scores.length)\n\n // Sort by segment-by-segment score comparison ONLY for the common prefix\n for (let i = 0; i < minLength; i++) {\n if (a.scores[i] !== b.scores[i]) {\n return b.scores[i]! - a.scores[i]!\n }\n }\n\n // If all common segments have equal scores, then consider length and specificity\n if (a.scores.length !== b.scores.length) {\n // If different number of optional parameters, fewer optional parameters wins (more specific)\n // only if both or none of the routes has static segments after the params\n if (a.optionalParamCount !== b.optionalParamCount) {\n if (a.hasStaticAfter === b.hasStaticAfter) {\n return a.optionalParamCount - b.optionalParamCount\n } else if (a.hasStaticAfter && !b.hasStaticAfter) {\n return -1\n } else if (!a.hasStaticAfter && b.hasStaticAfter) {\n return 1\n }\n }\n\n // If same number of optional parameters, longer path wins (for static segments)\n return b.scores.length - a.scores.length\n }\n\n // Sort by min available parsed value for alphabetical ordering\n for (let i = 0; i < minLength; i++) {\n if (a.parsed[i]!.value !== b.parsed[i]!.value) {\n return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1\n }\n }\n\n // Sort by original index\n return a.index - b.index\n })\n .map((d, i) => {\n d.child.rank = i\n return d.child\n })\n\n return flatRoutes\n}\n\nexport type ProcessRouteTreeResult<TRouteLike extends RouteLike> = {\n routesById: Record<string, TRouteLike>\n routesByPath: Record<string, TRouteLike>\n flatRoutes: Array<TRouteLike>\n}\n\n/**\n * Build lookup maps and a specificity-sorted flat list from a route tree.\n * Returns `routesById`, `routesByPath`, and `flatRoutes`.\n */\n/**\n * Build lookup maps and a specificity-sorted flat list from a route tree.\n * Returns `routesById`, `routesByPath`, and `flatRoutes`.\n */\nexport function processRouteTree<TRouteLike extends RouteLike>({\n routeTree,\n initRoute,\n}: {\n routeTree: TRouteLike\n initRoute?: (route: TRouteLike, index: number) => void\n}): ProcessRouteTreeResult<TRouteLike> {\n const routesById = {} as Record<string, TRouteLike>\n const routesByPath = {} as Record<string, TRouteLike>\n\n const recurseRoutes = (childRoutes: Array<TRouteLike>) => {\n childRoutes.forEach((childRoute, i) => {\n initRoute?.(childRoute, i)\n\n const existingRoute = routesById[childRoute.id]\n\n invariant(\n !existingRoute,\n `Duplicate routes found with id: ${String(childRoute.id)}`,\n )\n\n routesById[childRoute.id] = childRoute\n\n if (!childRoute.isRoot && childRoute.path) {\n const trimmedFullPath = trimPathRight(childRoute.fullPath)\n if (\n !routesByPath[trimmedFullPath] ||\n childRoute.fullPath.endsWith('/')\n ) {\n routesByPath[trimmedFullPath] = childRoute\n }\n }\n\n const children = childRoute.children as Array<TRouteLike>\n\n if (children?.length) {\n recurseRoutes(children)\n }\n })\n }\n\n recurseRoutes([routeTree])\n\n const flatRoutes = sortRoutes(Object.values(routesById))\n\n return { routesById, routesByPath, flatRoutes }\n}\n"],"names":["i"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/path.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { rootRouteId } from './root'
|
|
2
1
|
import { last } from './utils'
|
|
3
2
|
import type { LRUCache } from './lru-cache'
|
|
4
3
|
import type { MatchLocation } from './RouterProvider'
|
|
@@ -169,8 +168,8 @@ export function resolvePath({
|
|
|
169
168
|
trailingSlash = 'never',
|
|
170
169
|
parseCache,
|
|
171
170
|
}: ResolvePathOptions) {
|
|
172
|
-
let baseSegments =
|
|
173
|
-
const toSegments =
|
|
171
|
+
let baseSegments = parsePathname(base, parseCache).slice()
|
|
172
|
+
const toSegments = parsePathname(to, parseCache)
|
|
174
173
|
|
|
175
174
|
if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
|
|
176
175
|
baseSegments.pop()
|
|
@@ -216,16 +215,6 @@ export function resolvePath({
|
|
|
216
215
|
|
|
217
216
|
export type ParsePathnameCache = LRUCache<string, ReadonlyArray<Segment>>
|
|
218
217
|
|
|
219
|
-
export const parseBasePathSegments = (
|
|
220
|
-
pathname?: string,
|
|
221
|
-
cache?: ParsePathnameCache,
|
|
222
|
-
): ReadonlyArray<Segment> => parsePathname(pathname, cache, true)
|
|
223
|
-
|
|
224
|
-
export const parseRoutePathSegments = (
|
|
225
|
-
pathname?: string,
|
|
226
|
-
cache?: ParsePathnameCache,
|
|
227
|
-
): ReadonlyArray<Segment> => parsePathname(pathname, cache, false)
|
|
228
|
-
|
|
229
218
|
/**
|
|
230
219
|
* Parse a pathname into an array of typed segments used by the router's
|
|
231
220
|
* matcher. Results are optionally cached via an LRU cache.
|
|
@@ -233,12 +222,11 @@ export const parseRoutePathSegments = (
|
|
|
233
222
|
export const parsePathname = (
|
|
234
223
|
pathname?: string,
|
|
235
224
|
cache?: ParsePathnameCache,
|
|
236
|
-
basePathValues?: boolean,
|
|
237
225
|
): ReadonlyArray<Segment> => {
|
|
238
226
|
if (!pathname) return []
|
|
239
227
|
const cached = cache?.get(pathname)
|
|
240
228
|
if (cached) return cached
|
|
241
|
-
const parsed = baseParsePathname(pathname
|
|
229
|
+
const parsed = baseParsePathname(pathname)
|
|
242
230
|
cache?.set(pathname, parsed)
|
|
243
231
|
return parsed
|
|
244
232
|
}
|
|
@@ -268,10 +256,7 @@ const WILDCARD_W_CURLY_BRACES_RE = /^(.*?)\{\$\}(.*)$/ // prefix{$}suffix
|
|
|
268
256
|
* - `/foo/[$]{$foo} - Dynamic route with a static prefix of `$`
|
|
269
257
|
* - `/foo/{$foo}[$]` - Dynamic route with a static suffix of `$`
|
|
270
258
|
*/
|
|
271
|
-
function baseParsePathname(
|
|
272
|
-
pathname: string,
|
|
273
|
-
basePathValues?: boolean,
|
|
274
|
-
): ReadonlyArray<Segment> {
|
|
259
|
+
function baseParsePathname(pathname: string): ReadonlyArray<Segment> {
|
|
275
260
|
pathname = cleanPath(pathname)
|
|
276
261
|
|
|
277
262
|
const segments: Array<Segment> = []
|
|
@@ -293,14 +278,8 @@ function baseParsePathname(
|
|
|
293
278
|
|
|
294
279
|
segments.push(
|
|
295
280
|
...split.map((part): Segment => {
|
|
296
|
-
// strip tailing underscore for non-nested paths
|
|
297
|
-
const partToMatch =
|
|
298
|
-
!basePathValues && part !== rootRouteId && part.slice(-1) === '_'
|
|
299
|
-
? part.slice(0, -1)
|
|
300
|
-
: part
|
|
301
|
-
|
|
302
281
|
// Check for wildcard with curly braces: prefix{$}suffix
|
|
303
|
-
const wildcardBracesMatch =
|
|
282
|
+
const wildcardBracesMatch = part.match(WILDCARD_W_CURLY_BRACES_RE)
|
|
304
283
|
if (wildcardBracesMatch) {
|
|
305
284
|
const prefix = wildcardBracesMatch[1]
|
|
306
285
|
const suffix = wildcardBracesMatch[2]
|
|
@@ -313,7 +292,7 @@ function baseParsePathname(
|
|
|
313
292
|
}
|
|
314
293
|
|
|
315
294
|
// Check for optional parameter format: prefix{-$paramName}suffix
|
|
316
|
-
const optionalParamBracesMatch =
|
|
295
|
+
const optionalParamBracesMatch = part.match(
|
|
317
296
|
OPTIONAL_PARAM_W_CURLY_BRACES_RE,
|
|
318
297
|
)
|
|
319
298
|
if (optionalParamBracesMatch) {
|
|
@@ -329,7 +308,7 @@ function baseParsePathname(
|
|
|
329
308
|
}
|
|
330
309
|
|
|
331
310
|
// Check for the new parameter format: prefix{$paramName}suffix
|
|
332
|
-
const paramBracesMatch =
|
|
311
|
+
const paramBracesMatch = part.match(PARAM_W_CURLY_BRACES_RE)
|
|
333
312
|
if (paramBracesMatch) {
|
|
334
313
|
const prefix = paramBracesMatch[1]
|
|
335
314
|
const paramName = paramBracesMatch[2]
|
|
@@ -343,8 +322,8 @@ function baseParsePathname(
|
|
|
343
322
|
}
|
|
344
323
|
|
|
345
324
|
// Check for bare parameter format: $paramName (without curly braces)
|
|
346
|
-
if (PARAM_RE.test(
|
|
347
|
-
const paramName =
|
|
325
|
+
if (PARAM_RE.test(part)) {
|
|
326
|
+
const paramName = part.substring(1)
|
|
348
327
|
return {
|
|
349
328
|
type: SEGMENT_TYPE_PARAM,
|
|
350
329
|
value: '$' + paramName,
|
|
@@ -354,7 +333,7 @@ function baseParsePathname(
|
|
|
354
333
|
}
|
|
355
334
|
|
|
356
335
|
// Check for bare wildcard: $ (without curly braces)
|
|
357
|
-
if (WILDCARD_RE.test(
|
|
336
|
+
if (WILDCARD_RE.test(part)) {
|
|
358
337
|
return {
|
|
359
338
|
type: SEGMENT_TYPE_WILDCARD,
|
|
360
339
|
value: '$',
|
|
@@ -366,12 +345,12 @@ function baseParsePathname(
|
|
|
366
345
|
// Handle regular pathname segment
|
|
367
346
|
return {
|
|
368
347
|
type: SEGMENT_TYPE_PATHNAME,
|
|
369
|
-
value:
|
|
370
|
-
?
|
|
348
|
+
value: part.includes('%25')
|
|
349
|
+
? part
|
|
371
350
|
.split('%25')
|
|
372
351
|
.map((segment) => decodeURI(segment))
|
|
373
352
|
.join('%25')
|
|
374
|
-
: decodeURI(
|
|
353
|
+
: decodeURI(part),
|
|
375
354
|
}
|
|
376
355
|
}),
|
|
377
356
|
)
|
|
@@ -417,7 +396,7 @@ export function interpolatePath({
|
|
|
417
396
|
decodeCharMap,
|
|
418
397
|
parseCache,
|
|
419
398
|
}: InterpolatePathOptions): InterPolatePathResult {
|
|
420
|
-
const interpolatedPathSegments =
|
|
399
|
+
const interpolatedPathSegments = parsePathname(path, parseCache)
|
|
421
400
|
|
|
422
401
|
function encodeParam(key: string): any {
|
|
423
402
|
const value = params[key]
|
|
@@ -567,11 +546,11 @@ export function matchByPath(
|
|
|
567
546
|
const stringTo = to as string
|
|
568
547
|
|
|
569
548
|
// Parse the from and to
|
|
570
|
-
const baseSegments =
|
|
549
|
+
const baseSegments = parsePathname(
|
|
571
550
|
from.startsWith('/') ? from : `/${from}`,
|
|
572
551
|
parseCache,
|
|
573
552
|
)
|
|
574
|
-
const routeSegments =
|
|
553
|
+
const routeSegments = parsePathname(
|
|
575
554
|
stringTo.startsWith('/') ? stringTo : `/${stringTo}`,
|
|
576
555
|
parseCache,
|
|
577
556
|
)
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
SEGMENT_TYPE_OPTIONAL_PARAM,
|
|
4
4
|
SEGMENT_TYPE_PARAM,
|
|
5
5
|
SEGMENT_TYPE_PATHNAME,
|
|
6
|
-
|
|
6
|
+
parsePathname,
|
|
7
7
|
trimPathLeft,
|
|
8
8
|
trimPathRight,
|
|
9
9
|
} from './path'
|
|
@@ -70,7 +70,7 @@ function sortRoutes<TRouteLike extends RouteLike>(
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
const trimmed = trimPathLeft(d.fullPath)
|
|
73
|
-
let parsed =
|
|
73
|
+
let parsed = parsePathname(trimmed)
|
|
74
74
|
|
|
75
75
|
// Removes the leading slash if it is not the only remaining segment
|
|
76
76
|
let skip = 0
|