@tanstack/router-core 1.171.18 → 1.171.19
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/load-client.cjs +55 -66
- package/dist/cjs/load-client.cjs.map +1 -1
- package/dist/esm/load-client.js +55 -66
- package/dist/esm/load-client.js.map +1 -1
- package/package.json +1 -1
- package/src/load-client.ts +92 -102
package/src/load-client.ts
CHANGED
|
@@ -357,6 +357,7 @@ async function contextualize(
|
|
|
357
357
|
options: ExecuteLaneOptions,
|
|
358
358
|
end: number,
|
|
359
359
|
planSuccessfulLane: () => void,
|
|
360
|
+
retainedEnd: number,
|
|
360
361
|
): Promise<IndexedOutcome | undefined> {
|
|
361
362
|
const [location, matches] = lane
|
|
362
363
|
const signal = options[0 /* controller */].signal
|
|
@@ -433,7 +434,8 @@ async function contextualize(
|
|
|
433
434
|
}
|
|
434
435
|
|
|
435
436
|
const previousStatus = match.status
|
|
436
|
-
|
|
437
|
+
// Retain only a success that is mounted through the same valid prefix.
|
|
438
|
+
if (previousStatus === 'success' && index >= retainedEnd) {
|
|
437
439
|
match.status = 'pending'
|
|
438
440
|
}
|
|
439
441
|
options[8 /* onReady */]?.()
|
|
@@ -511,40 +513,21 @@ function transferMatchResources(
|
|
|
511
513
|
router: AnyRouter,
|
|
512
514
|
previous: Array<AnyRouteMatch>,
|
|
513
515
|
next?: Array<AnyRouteMatch>,
|
|
516
|
+
deferSameIdFlight?: true,
|
|
514
517
|
): void {
|
|
515
518
|
const abort: Array<AbortController> = []
|
|
516
519
|
for (const match of previous as Array<WorkMatch>) {
|
|
517
520
|
if (!next?.includes(match)) {
|
|
518
|
-
const flight = match._flight
|
|
519
|
-
match._flight = undefined
|
|
520
|
-
const controller = releaseOwnedFlight(router, match, flight)
|
|
521
|
-
if (controller) {
|
|
522
|
-
abort.push(controller)
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
for (const controller of abort) {
|
|
527
|
-
controller.abort()
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
function transferPredecessorResources(
|
|
532
|
-
router: AnyRouter,
|
|
533
|
-
previous: Array<AnyRouteMatch>,
|
|
534
|
-
next: Array<AnyRouteMatch>,
|
|
535
|
-
): void {
|
|
536
|
-
const abort: Array<AbortController> = []
|
|
537
|
-
for (const match of previous as Array<WorkMatch>) {
|
|
538
|
-
if (!next.includes(match)) {
|
|
539
521
|
const flight = match._flight
|
|
540
522
|
match._flight = undefined
|
|
541
523
|
if (
|
|
524
|
+
deferSameIdFlight &&
|
|
542
525
|
flight?.[2 /* leases */] === 1 &&
|
|
543
526
|
router._flights?.get(match.id) === flight &&
|
|
544
527
|
!(
|
|
545
528
|
process.env.NODE_ENV !== 'production' && router._tx?.[6 /* refresh */]
|
|
546
529
|
) &&
|
|
547
|
-
next
|
|
530
|
+
next?.some((candidate) => candidate.id === match.id)
|
|
548
531
|
) {
|
|
549
532
|
// The successor has not made its same-ID reload decision yet.
|
|
550
533
|
flight[2 /* leases */] = 0
|
|
@@ -561,19 +544,6 @@ function transferPredecessorResources(
|
|
|
561
544
|
}
|
|
562
545
|
}
|
|
563
546
|
|
|
564
|
-
function releaseUnownedFlights(router: AnyRouter): void {
|
|
565
|
-
const abort: Array<AbortController> = []
|
|
566
|
-
for (const [id, flight] of router._flights ?? []) {
|
|
567
|
-
if (!flight[2 /* leases */]) {
|
|
568
|
-
router._flights!.delete(id)
|
|
569
|
-
abort.push(flight[1 /* controller */])
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
for (const controller of abort) {
|
|
573
|
-
controller.abort()
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
547
|
function acquireMatchResources(matches: Array<AnyRouteMatch>): void {
|
|
578
548
|
for (const match of matches as Array<WorkMatch>) {
|
|
579
549
|
const flight = match._flight
|
|
@@ -775,6 +745,7 @@ function createLoaderTask(
|
|
|
775
745
|
tasks: Array<LoaderTask>,
|
|
776
746
|
semanticParent: Promise<WorkMatch> | undefined,
|
|
777
747
|
options: ExecuteLaneOptions,
|
|
748
|
+
retainedEnd: number,
|
|
778
749
|
): Promise<WorkMatch> {
|
|
779
750
|
const match = lane[1 /* matches */][index]!
|
|
780
751
|
const route = getRoute(router, match)
|
|
@@ -879,11 +850,9 @@ function createLoaderTask(
|
|
|
879
850
|
const acceptedFlight = match._flight
|
|
880
851
|
match._flight = donor
|
|
881
852
|
releaseOwnedFlight(router, match, acceptedFlight)?.abort()
|
|
882
|
-
// A successful route without a loader has no blocking work to present.
|
|
883
|
-
//
|
|
884
|
-
|
|
885
|
-
// ancestor while only a descendant is loading.
|
|
886
|
-
if (match.status === 'success') {
|
|
853
|
+
// A successful route without a loader has no blocking work to present. A
|
|
854
|
+
// mounted success likewise remains renderable while its loader revalidates.
|
|
855
|
+
if (match.status === 'success' && index >= retainedEnd) {
|
|
887
856
|
match.status = 'pending'
|
|
888
857
|
}
|
|
889
858
|
options[8 /* onReady */]?.()
|
|
@@ -1173,18 +1142,19 @@ async function reduceLane(
|
|
|
1173
1142
|
}
|
|
1174
1143
|
}
|
|
1175
1144
|
install()
|
|
1145
|
+
const route = getRoute(router, match)
|
|
1176
1146
|
try {
|
|
1177
1147
|
await waitFor<unknown>(
|
|
1178
1148
|
outcome
|
|
1179
1149
|
? Promise.resolve().then(() =>
|
|
1180
1150
|
loadRouteChunk(
|
|
1181
|
-
|
|
1151
|
+
route,
|
|
1182
1152
|
kind === ERROR ? 'errorComponent' : 'notFoundComponent',
|
|
1183
1153
|
),
|
|
1184
1154
|
)
|
|
1185
1155
|
: Promise.all([
|
|
1186
|
-
loadRouteChunk(
|
|
1187
|
-
loadRouteChunk(
|
|
1156
|
+
loadRouteChunk(route),
|
|
1157
|
+
loadRouteChunk(route, 'notFoundComponent'),
|
|
1188
1158
|
]),
|
|
1189
1159
|
controller.signal,
|
|
1190
1160
|
)
|
|
@@ -1268,6 +1238,7 @@ async function executeClientLane(
|
|
|
1268
1238
|
options: ExecuteLaneOptions,
|
|
1269
1239
|
): Promise<LaneResult> {
|
|
1270
1240
|
const matched = [location, matches as Array<WorkMatch>] as MatchedLane
|
|
1241
|
+
const presented = router.stores.matches.get()
|
|
1271
1242
|
let plannedBoundary = matches.findIndex((match) => match._notFound)
|
|
1272
1243
|
if (router.options.notFoundMode !== 'root' && plannedBoundary >= 0) {
|
|
1273
1244
|
const boundary = await getNotFoundBoundary(
|
|
@@ -1284,6 +1255,24 @@ async function executeClientLane(
|
|
|
1284
1255
|
plannedBoundary = boundary
|
|
1285
1256
|
}
|
|
1286
1257
|
let end = plannedBoundary < 0 ? matches.length : plannedBoundary + 1
|
|
1258
|
+
let retainedEnd = 0
|
|
1259
|
+
while (retainedEnd < end && retainedEnd !== plannedBoundary) {
|
|
1260
|
+
const match = matches[retainedEnd]!
|
|
1261
|
+
const committed = options[3 /* base */][retainedEnd]
|
|
1262
|
+
const visible = presented[retainedEnd]
|
|
1263
|
+
if (
|
|
1264
|
+
committed?.id !== match.id ||
|
|
1265
|
+
committed.status !== 'success' ||
|
|
1266
|
+
committed._notFound ||
|
|
1267
|
+
match.preload ||
|
|
1268
|
+
visible?.id !== match.id ||
|
|
1269
|
+
visible.status !== 'success' ||
|
|
1270
|
+
visible._notFound
|
|
1271
|
+
) {
|
|
1272
|
+
break
|
|
1273
|
+
}
|
|
1274
|
+
retainedEnd++
|
|
1275
|
+
}
|
|
1287
1276
|
const tasks: Array<LoaderTask> = []
|
|
1288
1277
|
const start = options[7 /* resolvedPrefix */] ?? 0
|
|
1289
1278
|
let semanticParent = start
|
|
@@ -1301,6 +1290,7 @@ async function executeClientLane(
|
|
|
1301
1290
|
tasks,
|
|
1302
1291
|
semanticParent,
|
|
1303
1292
|
options,
|
|
1293
|
+
retainedEnd,
|
|
1304
1294
|
)
|
|
1305
1295
|
}
|
|
1306
1296
|
}
|
|
@@ -1313,6 +1303,7 @@ async function executeClientLane(
|
|
|
1313
1303
|
options,
|
|
1314
1304
|
end,
|
|
1315
1305
|
planSuccessfulLane,
|
|
1306
|
+
retainedEnd,
|
|
1316
1307
|
)
|
|
1317
1308
|
if (failure) {
|
|
1318
1309
|
options[5 /* sync */] = true
|
|
@@ -1331,7 +1322,16 @@ async function executeClientLane(
|
|
|
1331
1322
|
planSuccessfulLane()
|
|
1332
1323
|
}
|
|
1333
1324
|
if (options[2 /* isCurrent */]() && !options[4 /* preload */]) {
|
|
1334
|
-
|
|
1325
|
+
const abort: Array<AbortController> = []
|
|
1326
|
+
for (const [id, flight] of router._flights ?? []) {
|
|
1327
|
+
if (!flight[2 /* leases */]) {
|
|
1328
|
+
router._flights!.delete(id)
|
|
1329
|
+
abort.push(flight[1 /* controller */])
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
for (const controller of abort) {
|
|
1333
|
+
controller.abort()
|
|
1334
|
+
}
|
|
1335
1335
|
}
|
|
1336
1336
|
let reduced: ReducedLane | ControlOutcome
|
|
1337
1337
|
try {
|
|
@@ -1376,49 +1376,6 @@ async function executeClientLane(
|
|
|
1376
1376
|
)
|
|
1377
1377
|
}
|
|
1378
1378
|
|
|
1379
|
-
/**
|
|
1380
|
-
* Finds the first route that should show pending UI and its two timing values.
|
|
1381
|
-
* A fallback already on screen remains selected after its route loads, so we
|
|
1382
|
-
* do not jump to a child fallback. Matches put back into pending by invalidation
|
|
1383
|
-
* skip pendingMs, and a route without a usable fallback blocks pending UI for deeper routes.
|
|
1384
|
-
*/
|
|
1385
|
-
function pendingConfig(
|
|
1386
|
-
router: AnyRouter,
|
|
1387
|
-
matches: Array<AnyRouteMatch>,
|
|
1388
|
-
):
|
|
1389
|
-
| [delay: number, boundary: number, min: number, component: unknown]
|
|
1390
|
-
| undefined
|
|
1391
|
-
| void {
|
|
1392
|
-
const presented = router.stores.matches.get()
|
|
1393
|
-
for (let index = 0; index < matches.length; index++) {
|
|
1394
|
-
const match = matches[index]!
|
|
1395
|
-
const success = match.status === 'success'
|
|
1396
|
-
const visible =
|
|
1397
|
-
success &&
|
|
1398
|
-
presented[index]?.id === match.id &&
|
|
1399
|
-
presented[index]?.status === 'pending'
|
|
1400
|
-
if (success && !visible) {
|
|
1401
|
-
continue
|
|
1402
|
-
}
|
|
1403
|
-
const route = getRoute(router, match as WorkMatch)
|
|
1404
|
-
const delay =
|
|
1405
|
-
visible || match.invalid
|
|
1406
|
-
? 0
|
|
1407
|
-
: (route.options.pendingMs ?? router.options.defaultPendingMs)
|
|
1408
|
-
const component =
|
|
1409
|
-
route.options.pendingComponent ??
|
|
1410
|
-
(router.options as any).defaultPendingComponent
|
|
1411
|
-
return component && typeof delay === 'number' && delay !== Infinity
|
|
1412
|
-
? [
|
|
1413
|
-
delay,
|
|
1414
|
-
index,
|
|
1415
|
-
route.options.pendingMinMs ?? router.options.defaultPendingMinMs ?? 0,
|
|
1416
|
-
component,
|
|
1417
|
-
]
|
|
1418
|
-
: undefined
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
1379
|
/**
|
|
1423
1380
|
* Waits for `pendingMs`, then presents the complete lane. Rendering applies the
|
|
1424
1381
|
* selected boundary cutoff while retaining every match's structural state.
|
|
@@ -1445,12 +1402,41 @@ function offerPending(router: CoordinatorRouter, tx: LoadTransaction): void {
|
|
|
1445
1402
|
router._pending = session = undefined
|
|
1446
1403
|
}
|
|
1447
1404
|
}
|
|
1448
|
-
const
|
|
1449
|
-
|
|
1405
|
+
const matches = tx[3 /* matches */]
|
|
1406
|
+
const presented = router.stores.matches.get()
|
|
1407
|
+
let boundary = -1
|
|
1408
|
+
let delay: number | undefined
|
|
1409
|
+
let min!: number
|
|
1410
|
+
let component: unknown
|
|
1411
|
+
let presentedPending = false
|
|
1412
|
+
for (let index = 0; index < matches.length; index++) {
|
|
1413
|
+
const match = matches[index]!
|
|
1414
|
+
const success = match.status === 'success'
|
|
1415
|
+
presentedPending =
|
|
1416
|
+
presented[index]?.id === match.id &&
|
|
1417
|
+
presented[index]?.status === 'pending'
|
|
1418
|
+
if (success && !presentedPending) {
|
|
1419
|
+
continue
|
|
1420
|
+
}
|
|
1421
|
+
const route = getRoute(router, match as WorkMatch)
|
|
1422
|
+
delay =
|
|
1423
|
+
(success && presentedPending) || match.invalid
|
|
1424
|
+
? 0
|
|
1425
|
+
: (route.options.pendingMs ?? router.options.defaultPendingMs)
|
|
1426
|
+
component =
|
|
1427
|
+
route.options.pendingComponent ??
|
|
1428
|
+
(router.options as any).defaultPendingComponent
|
|
1429
|
+
if (!component || typeof delay !== 'number' || delay === Infinity) {
|
|
1430
|
+
return
|
|
1431
|
+
}
|
|
1432
|
+
boundary = index
|
|
1433
|
+
min = route.options.pendingMinMs ?? router.options.defaultPendingMinMs ?? 0
|
|
1434
|
+
break
|
|
1435
|
+
}
|
|
1436
|
+
if (boundary < 0) {
|
|
1450
1437
|
return
|
|
1451
1438
|
}
|
|
1452
|
-
const [
|
|
1453
|
-
const matchId = tx[3 /* matches */][boundary]!.id
|
|
1439
|
+
const matchId = matches[boundary]!.id
|
|
1454
1440
|
if (
|
|
1455
1441
|
!session ||
|
|
1456
1442
|
session[1 /* boundary */] !== boundary ||
|
|
@@ -1459,14 +1445,12 @@ function offerPending(router: CoordinatorRouter, tx: LoadTransaction): void {
|
|
|
1459
1445
|
// Hydration and redirects can preserve pending presentation without a session.
|
|
1460
1446
|
// Do not delay it again; conservatively start pendingMinMs from now.
|
|
1461
1447
|
clearTimeout(session?.[3 /* timer */])
|
|
1462
|
-
const presented = router.stores.matches.get()[boundary]
|
|
1463
|
-
const visible = presented?.id === matchId && presented.status === 'pending'
|
|
1464
1448
|
router._pending = session = [
|
|
1465
1449
|
tx,
|
|
1466
1450
|
boundary,
|
|
1467
|
-
|
|
1451
|
+
presentedPending ? Date.now() + min : tx[4 /* startedAt */] + delay!,
|
|
1468
1452
|
undefined,
|
|
1469
|
-
|
|
1453
|
+
presentedPending ? Promise.resolve(true) : undefined,
|
|
1470
1454
|
component,
|
|
1471
1455
|
]
|
|
1472
1456
|
}
|
|
@@ -1482,14 +1466,15 @@ function offerPending(router: CoordinatorRouter, tx: LoadTransaction): void {
|
|
|
1482
1466
|
clearTimeout(session[3 /* timer */])
|
|
1483
1467
|
const remaining = session[2 /* deadline */] - Date.now()
|
|
1484
1468
|
if (remaining > 0) {
|
|
1485
|
-
session[3 /* timer */] = setTimeout(
|
|
1486
|
-
offerPending(router, tx)
|
|
1487
|
-
|
|
1469
|
+
session[3 /* timer */] = setTimeout(
|
|
1470
|
+
() => offerPending(router, tx),
|
|
1471
|
+
remaining,
|
|
1472
|
+
)
|
|
1488
1473
|
return
|
|
1489
1474
|
}
|
|
1490
1475
|
session[2 /* deadline */] = 0
|
|
1491
1476
|
}
|
|
1492
|
-
const offered =
|
|
1477
|
+
const offered = matches.map((match) => ({
|
|
1493
1478
|
...match,
|
|
1494
1479
|
_flight: undefined,
|
|
1495
1480
|
}))
|
|
@@ -2149,10 +2134,11 @@ export async function loadClientRoute(
|
|
|
2149
2134
|
}
|
|
2150
2135
|
}
|
|
2151
2136
|
previousOwner[0 /* controller */].abort()
|
|
2152
|
-
|
|
2137
|
+
transferMatchResources(
|
|
2153
2138
|
router,
|
|
2154
2139
|
previousOwner[3 /* matches */],
|
|
2155
2140
|
tx[3 /* matches */],
|
|
2141
|
+
true,
|
|
2156
2142
|
)
|
|
2157
2143
|
}
|
|
2158
2144
|
if (router._tx !== tx) {
|
|
@@ -2165,7 +2151,11 @@ export async function loadClientRoute(
|
|
|
2165
2151
|
router.stores.status.set('pending')
|
|
2166
2152
|
router.stores.location.set(location)
|
|
2167
2153
|
})
|
|
2168
|
-
|
|
2154
|
+
// Cold loads have no committed UI to retain, but provisional not-found
|
|
2155
|
+
// matches must wait for lazy routes to place the final boundary.
|
|
2156
|
+
if (!resolvedLocation && !matches.some((match) => match._notFound)) {
|
|
2157
|
+
offerPending(router, tx)
|
|
2158
|
+
}
|
|
2169
2159
|
try {
|
|
2170
2160
|
await tx[5 /* done */]
|
|
2171
2161
|
} finally {
|