@tanstack/router-core 1.131.39 → 1.131.40

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.
@@ -2,9 +2,12 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const invariant = require("tiny-invariant");
4
4
  const path = require("./path.cjs");
5
+ const SLASH_SCORE = 0.75;
6
+ const STATIC_SEGMENT_SCORE = 1;
5
7
  const REQUIRED_PARAM_BASE_SCORE = 0.5;
6
8
  const OPTIONAL_PARAM_BASE_SCORE = 0.4;
7
9
  const WILDCARD_PARAM_BASE_SCORE = 0.25;
10
+ const STATIC_AFTER_DYNAMIC_BONUS_SCORE = 0.2;
8
11
  const BOTH_PRESENCE_BASE_SCORE = 0.05;
9
12
  const PREFIX_PRESENCE_BASE_SCORE = 0.02;
10
13
  const SUFFIX_PRESENCE_BASE_SCORE = 0.01;
@@ -40,7 +43,10 @@ function sortRoutes(routes) {
40
43
  let hasStaticAfter = false;
41
44
  const scores = parsed.map((segment, index) => {
42
45
  if (segment.value === "/") {
43
- return 0.75;
46
+ return SLASH_SCORE;
47
+ }
48
+ if (segment.type === path.SEGMENT_TYPE_PATHNAME) {
49
+ return STATIC_SEGMENT_SCORE;
44
50
  }
45
51
  let baseScore = void 0;
46
52
  if (segment.type === path.SEGMENT_TYPE_PARAM) {
@@ -48,20 +54,20 @@ function sortRoutes(routes) {
48
54
  } else if (segment.type === path.SEGMENT_TYPE_OPTIONAL_PARAM) {
49
55
  baseScore = OPTIONAL_PARAM_BASE_SCORE;
50
56
  optionalParamCount++;
51
- } else if (segment.type === path.SEGMENT_TYPE_WILDCARD) {
57
+ } else {
52
58
  baseScore = WILDCARD_PARAM_BASE_SCORE;
53
59
  }
54
- if (baseScore) {
55
- for (let i2 = index + 1; i2 < parsed.length; i2++) {
56
- const nextSegment = parsed[i2];
57
- if (nextSegment.type === path.SEGMENT_TYPE_PATHNAME && nextSegment.value !== "/") {
58
- hasStaticAfter = true;
59
- return handleParam(segment, baseScore + 0.2);
60
- }
60
+ for (let i2 = index + 1; i2 < parsed.length; i2++) {
61
+ const nextSegment = parsed[i2];
62
+ if (nextSegment.type === path.SEGMENT_TYPE_PATHNAME && nextSegment.value !== "/") {
63
+ hasStaticAfter = true;
64
+ return handleParam(
65
+ segment,
66
+ baseScore + STATIC_AFTER_DYNAMIC_BONUS_SCORE
67
+ );
61
68
  }
62
- return handleParam(segment, baseScore);
63
69
  }
64
- return 1;
70
+ return handleParam(segment, baseScore);
65
71
  });
66
72
  scoredRoutes.push({
67
73
  child: d,
@@ -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 SEGMENT_TYPE_WILDCARD,\n parsePathname,\n trimPathLeft,\n trimPathRight,\n} from './path'\nimport type { Segment } from './path'\nimport type { RouteLike } from './route'\n\nconst REQUIRED_PARAM_BASE_SCORE = 0.5\nconst OPTIONAL_PARAM_BASE_SCORE = 0.4\nconst WILDCARD_PARAM_BASE_SCORE = 0.25\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 0.75\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 if (segment.type === SEGMENT_TYPE_WILDCARD) {\n baseScore = WILDCARD_PARAM_BASE_SCORE\n }\n\n if (baseScore) {\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(segment, baseScore + 0.2)\n }\n }\n\n return handleParam(segment, baseScore)\n }\n\n return 1\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\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_PARAM","SEGMENT_TYPE_OPTIONAL_PARAM","SEGMENT_TYPE_WILDCARD","i","SEGMENT_TYPE_PATHNAME","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\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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"}
@@ -1,8 +1,11 @@
1
1
  import invariant from "tiny-invariant";
2
- import { trimPathRight, trimPathLeft, parsePathname, SEGMENT_TYPE_PARAM, SEGMENT_TYPE_OPTIONAL_PARAM, SEGMENT_TYPE_WILDCARD, SEGMENT_TYPE_PATHNAME } from "./path.js";
2
+ import { trimPathRight, trimPathLeft, parsePathname, SEGMENT_TYPE_PATHNAME, SEGMENT_TYPE_PARAM, SEGMENT_TYPE_OPTIONAL_PARAM } from "./path.js";
3
+ const SLASH_SCORE = 0.75;
4
+ const STATIC_SEGMENT_SCORE = 1;
3
5
  const REQUIRED_PARAM_BASE_SCORE = 0.5;
4
6
  const OPTIONAL_PARAM_BASE_SCORE = 0.4;
5
7
  const WILDCARD_PARAM_BASE_SCORE = 0.25;
8
+ const STATIC_AFTER_DYNAMIC_BONUS_SCORE = 0.2;
6
9
  const BOTH_PRESENCE_BASE_SCORE = 0.05;
7
10
  const PREFIX_PRESENCE_BASE_SCORE = 0.02;
8
11
  const SUFFIX_PRESENCE_BASE_SCORE = 0.01;
@@ -38,7 +41,10 @@ function sortRoutes(routes) {
38
41
  let hasStaticAfter = false;
39
42
  const scores = parsed.map((segment, index) => {
40
43
  if (segment.value === "/") {
41
- return 0.75;
44
+ return SLASH_SCORE;
45
+ }
46
+ if (segment.type === SEGMENT_TYPE_PATHNAME) {
47
+ return STATIC_SEGMENT_SCORE;
42
48
  }
43
49
  let baseScore = void 0;
44
50
  if (segment.type === SEGMENT_TYPE_PARAM) {
@@ -46,20 +52,20 @@ function sortRoutes(routes) {
46
52
  } else if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {
47
53
  baseScore = OPTIONAL_PARAM_BASE_SCORE;
48
54
  optionalParamCount++;
49
- } else if (segment.type === SEGMENT_TYPE_WILDCARD) {
55
+ } else {
50
56
  baseScore = WILDCARD_PARAM_BASE_SCORE;
51
57
  }
52
- if (baseScore) {
53
- for (let i2 = index + 1; i2 < parsed.length; i2++) {
54
- const nextSegment = parsed[i2];
55
- if (nextSegment.type === SEGMENT_TYPE_PATHNAME && nextSegment.value !== "/") {
56
- hasStaticAfter = true;
57
- return handleParam(segment, baseScore + 0.2);
58
- }
58
+ for (let i2 = index + 1; i2 < parsed.length; i2++) {
59
+ const nextSegment = parsed[i2];
60
+ if (nextSegment.type === SEGMENT_TYPE_PATHNAME && nextSegment.value !== "/") {
61
+ hasStaticAfter = true;
62
+ return handleParam(
63
+ segment,
64
+ baseScore + STATIC_AFTER_DYNAMIC_BONUS_SCORE
65
+ );
59
66
  }
60
- return handleParam(segment, baseScore);
61
67
  }
62
- return 1;
68
+ return handleParam(segment, baseScore);
63
69
  });
64
70
  scoredRoutes.push({
65
71
  child: d,
@@ -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 SEGMENT_TYPE_WILDCARD,\n parsePathname,\n trimPathLeft,\n trimPathRight,\n} from './path'\nimport type { Segment } from './path'\nimport type { RouteLike } from './route'\n\nconst REQUIRED_PARAM_BASE_SCORE = 0.5\nconst OPTIONAL_PARAM_BASE_SCORE = 0.4\nconst WILDCARD_PARAM_BASE_SCORE = 0.25\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 0.75\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 if (segment.type === SEGMENT_TYPE_WILDCARD) {\n baseScore = WILDCARD_PARAM_BASE_SCORE\n }\n\n if (baseScore) {\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(segment, baseScore + 0.2)\n }\n }\n\n return handleParam(segment, baseScore)\n }\n\n return 1\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\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\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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/router-core",
3
- "version": "1.131.39",
3
+ "version": "1.131.40",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -3,7 +3,6 @@ import {
3
3
  SEGMENT_TYPE_OPTIONAL_PARAM,
4
4
  SEGMENT_TYPE_PARAM,
5
5
  SEGMENT_TYPE_PATHNAME,
6
- SEGMENT_TYPE_WILDCARD,
7
6
  parsePathname,
8
7
  trimPathLeft,
9
8
  trimPathRight,
@@ -11,9 +10,12 @@ import {
11
10
  import type { Segment } from './path'
12
11
  import type { RouteLike } from './route'
13
12
 
13
+ const SLASH_SCORE = 0.75
14
+ const STATIC_SEGMENT_SCORE = 1
14
15
  const REQUIRED_PARAM_BASE_SCORE = 0.5
15
16
  const OPTIONAL_PARAM_BASE_SCORE = 0.4
16
17
  const WILDCARD_PARAM_BASE_SCORE = 0.25
18
+ const STATIC_AFTER_DYNAMIC_BONUS_SCORE = 0.2
17
19
  const BOTH_PRESENCE_BASE_SCORE = 0.05
18
20
  const PREFIX_PRESENCE_BASE_SCORE = 0.02
19
21
  const SUFFIX_PRESENCE_BASE_SCORE = 0.01
@@ -81,7 +83,11 @@ function sortRoutes<TRouteLike extends RouteLike>(
81
83
  let hasStaticAfter = false
82
84
  const scores = parsed.map((segment, index) => {
83
85
  if (segment.value === '/') {
84
- return 0.75
86
+ return SLASH_SCORE
87
+ }
88
+
89
+ if (segment.type === SEGMENT_TYPE_PATHNAME) {
90
+ return STATIC_SEGMENT_SCORE
85
91
  }
86
92
 
87
93
  let baseScore: number | undefined = undefined
@@ -90,29 +96,28 @@ function sortRoutes<TRouteLike extends RouteLike>(
90
96
  } else if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {
91
97
  baseScore = OPTIONAL_PARAM_BASE_SCORE
92
98
  optionalParamCount++
93
- } else if (segment.type === SEGMENT_TYPE_WILDCARD) {
99
+ } else {
94
100
  baseScore = WILDCARD_PARAM_BASE_SCORE
95
101
  }
96
102
 
97
- if (baseScore) {
98
- // if there is any static segment (that is not an index) after a required / optional param,
99
- // we will boost this param so it ranks higher than a required/optional param without a static segment after it
100
- // JUST FOR SORTING, NOT FOR MATCHING
101
- for (let i = index + 1; i < parsed.length; i++) {
102
- const nextSegment = parsed[i]!
103
- if (
104
- nextSegment.type === SEGMENT_TYPE_PATHNAME &&
105
- nextSegment.value !== '/'
106
- ) {
107
- hasStaticAfter = true
108
- return handleParam(segment, baseScore + 0.2)
109
- }
103
+ // if there is any static segment (that is not an index) after a required / optional param,
104
+ // we will boost this param so it ranks higher than a required/optional param without a static segment after it
105
+ // JUST FOR SORTING, NOT FOR MATCHING
106
+ for (let i = index + 1; i < parsed.length; i++) {
107
+ const nextSegment = parsed[i]!
108
+ if (
109
+ nextSegment.type === SEGMENT_TYPE_PATHNAME &&
110
+ nextSegment.value !== '/'
111
+ ) {
112
+ hasStaticAfter = true
113
+ return handleParam(
114
+ segment,
115
+ baseScore + STATIC_AFTER_DYNAMIC_BONUS_SCORE,
116
+ )
110
117
  }
111
-
112
- return handleParam(segment, baseScore)
113
118
  }
114
119
 
115
- return 1
120
+ return handleParam(segment, baseScore)
116
121
  })
117
122
 
118
123
  scoredRoutes.push({