@tanstack/router-core 1.131.37 → 1.131.39
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/index.cjs +2 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +4 -2
- package/dist/cjs/process-route-tree.cjs +139 -0
- package/dist/cjs/process-route-tree.cjs.map +1 -0
- package/dist/cjs/process-route-tree.d.cts +10 -0
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +12 -0
- package/dist/cjs/router.cjs +19 -166
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +1 -22
- package/dist/esm/index.d.ts +4 -2
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/process-route-tree.d.ts +10 -0
- package/dist/esm/process-route-tree.js +139 -0
- package/dist/esm/process-route-tree.js.map +1 -0
- package/dist/esm/route.d.ts +12 -0
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +1 -22
- package/dist/esm/router.js +20 -167
- package/dist/esm/router.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -3
- package/src/process-route-tree.ts +228 -0
- package/src/route.ts +13 -0
- package/src/router.ts +31 -282
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import invariant from 'tiny-invariant'
|
|
2
|
+
import {
|
|
3
|
+
SEGMENT_TYPE_OPTIONAL_PARAM,
|
|
4
|
+
SEGMENT_TYPE_PARAM,
|
|
5
|
+
SEGMENT_TYPE_PATHNAME,
|
|
6
|
+
SEGMENT_TYPE_WILDCARD,
|
|
7
|
+
parsePathname,
|
|
8
|
+
trimPathLeft,
|
|
9
|
+
trimPathRight,
|
|
10
|
+
} from './path'
|
|
11
|
+
import type { Segment } from './path'
|
|
12
|
+
import type { RouteLike } from './route'
|
|
13
|
+
|
|
14
|
+
const REQUIRED_PARAM_BASE_SCORE = 0.5
|
|
15
|
+
const OPTIONAL_PARAM_BASE_SCORE = 0.4
|
|
16
|
+
const WILDCARD_PARAM_BASE_SCORE = 0.25
|
|
17
|
+
const BOTH_PRESENCE_BASE_SCORE = 0.05
|
|
18
|
+
const PREFIX_PRESENCE_BASE_SCORE = 0.02
|
|
19
|
+
const SUFFIX_PRESENCE_BASE_SCORE = 0.01
|
|
20
|
+
const PREFIX_LENGTH_SCORE_MULTIPLIER = 0.0002
|
|
21
|
+
const SUFFIX_LENGTH_SCORE_MULTIPLIER = 0.0001
|
|
22
|
+
|
|
23
|
+
function handleParam(segment: Segment, baseScore: number) {
|
|
24
|
+
if (segment.prefixSegment && segment.suffixSegment) {
|
|
25
|
+
return (
|
|
26
|
+
baseScore +
|
|
27
|
+
BOTH_PRESENCE_BASE_SCORE +
|
|
28
|
+
PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length +
|
|
29
|
+
SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (segment.prefixSegment) {
|
|
34
|
+
return (
|
|
35
|
+
baseScore +
|
|
36
|
+
PREFIX_PRESENCE_BASE_SCORE +
|
|
37
|
+
PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (segment.suffixSegment) {
|
|
42
|
+
return (
|
|
43
|
+
baseScore +
|
|
44
|
+
SUFFIX_PRESENCE_BASE_SCORE +
|
|
45
|
+
SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return baseScore
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function sortRoutes<TRouteLike extends RouteLike>(
|
|
53
|
+
routes: ReadonlyArray<TRouteLike>,
|
|
54
|
+
): Array<TRouteLike> {
|
|
55
|
+
const scoredRoutes: Array<{
|
|
56
|
+
child: TRouteLike
|
|
57
|
+
trimmed: string
|
|
58
|
+
parsed: ReadonlyArray<Segment>
|
|
59
|
+
index: number
|
|
60
|
+
scores: Array<number>
|
|
61
|
+
hasStaticAfter: boolean
|
|
62
|
+
optionalParamCount: number
|
|
63
|
+
}> = []
|
|
64
|
+
|
|
65
|
+
routes.forEach((d, i) => {
|
|
66
|
+
if (d.isRoot || !d.path) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const trimmed = trimPathLeft(d.fullPath)
|
|
71
|
+
let parsed = parsePathname(trimmed)
|
|
72
|
+
|
|
73
|
+
// Removes the leading slash if it is not the only remaining segment
|
|
74
|
+
let skip = 0
|
|
75
|
+
while (parsed.length > skip + 1 && parsed[skip]?.value === '/') {
|
|
76
|
+
skip++
|
|
77
|
+
}
|
|
78
|
+
if (skip > 0) parsed = parsed.slice(skip)
|
|
79
|
+
|
|
80
|
+
let optionalParamCount = 0
|
|
81
|
+
let hasStaticAfter = false
|
|
82
|
+
const scores = parsed.map((segment, index) => {
|
|
83
|
+
if (segment.value === '/') {
|
|
84
|
+
return 0.75
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let baseScore: number | undefined = undefined
|
|
88
|
+
if (segment.type === SEGMENT_TYPE_PARAM) {
|
|
89
|
+
baseScore = REQUIRED_PARAM_BASE_SCORE
|
|
90
|
+
} else if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {
|
|
91
|
+
baseScore = OPTIONAL_PARAM_BASE_SCORE
|
|
92
|
+
optionalParamCount++
|
|
93
|
+
} else if (segment.type === SEGMENT_TYPE_WILDCARD) {
|
|
94
|
+
baseScore = WILDCARD_PARAM_BASE_SCORE
|
|
95
|
+
}
|
|
96
|
+
|
|
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
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return handleParam(segment, baseScore)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return 1
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
scoredRoutes.push({
|
|
119
|
+
child: d,
|
|
120
|
+
trimmed,
|
|
121
|
+
parsed,
|
|
122
|
+
index: i,
|
|
123
|
+
scores,
|
|
124
|
+
optionalParamCount,
|
|
125
|
+
hasStaticAfter,
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const flatRoutes = scoredRoutes
|
|
130
|
+
.sort((a, b) => {
|
|
131
|
+
const minLength = Math.min(a.scores.length, b.scores.length)
|
|
132
|
+
|
|
133
|
+
// Sort by segment-by-segment score comparison ONLY for the common prefix
|
|
134
|
+
for (let i = 0; i < minLength; i++) {
|
|
135
|
+
if (a.scores[i] !== b.scores[i]) {
|
|
136
|
+
return b.scores[i]! - a.scores[i]!
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// If all common segments have equal scores, then consider length and specificity
|
|
141
|
+
if (a.scores.length !== b.scores.length) {
|
|
142
|
+
// If different number of optional parameters, fewer optional parameters wins (more specific)
|
|
143
|
+
// only if both or none of the routes has static segments after the params
|
|
144
|
+
if (a.optionalParamCount !== b.optionalParamCount) {
|
|
145
|
+
if (a.hasStaticAfter === b.hasStaticAfter) {
|
|
146
|
+
return a.optionalParamCount - b.optionalParamCount
|
|
147
|
+
} else if (a.hasStaticAfter && !b.hasStaticAfter) {
|
|
148
|
+
return -1
|
|
149
|
+
} else if (!a.hasStaticAfter && b.hasStaticAfter) {
|
|
150
|
+
return 1
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// If same number of optional parameters, longer path wins (for static segments)
|
|
155
|
+
return b.scores.length - a.scores.length
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Sort by min available parsed value for alphabetical ordering
|
|
159
|
+
for (let i = 0; i < minLength; i++) {
|
|
160
|
+
if (a.parsed[i]!.value !== b.parsed[i]!.value) {
|
|
161
|
+
return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Sort by original index
|
|
166
|
+
return a.index - b.index
|
|
167
|
+
})
|
|
168
|
+
.map((d, i) => {
|
|
169
|
+
d.child.rank = i
|
|
170
|
+
return d.child
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
return flatRoutes
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export type ProcessRouteTreeResult<TRouteLike extends RouteLike> = {
|
|
177
|
+
routesById: Record<string, TRouteLike>
|
|
178
|
+
routesByPath: Record<string, TRouteLike>
|
|
179
|
+
flatRoutes: Array<TRouteLike>
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function processRouteTree<TRouteLike extends RouteLike>({
|
|
183
|
+
routeTree,
|
|
184
|
+
initRoute,
|
|
185
|
+
}: {
|
|
186
|
+
routeTree: TRouteLike
|
|
187
|
+
initRoute?: (route: TRouteLike, index: number) => void
|
|
188
|
+
}): ProcessRouteTreeResult<TRouteLike> {
|
|
189
|
+
const routesById = {} as Record<string, TRouteLike>
|
|
190
|
+
const routesByPath = {} as Record<string, TRouteLike>
|
|
191
|
+
|
|
192
|
+
const recurseRoutes = (childRoutes: Array<TRouteLike>) => {
|
|
193
|
+
childRoutes.forEach((childRoute, i) => {
|
|
194
|
+
initRoute?.(childRoute, i)
|
|
195
|
+
|
|
196
|
+
const existingRoute = routesById[childRoute.id]
|
|
197
|
+
|
|
198
|
+
invariant(
|
|
199
|
+
!existingRoute,
|
|
200
|
+
`Duplicate routes found with id: ${String(childRoute.id)}`,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
routesById[childRoute.id] = childRoute
|
|
204
|
+
|
|
205
|
+
if (!childRoute.isRoot && childRoute.path) {
|
|
206
|
+
const trimmedFullPath = trimPathRight(childRoute.fullPath)
|
|
207
|
+
if (
|
|
208
|
+
!routesByPath[trimmedFullPath] ||
|
|
209
|
+
childRoute.fullPath.endsWith('/')
|
|
210
|
+
) {
|
|
211
|
+
routesByPath[trimmedFullPath] = childRoute
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const children = childRoute.children as Array<TRouteLike>
|
|
216
|
+
|
|
217
|
+
if (children?.length) {
|
|
218
|
+
recurseRoutes(children)
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
recurseRoutes([routeTree])
|
|
224
|
+
|
|
225
|
+
const flatRoutes = sortRoutes(Object.values(routesById))
|
|
226
|
+
|
|
227
|
+
return { routesById, routesByPath, flatRoutes }
|
|
228
|
+
}
|
package/src/route.ts
CHANGED
|
@@ -1749,3 +1749,16 @@ export class BaseRootRoute<
|
|
|
1749
1749
|
}
|
|
1750
1750
|
|
|
1751
1751
|
//
|
|
1752
|
+
|
|
1753
|
+
export interface RouteLike {
|
|
1754
|
+
id: string
|
|
1755
|
+
isRoot?: boolean
|
|
1756
|
+
path?: string
|
|
1757
|
+
fullPath: string
|
|
1758
|
+
rank?: number
|
|
1759
|
+
parentRoute?: RouteLike
|
|
1760
|
+
children?: Array<RouteLike>
|
|
1761
|
+
options?: {
|
|
1762
|
+
caseSensitive?: boolean
|
|
1763
|
+
}
|
|
1764
|
+
}
|
package/src/router.ts
CHANGED
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
createMemoryHistory,
|
|
5
5
|
parseHref,
|
|
6
6
|
} from '@tanstack/history'
|
|
7
|
-
import invariant from 'tiny-invariant'
|
|
8
7
|
import {
|
|
9
8
|
createControlledPromise,
|
|
10
9
|
deepEqual,
|
|
@@ -13,19 +12,14 @@ import {
|
|
|
13
12
|
last,
|
|
14
13
|
replaceEqualDeep,
|
|
15
14
|
} from './utils'
|
|
15
|
+
import { processRouteTree } from './process-route-tree'
|
|
16
16
|
import {
|
|
17
|
-
SEGMENT_TYPE_OPTIONAL_PARAM,
|
|
18
|
-
SEGMENT_TYPE_PARAM,
|
|
19
|
-
SEGMENT_TYPE_PATHNAME,
|
|
20
|
-
SEGMENT_TYPE_WILDCARD,
|
|
21
17
|
cleanPath,
|
|
22
18
|
interpolatePath,
|
|
23
19
|
joinPaths,
|
|
24
20
|
matchPathname,
|
|
25
|
-
parsePathname,
|
|
26
21
|
resolvePath,
|
|
27
22
|
trimPath,
|
|
28
|
-
trimPathLeft,
|
|
29
23
|
trimPathRight,
|
|
30
24
|
} from './path'
|
|
31
25
|
import { isNotFound } from './not-found'
|
|
@@ -35,7 +29,7 @@ import { rootRouteId } from './root'
|
|
|
35
29
|
import { isRedirect, redirect } from './redirect'
|
|
36
30
|
import { createLRUCache } from './lru-cache'
|
|
37
31
|
import { loadMatches, loadRouteChunk, routeNeedsPreload } from './load-matches'
|
|
38
|
-
import type { ParsePathnameCache
|
|
32
|
+
import type { ParsePathnameCache } from './path'
|
|
39
33
|
import type { SearchParser, SearchSerializer } from './searchParams'
|
|
40
34
|
import type { AnyRedirect, ResolvedRedirect } from './redirect'
|
|
41
35
|
import type {
|
|
@@ -59,6 +53,7 @@ import type {
|
|
|
59
53
|
AnyRouteWithContext,
|
|
60
54
|
MakeRemountDepsOptionsUnion,
|
|
61
55
|
RouteContextOptions,
|
|
56
|
+
RouteLike,
|
|
62
57
|
RouteMask,
|
|
63
58
|
SearchMiddleware,
|
|
64
59
|
} from './route'
|
|
@@ -1111,33 +1106,6 @@ export class RouterCore<
|
|
|
1111
1106
|
return rootRouteId
|
|
1112
1107
|
})()
|
|
1113
1108
|
|
|
1114
|
-
const parseErrors = matchedRoutes.map((route) => {
|
|
1115
|
-
let parsedParamsError
|
|
1116
|
-
|
|
1117
|
-
const parseParams =
|
|
1118
|
-
route.options.params?.parse ?? route.options.parseParams
|
|
1119
|
-
|
|
1120
|
-
if (parseParams) {
|
|
1121
|
-
try {
|
|
1122
|
-
const parsedParams = parseParams(routeParams)
|
|
1123
|
-
// Add the parsed params to the accumulated params bag
|
|
1124
|
-
Object.assign(routeParams, parsedParams)
|
|
1125
|
-
} catch (err: any) {
|
|
1126
|
-
parsedParamsError = new PathParamError(err.message, {
|
|
1127
|
-
cause: err,
|
|
1128
|
-
})
|
|
1129
|
-
|
|
1130
|
-
if (opts?.throwOnError) {
|
|
1131
|
-
throw parsedParamsError
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
return parsedParamsError
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
return
|
|
1139
|
-
})
|
|
1140
|
-
|
|
1141
1109
|
const matches: Array<AnyRouteMatch> = []
|
|
1142
1110
|
|
|
1143
1111
|
const getParentContext = (parentMatch?: AnyRouteMatch) => {
|
|
@@ -1224,20 +1192,36 @@ export class RouterCore<
|
|
|
1224
1192
|
parseCache: this.parsePathnameCache,
|
|
1225
1193
|
})
|
|
1226
1194
|
|
|
1227
|
-
|
|
1195
|
+
// Waste not, want not. If we already have a match for this route,
|
|
1196
|
+
// reuse it. This is important for layout routes, which might stick
|
|
1197
|
+
// around between navigation actions that only change leaf routes.
|
|
1198
|
+
|
|
1199
|
+
// Existing matches are matches that are already loaded along with
|
|
1200
|
+
// pending matches that are still loading
|
|
1201
|
+
const matchId = interpolatePathResult.interpolatedPath + loaderDepsHash
|
|
1228
1202
|
|
|
1229
|
-
|
|
1203
|
+
const existingMatch = this.getMatch(matchId)
|
|
1230
1204
|
|
|
1231
|
-
const
|
|
1232
|
-
|
|
1205
|
+
const previousMatch = this.state.matches.find(
|
|
1206
|
+
(d) => d.routeId === route.id,
|
|
1207
|
+
)
|
|
1233
1208
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1209
|
+
const strictParams =
|
|
1210
|
+
existingMatch?._strictParams ?? interpolatePathResult.usedParams
|
|
1211
|
+
|
|
1212
|
+
let paramsError: PathParamError | undefined = undefined
|
|
1213
|
+
|
|
1214
|
+
if (!existingMatch) {
|
|
1215
|
+
const strictParseParams =
|
|
1216
|
+
route.options.params?.parse ?? route.options.parseParams
|
|
1217
|
+
|
|
1218
|
+
if (strictParseParams) {
|
|
1219
|
+
try {
|
|
1220
|
+
Object.assign(
|
|
1221
|
+
strictParams,
|
|
1222
|
+
strictParseParams(strictParams as Record<string, string>),
|
|
1223
|
+
)
|
|
1224
|
+
} catch (err: any) {
|
|
1241
1225
|
paramsError = new PathParamError(err.message, {
|
|
1242
1226
|
cause: err,
|
|
1243
1227
|
})
|
|
@@ -1249,19 +1233,7 @@ export class RouterCore<
|
|
|
1249
1233
|
}
|
|
1250
1234
|
}
|
|
1251
1235
|
|
|
1252
|
-
|
|
1253
|
-
// reuse it. This is important for layout routes, which might stick
|
|
1254
|
-
// around between navigation actions that only change leaf routes.
|
|
1255
|
-
|
|
1256
|
-
// Existing matches are matches that are already loaded along with
|
|
1257
|
-
// pending matches that are still loading
|
|
1258
|
-
const matchId = interpolatePathResult.interpolatedPath + loaderDepsHash
|
|
1259
|
-
|
|
1260
|
-
const existingMatch = this.getMatch(matchId)
|
|
1261
|
-
|
|
1262
|
-
const previousMatch = this.state.matches.find(
|
|
1263
|
-
(d) => d.routeId === route.id,
|
|
1264
|
-
)
|
|
1236
|
+
Object.assign(routeParams, strictParams)
|
|
1265
1237
|
|
|
1266
1238
|
const cause = previousMatch ? 'stay' : 'enter'
|
|
1267
1239
|
|
|
@@ -2406,229 +2378,6 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
|
|
|
2406
2378
|
return {}
|
|
2407
2379
|
}
|
|
2408
2380
|
|
|
2409
|
-
interface RouteLike {
|
|
2410
|
-
id: string
|
|
2411
|
-
isRoot?: boolean
|
|
2412
|
-
path?: string
|
|
2413
|
-
fullPath: string
|
|
2414
|
-
rank?: number
|
|
2415
|
-
parentRoute?: RouteLike
|
|
2416
|
-
children?: Array<RouteLike>
|
|
2417
|
-
options?: {
|
|
2418
|
-
caseSensitive?: boolean
|
|
2419
|
-
}
|
|
2420
|
-
}
|
|
2421
|
-
|
|
2422
|
-
export type ProcessRouteTreeResult<TRouteLike extends RouteLike> = {
|
|
2423
|
-
routesById: Record<string, TRouteLike>
|
|
2424
|
-
routesByPath: Record<string, TRouteLike>
|
|
2425
|
-
flatRoutes: Array<TRouteLike>
|
|
2426
|
-
}
|
|
2427
|
-
|
|
2428
|
-
const REQUIRED_PARAM_BASE_SCORE = 0.5
|
|
2429
|
-
const OPTIONAL_PARAM_BASE_SCORE = 0.4
|
|
2430
|
-
const WILDCARD_PARAM_BASE_SCORE = 0.25
|
|
2431
|
-
const BOTH_PRESENCE_BASE_SCORE = 0.05
|
|
2432
|
-
const PREFIX_PRESENCE_BASE_SCORE = 0.02
|
|
2433
|
-
const SUFFIX_PRESENCE_BASE_SCORE = 0.01
|
|
2434
|
-
const PREFIX_LENGTH_SCORE_MULTIPLIER = 0.0002
|
|
2435
|
-
const SUFFIX_LENGTH_SCORE_MULTIPLIER = 0.0001
|
|
2436
|
-
|
|
2437
|
-
function handleParam(segment: Segment, baseScore: number) {
|
|
2438
|
-
if (segment.prefixSegment && segment.suffixSegment) {
|
|
2439
|
-
return (
|
|
2440
|
-
baseScore +
|
|
2441
|
-
BOTH_PRESENCE_BASE_SCORE +
|
|
2442
|
-
PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length +
|
|
2443
|
-
SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length
|
|
2444
|
-
)
|
|
2445
|
-
}
|
|
2446
|
-
|
|
2447
|
-
if (segment.prefixSegment) {
|
|
2448
|
-
return (
|
|
2449
|
-
baseScore +
|
|
2450
|
-
PREFIX_PRESENCE_BASE_SCORE +
|
|
2451
|
-
PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length
|
|
2452
|
-
)
|
|
2453
|
-
}
|
|
2454
|
-
|
|
2455
|
-
if (segment.suffixSegment) {
|
|
2456
|
-
return (
|
|
2457
|
-
baseScore +
|
|
2458
|
-
SUFFIX_PRESENCE_BASE_SCORE +
|
|
2459
|
-
SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length
|
|
2460
|
-
)
|
|
2461
|
-
}
|
|
2462
|
-
|
|
2463
|
-
return baseScore
|
|
2464
|
-
}
|
|
2465
|
-
|
|
2466
|
-
export function processRouteTree<TRouteLike extends RouteLike>({
|
|
2467
|
-
routeTree,
|
|
2468
|
-
initRoute,
|
|
2469
|
-
}: {
|
|
2470
|
-
routeTree: TRouteLike
|
|
2471
|
-
initRoute?: (route: TRouteLike, index: number) => void
|
|
2472
|
-
}): ProcessRouteTreeResult<TRouteLike> {
|
|
2473
|
-
const routesById = {} as Record<string, TRouteLike>
|
|
2474
|
-
const routesByPath = {} as Record<string, TRouteLike>
|
|
2475
|
-
|
|
2476
|
-
const recurseRoutes = (childRoutes: Array<TRouteLike>) => {
|
|
2477
|
-
childRoutes.forEach((childRoute, i) => {
|
|
2478
|
-
initRoute?.(childRoute, i)
|
|
2479
|
-
|
|
2480
|
-
const existingRoute = routesById[childRoute.id]
|
|
2481
|
-
|
|
2482
|
-
invariant(
|
|
2483
|
-
!existingRoute,
|
|
2484
|
-
`Duplicate routes found with id: ${String(childRoute.id)}`,
|
|
2485
|
-
)
|
|
2486
|
-
|
|
2487
|
-
routesById[childRoute.id] = childRoute
|
|
2488
|
-
|
|
2489
|
-
if (!childRoute.isRoot && childRoute.path) {
|
|
2490
|
-
const trimmedFullPath = trimPathRight(childRoute.fullPath)
|
|
2491
|
-
if (
|
|
2492
|
-
!routesByPath[trimmedFullPath] ||
|
|
2493
|
-
childRoute.fullPath.endsWith('/')
|
|
2494
|
-
) {
|
|
2495
|
-
routesByPath[trimmedFullPath] = childRoute
|
|
2496
|
-
}
|
|
2497
|
-
}
|
|
2498
|
-
|
|
2499
|
-
const children = childRoute.children as Array<TRouteLike>
|
|
2500
|
-
|
|
2501
|
-
if (children?.length) {
|
|
2502
|
-
recurseRoutes(children)
|
|
2503
|
-
}
|
|
2504
|
-
})
|
|
2505
|
-
}
|
|
2506
|
-
|
|
2507
|
-
recurseRoutes([routeTree])
|
|
2508
|
-
|
|
2509
|
-
const scoredRoutes: Array<{
|
|
2510
|
-
child: TRouteLike
|
|
2511
|
-
trimmed: string
|
|
2512
|
-
parsed: ReadonlyArray<Segment>
|
|
2513
|
-
index: number
|
|
2514
|
-
scores: Array<number>
|
|
2515
|
-
hasStaticAfter: boolean
|
|
2516
|
-
optionalParamCount: number
|
|
2517
|
-
}> = []
|
|
2518
|
-
|
|
2519
|
-
const routes: Array<TRouteLike> = Object.values(routesById)
|
|
2520
|
-
|
|
2521
|
-
routes.forEach((d, i) => {
|
|
2522
|
-
if (d.isRoot || !d.path) {
|
|
2523
|
-
return
|
|
2524
|
-
}
|
|
2525
|
-
|
|
2526
|
-
const trimmed = trimPathLeft(d.fullPath)
|
|
2527
|
-
let parsed = parsePathname(trimmed)
|
|
2528
|
-
|
|
2529
|
-
// Removes the leading slash if it is not the only remaining segment
|
|
2530
|
-
let skip = 0
|
|
2531
|
-
while (parsed.length > skip + 1 && parsed[skip]?.value === '/') {
|
|
2532
|
-
skip++
|
|
2533
|
-
}
|
|
2534
|
-
if (skip > 0) parsed = parsed.slice(skip)
|
|
2535
|
-
|
|
2536
|
-
let optionalParamCount = 0
|
|
2537
|
-
let hasStaticAfter = false
|
|
2538
|
-
const scores = parsed.map((segment, index) => {
|
|
2539
|
-
if (segment.value === '/') {
|
|
2540
|
-
return 0.75
|
|
2541
|
-
}
|
|
2542
|
-
|
|
2543
|
-
let baseScore: number | undefined = undefined
|
|
2544
|
-
if (segment.type === SEGMENT_TYPE_PARAM) {
|
|
2545
|
-
baseScore = REQUIRED_PARAM_BASE_SCORE
|
|
2546
|
-
} else if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {
|
|
2547
|
-
baseScore = OPTIONAL_PARAM_BASE_SCORE
|
|
2548
|
-
optionalParamCount++
|
|
2549
|
-
} else if (segment.type === SEGMENT_TYPE_WILDCARD) {
|
|
2550
|
-
baseScore = WILDCARD_PARAM_BASE_SCORE
|
|
2551
|
-
}
|
|
2552
|
-
|
|
2553
|
-
if (baseScore) {
|
|
2554
|
-
// if there is any static segment (that is not an index) after a required / optional param,
|
|
2555
|
-
// we will boost this param so it ranks higher than a required/optional param without a static segment after it
|
|
2556
|
-
// JUST FOR SORTING, NOT FOR MATCHING
|
|
2557
|
-
for (let i = index + 1; i < parsed.length; i++) {
|
|
2558
|
-
const nextSegment = parsed[i]!
|
|
2559
|
-
if (
|
|
2560
|
-
nextSegment.type === SEGMENT_TYPE_PATHNAME &&
|
|
2561
|
-
nextSegment.value !== '/'
|
|
2562
|
-
) {
|
|
2563
|
-
hasStaticAfter = true
|
|
2564
|
-
return handleParam(segment, baseScore + 0.2)
|
|
2565
|
-
}
|
|
2566
|
-
}
|
|
2567
|
-
|
|
2568
|
-
return handleParam(segment, baseScore)
|
|
2569
|
-
}
|
|
2570
|
-
|
|
2571
|
-
return 1
|
|
2572
|
-
})
|
|
2573
|
-
|
|
2574
|
-
scoredRoutes.push({
|
|
2575
|
-
child: d,
|
|
2576
|
-
trimmed,
|
|
2577
|
-
parsed,
|
|
2578
|
-
index: i,
|
|
2579
|
-
scores,
|
|
2580
|
-
optionalParamCount,
|
|
2581
|
-
hasStaticAfter,
|
|
2582
|
-
})
|
|
2583
|
-
})
|
|
2584
|
-
|
|
2585
|
-
const flatRoutes = scoredRoutes
|
|
2586
|
-
.sort((a, b) => {
|
|
2587
|
-
const minLength = Math.min(a.scores.length, b.scores.length)
|
|
2588
|
-
|
|
2589
|
-
// Sort by segment-by-segment score comparison ONLY for the common prefix
|
|
2590
|
-
for (let i = 0; i < minLength; i++) {
|
|
2591
|
-
if (a.scores[i] !== b.scores[i]) {
|
|
2592
|
-
return b.scores[i]! - a.scores[i]!
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
|
|
2596
|
-
// If all common segments have equal scores, then consider length and specificity
|
|
2597
|
-
if (a.scores.length !== b.scores.length) {
|
|
2598
|
-
// If different number of optional parameters, fewer optional parameters wins (more specific)
|
|
2599
|
-
// only if both or none of the routes has static segments after the params
|
|
2600
|
-
if (a.optionalParamCount !== b.optionalParamCount) {
|
|
2601
|
-
if (a.hasStaticAfter === b.hasStaticAfter) {
|
|
2602
|
-
return a.optionalParamCount - b.optionalParamCount
|
|
2603
|
-
} else if (a.hasStaticAfter && !b.hasStaticAfter) {
|
|
2604
|
-
return -1
|
|
2605
|
-
} else if (!a.hasStaticAfter && b.hasStaticAfter) {
|
|
2606
|
-
return 1
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
|
|
2610
|
-
// If same number of optional parameters, longer path wins (for static segments)
|
|
2611
|
-
return b.scores.length - a.scores.length
|
|
2612
|
-
}
|
|
2613
|
-
|
|
2614
|
-
// Sort by min available parsed value for alphabetical ordering
|
|
2615
|
-
for (let i = 0; i < minLength; i++) {
|
|
2616
|
-
if (a.parsed[i]!.value !== b.parsed[i]!.value) {
|
|
2617
|
-
return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
|
|
2621
|
-
// Sort by original index
|
|
2622
|
-
return a.index - b.index
|
|
2623
|
-
})
|
|
2624
|
-
.map((d, i) => {
|
|
2625
|
-
d.child.rank = i
|
|
2626
|
-
return d.child
|
|
2627
|
-
})
|
|
2628
|
-
|
|
2629
|
-
return { routesById, routesByPath, flatRoutes }
|
|
2630
|
-
}
|
|
2631
|
-
|
|
2632
2381
|
export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
2633
2382
|
pathname,
|
|
2634
2383
|
routePathname,
|