@tanstack/router-core 1.168.15 → 1.168.17
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 +3 -0
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/manifest.cjs +31 -0
- package/dist/cjs/manifest.cjs.map +1 -1
- package/dist/cjs/manifest.d.cts +8 -0
- package/dist/cjs/new-process-route-tree.cjs +19 -19
- package/dist/cjs/new-process-route-tree.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.cjs +71 -5
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/manifest.d.ts +8 -0
- package/dist/esm/manifest.js +28 -1
- package/dist/esm/manifest.js.map +1 -1
- package/dist/esm/new-process-route-tree.js +19 -19
- package/dist/esm/new-process-route-tree.js.map +1 -1
- package/dist/esm/ssr/ssr-server.js +71 -5
- package/dist/esm/ssr/ssr-server.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +7 -1
- package/src/manifest.ts +46 -0
- package/src/new-process-route-tree.ts +51 -29
- package/src/ssr/ssr-server.ts +125 -6
|
@@ -1023,6 +1023,7 @@ type MatchStackFrame<T extends RouteLike> = {
|
|
|
1023
1023
|
* If we really really need to support more than 32 segments we can switch to using a `BigInt` here. It's about 2x slower in worst case scenarios.
|
|
1024
1024
|
*/
|
|
1025
1025
|
skipped: number
|
|
1026
|
+
/** Positional bitmasks tracking which consumed URL segments matched each segment kind. */
|
|
1026
1027
|
statics: number
|
|
1027
1028
|
dynamics: number
|
|
1028
1029
|
optionals: number
|
|
@@ -1066,13 +1067,12 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1066
1067
|
index: 1,
|
|
1067
1068
|
skipped: 0,
|
|
1068
1069
|
depth: 1,
|
|
1069
|
-
statics:
|
|
1070
|
+
statics: 0,
|
|
1070
1071
|
dynamics: 0,
|
|
1071
1072
|
optionals: 0,
|
|
1072
1073
|
},
|
|
1073
1074
|
]
|
|
1074
1075
|
|
|
1075
|
-
let wildcardMatch: Frame | null = null
|
|
1076
1076
|
let bestFuzzy: Frame | null = null
|
|
1077
1077
|
let bestMatch: Frame | null = null
|
|
1078
1078
|
|
|
@@ -1081,6 +1081,18 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1081
1081
|
const { node, index, skipped, depth, statics, dynamics, optionals } = frame
|
|
1082
1082
|
let { extract, rawParams, parsedParams } = frame
|
|
1083
1083
|
|
|
1084
|
+
// Wildcard candidates are pushed speculatively as fallbacks in case a
|
|
1085
|
+
// higher-priority wildcard later fails params.parse. If a better wildcard
|
|
1086
|
+
// has already validated and become bestMatch, lower-priority wildcard
|
|
1087
|
+
// fallbacks cannot win anymore and should not run params.parse.
|
|
1088
|
+
if (
|
|
1089
|
+
node.kind === SEGMENT_TYPE_WILDCARD &&
|
|
1090
|
+
node.route &&
|
|
1091
|
+
!isFrameMoreSpecific(bestMatch, frame)
|
|
1092
|
+
) {
|
|
1093
|
+
continue
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1084
1096
|
if (node.skipOnParamError) {
|
|
1085
1097
|
const result = validateMatchParams(path, parts, frame)
|
|
1086
1098
|
if (!result) continue
|
|
@@ -1101,7 +1113,13 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1101
1113
|
|
|
1102
1114
|
const isBeyondPath = index === partsLength
|
|
1103
1115
|
if (isBeyondPath) {
|
|
1104
|
-
if (
|
|
1116
|
+
if (
|
|
1117
|
+
node.route &&
|
|
1118
|
+
(!pathIsIndex ||
|
|
1119
|
+
node.kind === SEGMENT_TYPE_INDEX ||
|
|
1120
|
+
node.kind === SEGMENT_TYPE_WILDCARD) &&
|
|
1121
|
+
isFrameMoreSpecific(bestMatch, frame)
|
|
1122
|
+
) {
|
|
1105
1123
|
bestMatch = frame
|
|
1106
1124
|
}
|
|
1107
1125
|
// beyond the length of the path parts, only some segment types can match
|
|
@@ -1134,7 +1152,12 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1134
1152
|
if (indexValid) {
|
|
1135
1153
|
// perfect match, no need to continue
|
|
1136
1154
|
// this is an optimization, algorithm should work correctly without this block
|
|
1137
|
-
if (
|
|
1155
|
+
if (
|
|
1156
|
+
!dynamics &&
|
|
1157
|
+
!optionals &&
|
|
1158
|
+
!skipped &&
|
|
1159
|
+
isPerfectStaticMatch(statics, partsLength)
|
|
1160
|
+
) {
|
|
1138
1161
|
return indexFrame
|
|
1139
1162
|
}
|
|
1140
1163
|
if (isFrameMoreSpecific(bestMatch, indexFrame)) {
|
|
@@ -1145,8 +1168,9 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1145
1168
|
}
|
|
1146
1169
|
|
|
1147
1170
|
// 5. Try wildcard match
|
|
1148
|
-
if (node.wildcard
|
|
1149
|
-
for (
|
|
1171
|
+
if (node.wildcard) {
|
|
1172
|
+
for (let i = node.wildcard.length - 1; i >= 0; i--) {
|
|
1173
|
+
const segment = node.wildcard[i]!
|
|
1150
1174
|
const { prefix, suffix } = segment
|
|
1151
1175
|
if (prefix) {
|
|
1152
1176
|
if (isBeyondPath) continue
|
|
@@ -1161,26 +1185,19 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1161
1185
|
const casePart = segment.caseSensitive ? end : end.toLowerCase()
|
|
1162
1186
|
if (casePart !== suffix) continue
|
|
1163
1187
|
}
|
|
1164
|
-
//
|
|
1165
|
-
|
|
1166
|
-
const frame = {
|
|
1188
|
+
// wildcard matches consume the rest of the URL and cannot have children
|
|
1189
|
+
stack.push({
|
|
1167
1190
|
node: segment,
|
|
1168
1191
|
index: partsLength,
|
|
1169
1192
|
skipped,
|
|
1170
|
-
depth,
|
|
1193
|
+
depth: depth + 1,
|
|
1171
1194
|
statics,
|
|
1172
1195
|
dynamics,
|
|
1173
1196
|
optionals,
|
|
1174
1197
|
extract,
|
|
1175
1198
|
rawParams,
|
|
1176
1199
|
parsedParams,
|
|
1177
|
-
}
|
|
1178
|
-
if (segment.skipOnParamError) {
|
|
1179
|
-
const result = validateMatchParams(path, parts, frame)
|
|
1180
|
-
if (!result) continue
|
|
1181
|
-
}
|
|
1182
|
-
wildcardMatch = frame
|
|
1183
|
-
break
|
|
1200
|
+
})
|
|
1184
1201
|
}
|
|
1185
1202
|
}
|
|
1186
1203
|
|
|
@@ -1222,7 +1239,7 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1222
1239
|
depth: nextDepth,
|
|
1223
1240
|
statics,
|
|
1224
1241
|
dynamics,
|
|
1225
|
-
optionals: optionals +
|
|
1242
|
+
optionals: optionals + segmentScore(partsLength, index),
|
|
1226
1243
|
extract,
|
|
1227
1244
|
rawParams,
|
|
1228
1245
|
parsedParams,
|
|
@@ -1249,7 +1266,7 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1249
1266
|
skipped,
|
|
1250
1267
|
depth: depth + 1,
|
|
1251
1268
|
statics,
|
|
1252
|
-
dynamics: dynamics +
|
|
1269
|
+
dynamics: dynamics + segmentScore(partsLength, index),
|
|
1253
1270
|
optionals,
|
|
1254
1271
|
extract,
|
|
1255
1272
|
rawParams,
|
|
@@ -1269,7 +1286,7 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1269
1286
|
index: index + 1,
|
|
1270
1287
|
skipped,
|
|
1271
1288
|
depth: depth + 1,
|
|
1272
|
-
statics: statics +
|
|
1289
|
+
statics: statics + segmentScore(partsLength, index),
|
|
1273
1290
|
dynamics,
|
|
1274
1291
|
optionals,
|
|
1275
1292
|
extract,
|
|
@@ -1288,7 +1305,7 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1288
1305
|
index: index + 1,
|
|
1289
1306
|
skipped,
|
|
1290
1307
|
depth: depth + 1,
|
|
1291
|
-
statics: statics +
|
|
1308
|
+
statics: statics + segmentScore(partsLength, index),
|
|
1292
1309
|
dynamics,
|
|
1293
1310
|
optionals,
|
|
1294
1311
|
extract,
|
|
@@ -1319,16 +1336,8 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1319
1336
|
}
|
|
1320
1337
|
}
|
|
1321
1338
|
|
|
1322
|
-
if (bestMatch && wildcardMatch) {
|
|
1323
|
-
return isFrameMoreSpecific(wildcardMatch, bestMatch)
|
|
1324
|
-
? bestMatch
|
|
1325
|
-
: wildcardMatch
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
1339
|
if (bestMatch) return bestMatch
|
|
1329
1340
|
|
|
1330
|
-
if (wildcardMatch) return wildcardMatch
|
|
1331
|
-
|
|
1332
1341
|
if (fuzzy && bestFuzzy) {
|
|
1333
1342
|
let sliceIndex = bestFuzzy.index
|
|
1334
1343
|
for (let i = 0; i < bestFuzzy.index; i++) {
|
|
@@ -1343,6 +1352,19 @@ function getNodeMatch<T extends RouteLike>(
|
|
|
1343
1352
|
return null
|
|
1344
1353
|
}
|
|
1345
1354
|
|
|
1355
|
+
function segmentScore(partsLength: number, index: number): number {
|
|
1356
|
+
// The specificity scores are bitmasks over consumed URL segments. Earlier
|
|
1357
|
+
// URL segments should dominate later ones when comparing scores, so the
|
|
1358
|
+
// first real segment gets the highest bit and the last gets bit 0. Since
|
|
1359
|
+
// `parts[0]` is the empty string before the leading slash, real URL segments
|
|
1360
|
+
// are [1, partsLength), making this segment's bit `partsLength - index - 1`.
|
|
1361
|
+
return 2 ** (partsLength - index - 1)
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
function isPerfectStaticMatch(statics: number, partsLength: number): boolean {
|
|
1365
|
+
return statics === 2 ** (partsLength - 1) - 1
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1346
1368
|
function validateMatchParams<T extends RouteLike>(
|
|
1347
1369
|
path: string,
|
|
1348
1370
|
parts: Array<string>,
|
package/src/ssr/ssr-server.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { crossSerializeStream, getCrossReferenceHeader } from 'seroval'
|
|
2
2
|
import { invariant } from '../invariant'
|
|
3
|
+
import {
|
|
4
|
+
createInlineCssPlaceholderAsset,
|
|
5
|
+
createInlineCssStyleAsset,
|
|
6
|
+
getStylesheetHref,
|
|
7
|
+
isInlinableStylesheet,
|
|
8
|
+
} from '../manifest'
|
|
3
9
|
import { decodePath } from '../utils'
|
|
4
10
|
import { createLRUCache } from '../lru-cache'
|
|
5
11
|
import { rootRouteId } from '../root'
|
|
@@ -157,9 +163,11 @@ const isProd = process.env.NODE_ENV === 'production'
|
|
|
157
163
|
type FilteredRoutes = Manifest['routes']
|
|
158
164
|
|
|
159
165
|
type ManifestLRU = LRUCache<string, FilteredRoutes>
|
|
166
|
+
type InlineCssLRU = LRUCache<string, string>
|
|
160
167
|
|
|
161
168
|
const MANIFEST_CACHE_SIZE = 100
|
|
162
169
|
const manifestCaches = new WeakMap<Manifest, ManifestLRU>()
|
|
170
|
+
const inlineCssCaches = new WeakMap<Manifest, InlineCssLRU>()
|
|
163
171
|
|
|
164
172
|
function getManifestCache(manifest: Manifest): ManifestLRU {
|
|
165
173
|
const cache = manifestCaches.get(manifest)
|
|
@@ -169,6 +177,108 @@ function getManifestCache(manifest: Manifest): ManifestLRU {
|
|
|
169
177
|
return newCache
|
|
170
178
|
}
|
|
171
179
|
|
|
180
|
+
function getInlineCssCache(manifest: Manifest): InlineCssLRU {
|
|
181
|
+
const cache = inlineCssCaches.get(manifest)
|
|
182
|
+
if (cache) return cache
|
|
183
|
+
const newCache = createLRUCache<string, string>(MANIFEST_CACHE_SIZE)
|
|
184
|
+
inlineCssCaches.set(manifest, newCache)
|
|
185
|
+
return newCache
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function getInlineCssHrefsForMatches(
|
|
189
|
+
manifest: Manifest | undefined,
|
|
190
|
+
matches: Array<AnyRouteMatch>,
|
|
191
|
+
) {
|
|
192
|
+
const styles = manifest?.inlineCss?.styles
|
|
193
|
+
if (!styles) return []
|
|
194
|
+
|
|
195
|
+
const seen = new Set<string>()
|
|
196
|
+
const hrefs: Array<string> = []
|
|
197
|
+
|
|
198
|
+
for (const match of matches) {
|
|
199
|
+
const assets = manifest?.routes[match.routeId]?.assets ?? []
|
|
200
|
+
for (const asset of assets) {
|
|
201
|
+
const href = getStylesheetHref(asset)
|
|
202
|
+
if (!href || seen.has(href) || styles[href] === undefined) {
|
|
203
|
+
continue
|
|
204
|
+
}
|
|
205
|
+
seen.add(href)
|
|
206
|
+
hrefs.push(href)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return hrefs
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function getInlineCssForHrefs(manifest: Manifest, hrefs: Array<string>) {
|
|
214
|
+
const styles = manifest.inlineCss?.styles
|
|
215
|
+
if (!styles || hrefs.length === 0) return undefined
|
|
216
|
+
|
|
217
|
+
const cacheKey = hrefs.join('\0')
|
|
218
|
+
if (isProd) {
|
|
219
|
+
const cached = getInlineCssCache(manifest).get(cacheKey)
|
|
220
|
+
if (cached !== undefined) return cached
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const css = hrefs.map((href) => styles[href]!).join('')
|
|
224
|
+
|
|
225
|
+
if (isProd) {
|
|
226
|
+
getInlineCssCache(manifest).set(cacheKey, css)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return css
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function getInlineCssAssetForMatches(
|
|
233
|
+
manifest: Manifest | undefined,
|
|
234
|
+
matches: Array<AnyRouteMatch>,
|
|
235
|
+
) {
|
|
236
|
+
if (!manifest?.inlineCss) return undefined
|
|
237
|
+
|
|
238
|
+
const hrefs = getInlineCssHrefsForMatches(manifest, matches)
|
|
239
|
+
const css = getInlineCssForHrefs(manifest, hrefs)
|
|
240
|
+
|
|
241
|
+
return css === undefined ? undefined : createInlineCssStyleAsset(css)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function stripInlinedStylesheetAssets(
|
|
245
|
+
manifest: Manifest,
|
|
246
|
+
routes: FilteredRoutes,
|
|
247
|
+
matches: Array<AnyRouteMatch>,
|
|
248
|
+
): FilteredRoutes {
|
|
249
|
+
if (!manifest.inlineCss) {
|
|
250
|
+
return routes
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const nextRoutes: FilteredRoutes = {}
|
|
254
|
+
|
|
255
|
+
for (const [routeId, route] of Object.entries(routes)) {
|
|
256
|
+
const assets = route.assets?.filter(
|
|
257
|
+
(asset) => !isInlinableStylesheet(manifest, asset),
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
const nextRoute = { ...route }
|
|
261
|
+
if (assets) {
|
|
262
|
+
if (assets.length > 0) {
|
|
263
|
+
nextRoute.assets = assets
|
|
264
|
+
} else {
|
|
265
|
+
delete nextRoute.assets
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
nextRoutes[routeId] = nextRoute
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (getInlineCssAssetForMatches(manifest, matches)) {
|
|
272
|
+
const rootRoute = nextRoutes[rootRouteId] ?? {}
|
|
273
|
+
nextRoutes[rootRouteId] = {
|
|
274
|
+
...rootRoute,
|
|
275
|
+
assets: [createInlineCssPlaceholderAsset(), ...(rootRoute.assets ?? [])],
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return nextRoutes
|
|
280
|
+
}
|
|
281
|
+
|
|
172
282
|
export function attachRouterServerSsrUtils({
|
|
173
283
|
router,
|
|
174
284
|
manifest,
|
|
@@ -183,7 +293,11 @@ export function attachRouterServerSsrUtils({
|
|
|
183
293
|
router.ssr = {
|
|
184
294
|
get manifest() {
|
|
185
295
|
const requestAssets = getRequestAssets?.()
|
|
186
|
-
|
|
296
|
+
const inlineCssAsset = getInlineCssAssetForMatches(
|
|
297
|
+
manifest,
|
|
298
|
+
router.stores.matches.get(),
|
|
299
|
+
)
|
|
300
|
+
if (!requestAssets?.length && !inlineCssAsset) return manifest
|
|
187
301
|
// Merge request-scoped assets into root route without mutating cached manifest
|
|
188
302
|
return {
|
|
189
303
|
...manifest,
|
|
@@ -192,7 +306,8 @@ export function attachRouterServerSsrUtils({
|
|
|
192
306
|
[rootRouteId]: {
|
|
193
307
|
...manifest?.routes?.[rootRouteId],
|
|
194
308
|
assets: [
|
|
195
|
-
...requestAssets,
|
|
309
|
+
...(requestAssets ?? []),
|
|
310
|
+
...(inlineCssAsset ? [inlineCssAsset] : []),
|
|
196
311
|
...(manifest?.routes?.[rootRouteId]?.assets ?? []),
|
|
197
312
|
],
|
|
198
313
|
},
|
|
@@ -272,15 +387,19 @@ export function attachRouterServerSsrUtils({
|
|
|
272
387
|
}
|
|
273
388
|
}
|
|
274
389
|
|
|
390
|
+
filteredRoutes = stripInlinedStylesheetAssets(
|
|
391
|
+
manifest,
|
|
392
|
+
nextFilteredRoutes,
|
|
393
|
+
matchesToDehydrate,
|
|
394
|
+
)
|
|
395
|
+
|
|
275
396
|
if (isProd) {
|
|
276
|
-
getManifestCache(manifest).set(manifestCacheKey,
|
|
397
|
+
getManifestCache(manifest).set(manifestCacheKey, filteredRoutes)
|
|
277
398
|
}
|
|
278
|
-
|
|
279
|
-
filteredRoutes = nextFilteredRoutes
|
|
280
399
|
}
|
|
281
400
|
|
|
282
401
|
manifestToDehydrate = {
|
|
283
|
-
routes: filteredRoutes,
|
|
402
|
+
routes: { ...filteredRoutes },
|
|
284
403
|
}
|
|
285
404
|
|
|
286
405
|
// Merge request-scoped assets into root route (without mutating cached manifest)
|