@tanstack/router-core 1.171.24 → 1.171.26
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 +299 -389
- package/dist/cjs/load-client.cjs.map +1 -1
- package/dist/cjs/load-client.d.cts +18 -18
- package/dist/cjs/load-server.cjs +45 -32
- package/dist/cjs/load-server.cjs.map +1 -1
- package/dist/cjs/new-process-route-tree.cjs +1 -1
- package/dist/cjs/new-process-route-tree.cjs.map +1 -1
- package/dist/cjs/path.cjs +10 -4
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/redirect.cjs +1 -1
- package/dist/cjs/redirect.cjs.map +1 -1
- package/dist/cjs/redirect.d.cts +1 -1
- package/dist/cjs/router.cjs +34 -30
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +3 -5
- package/dist/esm/load-client.d.ts +18 -18
- package/dist/esm/load-client.js +299 -389
- package/dist/esm/load-client.js.map +1 -1
- package/dist/esm/load-server.js +45 -32
- package/dist/esm/load-server.js.map +1 -1
- package/dist/esm/new-process-route-tree.js +1 -1
- package/dist/esm/new-process-route-tree.js.map +1 -1
- package/dist/esm/path.js +10 -4
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/redirect.d.ts +1 -1
- package/dist/esm/redirect.js +1 -1
- package/dist/esm/redirect.js.map +1 -1
- package/dist/esm/router.d.ts +3 -5
- package/dist/esm/router.js +35 -31
- package/dist/esm/router.js.map +1 -1
- package/package.json +8 -2
- package/src/load-client.ts +535 -659
- package/src/load-server.ts +97 -35
- package/src/new-process-route-tree.ts +1 -1
- package/src/path.ts +20 -6
- package/src/redirect.ts +2 -18
- package/src/router.ts +54 -78
package/src/load-client.ts
CHANGED
|
@@ -163,18 +163,30 @@ const NOT_FOUND = 2
|
|
|
163
163
|
// Control outcomes stay contiguous so the hot path can test them together.
|
|
164
164
|
const REDIRECTED = 3
|
|
165
165
|
const CANCELED = 4
|
|
166
|
+
const CANCELED_OUTCOME: [kind: typeof CANCELED] = [CANCELED]
|
|
166
167
|
|
|
167
|
-
type
|
|
168
|
+
type RedirectOutcome = [
|
|
169
|
+
kind: typeof REDIRECTED,
|
|
170
|
+
redirect: AnyRedirect,
|
|
171
|
+
location?: ParsedLocation,
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
type NonRedirectOutcome =
|
|
168
175
|
| [kind: typeof SUCCESS, data: unknown]
|
|
169
176
|
| [kind: typeof ERROR, error: unknown]
|
|
170
177
|
| [kind: typeof NOT_FOUND, error: NotFoundError]
|
|
171
|
-
| [kind: typeof REDIRECTED, redirect: AnyRedirect]
|
|
172
178
|
| [kind: typeof CANCELED]
|
|
173
179
|
|
|
180
|
+
type RawLoaderOutcome =
|
|
181
|
+
| NonRedirectOutcome
|
|
182
|
+
| [kind: typeof REDIRECTED, redirect: AnyRedirect]
|
|
183
|
+
|
|
184
|
+
type LoaderOutcome = NonRedirectOutcome | RedirectOutcome
|
|
185
|
+
|
|
174
186
|
type IndexedOutcome = [index: number, outcome: LoaderOutcome, boundary?: number]
|
|
175
187
|
|
|
176
188
|
export type LoaderFlight = [
|
|
177
|
-
outcome: Promise<
|
|
189
|
+
outcome: Promise<RawLoaderOutcome>,
|
|
178
190
|
controller: AbortController,
|
|
179
191
|
leases: number,
|
|
180
192
|
]
|
|
@@ -202,25 +214,20 @@ export type LoadTransaction = [
|
|
|
202
214
|
startedAt: number,
|
|
203
215
|
done: Promise<void>,
|
|
204
216
|
/**
|
|
205
|
-
* Dev-only HMR refresh mode. Presence
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* with the transaction that owns the publication.
|
|
217
|
+
* Dev-only HMR refresh mode. Presence forces successor rematerialization
|
|
218
|
+
* until this publication is acknowledged. The optional hydration handoff is
|
|
219
|
+
* retired when the refresh publishes.
|
|
209
220
|
*/
|
|
210
|
-
refresh?: [
|
|
211
|
-
presentation: Array<AnyRouteMatch>,
|
|
212
|
-
handoff: NonNullable<AnyRouter['_handoff']> | undefined,
|
|
213
|
-
rollback?: () => boolean,
|
|
214
|
-
],
|
|
221
|
+
refresh?: [handoff: NonNullable<AnyRouter['_handoff']> | undefined],
|
|
215
222
|
]
|
|
216
223
|
|
|
217
224
|
export type PendingSession = [
|
|
218
|
-
|
|
219
|
-
|
|
225
|
+
generation: LoadTransaction,
|
|
226
|
+
boundaryId: string,
|
|
220
227
|
/** Pending reveal time until acknowledged, then minimum-visible-until time. */
|
|
221
228
|
deadline: number,
|
|
222
|
-
|
|
223
|
-
ack?: Promise<boolean
|
|
229
|
+
revealTimer?: ReturnType<typeof setTimeout>,
|
|
230
|
+
ack?: Promise<boolean> | true,
|
|
224
231
|
component?: unknown,
|
|
225
232
|
]
|
|
226
233
|
|
|
@@ -228,15 +235,6 @@ type CoordinatorRouter = AnyRouter & {
|
|
|
228
235
|
/** Active speculative lanes retained for cancellation, invalidation, and cache clearing. */
|
|
229
236
|
_preloads?: Map<AbortController, Array<AnyRouteMatch>>
|
|
230
237
|
_refreshNextLoad?: boolean
|
|
231
|
-
_cancelTransition?: () => void
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
type PublicationCheckpoint = {
|
|
235
|
-
previousMatches: Array<AnyRouteMatch>
|
|
236
|
-
previousPresentation: Array<AnyRouteMatch>
|
|
237
|
-
previousCache: Map<string, AnyRouteMatch>
|
|
238
|
-
commitPromise: CoordinatorRouter['_commitPromise']
|
|
239
|
-
published: boolean
|
|
240
238
|
}
|
|
241
239
|
|
|
242
240
|
type LoaderTask = [
|
|
@@ -256,7 +254,6 @@ type BackgroundLoaderTask = [
|
|
|
256
254
|
type ExecuteLaneOptions = [
|
|
257
255
|
controller: AbortController,
|
|
258
256
|
redirects: number,
|
|
259
|
-
isCurrent: () => boolean,
|
|
260
257
|
base: Array<AnyRouteMatch>,
|
|
261
258
|
preload?: boolean,
|
|
262
259
|
sync?: boolean,
|
|
@@ -265,9 +262,7 @@ type ExecuteLaneOptions = [
|
|
|
265
262
|
onReady?: () => void,
|
|
266
263
|
]
|
|
267
264
|
|
|
268
|
-
type ControlOutcome =
|
|
269
|
-
| [kind: typeof REDIRECTED, redirect: AnyRedirect]
|
|
270
|
-
| [kind: typeof CANCELED]
|
|
265
|
+
type ControlOutcome = RedirectOutcome | [kind: typeof CANCELED]
|
|
271
266
|
|
|
272
267
|
type LaneResult = ProjectedLane | ControlOutcome
|
|
273
268
|
|
|
@@ -301,7 +296,7 @@ function normalize(
|
|
|
301
296
|
value: unknown,
|
|
302
297
|
rejected: boolean,
|
|
303
298
|
routeId?: string,
|
|
304
|
-
):
|
|
299
|
+
): RawLoaderOutcome {
|
|
305
300
|
if (isRedirect(value)) {
|
|
306
301
|
return [REDIRECTED, value]
|
|
307
302
|
}
|
|
@@ -315,7 +310,7 @@ function normalize(
|
|
|
315
310
|
return rejected ? [ERROR, value] : [SUCCESS, value]
|
|
316
311
|
}
|
|
317
312
|
|
|
318
|
-
function normalizeError(route: AnyRoute, cause: unknown):
|
|
313
|
+
function normalizeError(route: AnyRoute, cause: unknown): RawLoaderOutcome {
|
|
319
314
|
let outcome = normalize(cause, true, route.id)
|
|
320
315
|
if (outcome[0 /* kind */] !== ERROR) {
|
|
321
316
|
return outcome
|
|
@@ -329,26 +324,22 @@ function normalizeError(route: AnyRoute, cause: unknown): LoaderOutcome {
|
|
|
329
324
|
}
|
|
330
325
|
|
|
331
326
|
function normalizeLaneError(
|
|
327
|
+
router: AnyRouter,
|
|
328
|
+
lane: Lane<any>,
|
|
332
329
|
route: AnyRoute,
|
|
333
330
|
cause: unknown,
|
|
334
331
|
options: ExecuteLaneOptions,
|
|
335
332
|
): LoaderOutcome {
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
!options[2 /* isCurrent */]()
|
|
339
|
-
) {
|
|
340
|
-
options[0 /* controller */].abort()
|
|
341
|
-
return [CANCELED]
|
|
333
|
+
if (options[0 /* controller */].signal.aborted) {
|
|
334
|
+
return CANCELED_OUTCOME
|
|
342
335
|
}
|
|
343
|
-
return
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
_fromLocation: location,
|
|
351
|
-
})
|
|
336
|
+
return materializeRedirect(
|
|
337
|
+
router,
|
|
338
|
+
lane,
|
|
339
|
+
route,
|
|
340
|
+
normalizeError(route, cause),
|
|
341
|
+
options,
|
|
342
|
+
)
|
|
352
343
|
}
|
|
353
344
|
|
|
354
345
|
async function contextualize(
|
|
@@ -361,8 +352,8 @@ async function contextualize(
|
|
|
361
352
|
): Promise<IndexedOutcome | undefined> {
|
|
362
353
|
const [location, matches] = lane
|
|
363
354
|
const signal = options[0 /* controller */].signal
|
|
364
|
-
const preload = !!options[
|
|
365
|
-
for (let index = options[
|
|
355
|
+
const preload = !!options[3 /* preload */]
|
|
356
|
+
for (let index = options[6 /* resolvedPrefix */] ?? 0; index < end; index++) {
|
|
366
357
|
const match = matches[index]!
|
|
367
358
|
const route = getRoute(router, match)
|
|
368
359
|
|
|
@@ -374,7 +365,11 @@ async function contextualize(
|
|
|
374
365
|
const common = {
|
|
375
366
|
params: match.params,
|
|
376
367
|
location,
|
|
377
|
-
navigate:
|
|
368
|
+
navigate: (opts: any) =>
|
|
369
|
+
router.navigate({
|
|
370
|
+
...opts,
|
|
371
|
+
_fromLocation: location,
|
|
372
|
+
}),
|
|
378
373
|
buildLocation: router.buildLocation,
|
|
379
374
|
cause: preload ? ('preload' as const) : match.cause,
|
|
380
375
|
abortController: options[0 /* controller */],
|
|
@@ -400,16 +395,18 @@ async function contextualize(
|
|
|
400
395
|
match.context = context
|
|
401
396
|
} catch (cause) {
|
|
402
397
|
releaseFlight(router, match)
|
|
403
|
-
return [index, normalizeLaneError(route, cause, options)]
|
|
398
|
+
return [index, normalizeLaneError(router, lane, route, cause, options)]
|
|
404
399
|
}
|
|
405
|
-
if (signal.aborted
|
|
406
|
-
|
|
407
|
-
return [index, [CANCELED]]
|
|
400
|
+
if (signal.aborted) {
|
|
401
|
+
return [index, CANCELED_OUTCOME]
|
|
408
402
|
}
|
|
409
403
|
const validationError = match.paramsError ?? match.searchError
|
|
410
404
|
if (validationError !== undefined) {
|
|
411
405
|
releaseFlight(router, match)
|
|
412
|
-
return [
|
|
406
|
+
return [
|
|
407
|
+
index,
|
|
408
|
+
normalizeLaneError(router, lane, route, validationError, options),
|
|
409
|
+
]
|
|
413
410
|
}
|
|
414
411
|
const beforeLoad = route.options.beforeLoad
|
|
415
412
|
if (!beforeLoad) {
|
|
@@ -434,19 +431,23 @@ async function contextualize(
|
|
|
434
431
|
}
|
|
435
432
|
|
|
436
433
|
const previousStatus = match.status
|
|
437
|
-
|
|
438
|
-
if (previousStatus === 'success' && index >= retainedEnd) {
|
|
434
|
+
if (index >= retainedEnd) {
|
|
439
435
|
match.status = 'pending'
|
|
436
|
+
options[7 /* onReady */]?.()
|
|
440
437
|
}
|
|
441
|
-
options[8 /* onReady */]?.()
|
|
442
438
|
try {
|
|
443
439
|
setFetching(router, match, 'beforeLoad', options[0 /* controller */])
|
|
444
440
|
const result = await waitFor(beforeLoad(beforeLoadContext), signal)
|
|
445
|
-
if (
|
|
446
|
-
|
|
447
|
-
return [index, [CANCELED]]
|
|
441
|
+
if (signal.aborted) {
|
|
442
|
+
return [index, CANCELED_OUTCOME]
|
|
448
443
|
}
|
|
449
|
-
const outcome =
|
|
444
|
+
const outcome = materializeRedirect(
|
|
445
|
+
router,
|
|
446
|
+
lane,
|
|
447
|
+
route,
|
|
448
|
+
normalize(result, false, route.id),
|
|
449
|
+
options,
|
|
450
|
+
)
|
|
450
451
|
if (outcome[0 /* kind */] !== SUCCESS) {
|
|
451
452
|
releaseFlight(router, match)
|
|
452
453
|
return [index, outcome]
|
|
@@ -457,10 +458,10 @@ async function contextualize(
|
|
|
457
458
|
}
|
|
458
459
|
} catch (cause) {
|
|
459
460
|
releaseFlight(router, match)
|
|
460
|
-
return [index, normalizeLaneError(route, cause, options)]
|
|
461
|
+
return [index, normalizeLaneError(router, lane, route, cause, options)]
|
|
461
462
|
} finally {
|
|
462
|
-
if (
|
|
463
|
-
match.status =
|
|
463
|
+
if (match.status === 'pending') {
|
|
464
|
+
match.status = previousStatus
|
|
464
465
|
}
|
|
465
466
|
setFetching(router, match, false, options[0 /* controller */])
|
|
466
467
|
}
|
|
@@ -484,7 +485,6 @@ function releaseOwnedFlight(
|
|
|
484
485
|
if (
|
|
485
486
|
current &&
|
|
486
487
|
!current[0 /* controller */].signal.aborted &&
|
|
487
|
-
!(process.env.NODE_ENV !== 'production' && current[6 /* refresh */]) &&
|
|
488
488
|
!current[3 /* matches */].includes(match) &&
|
|
489
489
|
current[3 /* matches */].some((candidate) => candidate.id === match.id) &&
|
|
490
490
|
current[3 /* matches */].some(
|
|
@@ -524,9 +524,6 @@ function transferMatchResources(
|
|
|
524
524
|
deferSameIdFlight &&
|
|
525
525
|
flight?.[2 /* leases */] === 1 &&
|
|
526
526
|
router._flights?.get(match.id) === flight &&
|
|
527
|
-
!(
|
|
528
|
-
process.env.NODE_ENV !== 'production' && router._tx?.[6 /* refresh */]
|
|
529
|
-
) &&
|
|
530
527
|
next?.some((candidate) => candidate.id === match.id)
|
|
531
528
|
) {
|
|
532
529
|
// The successor has not made its same-ID reload decision yet.
|
|
@@ -583,7 +580,11 @@ function getLoaderContext(
|
|
|
583
580
|
return {
|
|
584
581
|
params: match.params,
|
|
585
582
|
location,
|
|
586
|
-
navigate:
|
|
583
|
+
navigate: (opts: any) =>
|
|
584
|
+
router.navigate({
|
|
585
|
+
...opts,
|
|
586
|
+
_fromLocation: location,
|
|
587
|
+
}),
|
|
587
588
|
cause: preload ? ('preload' as const) : match.cause,
|
|
588
589
|
abortController: controller,
|
|
589
590
|
preload,
|
|
@@ -602,12 +603,12 @@ async function loadResource(
|
|
|
602
603
|
route: AnyRoute,
|
|
603
604
|
loader: RouteLoaderFn<any> | undefined,
|
|
604
605
|
parentMatchPromise: Promise<WorkMatch> | undefined,
|
|
605
|
-
|
|
606
|
-
owner: AbortController,
|
|
606
|
+
options: ExecuteLaneOptions,
|
|
607
607
|
): Promise<LoaderOutcome> {
|
|
608
|
+
const owner = options[0 /* controller */]
|
|
608
609
|
const signal = owner.signal
|
|
609
610
|
if (signal.aborted) {
|
|
610
|
-
return
|
|
611
|
+
return CANCELED_OUTCOME
|
|
611
612
|
}
|
|
612
613
|
if (!loader) {
|
|
613
614
|
return [SUCCESS, undefined]
|
|
@@ -629,7 +630,7 @@ async function loadResource(
|
|
|
629
630
|
route,
|
|
630
631
|
controller,
|
|
631
632
|
parentMatchPromise,
|
|
632
|
-
preload,
|
|
633
|
+
!!options[3 /* preload */],
|
|
633
634
|
),
|
|
634
635
|
),
|
|
635
636
|
)
|
|
@@ -637,7 +638,7 @@ async function loadResource(
|
|
|
637
638
|
(value) => normalize(value, false, route.id),
|
|
638
639
|
(cause) => normalize(cause, true, route.id),
|
|
639
640
|
)
|
|
640
|
-
.then((result):
|
|
641
|
+
.then((result): RawLoaderOutcome => {
|
|
641
642
|
// The registry controls discovery; leases keep current consumers
|
|
642
643
|
// sharing the same terminal outcome.
|
|
643
644
|
if (
|
|
@@ -660,13 +661,19 @@ async function loadResource(
|
|
|
660
661
|
}
|
|
661
662
|
match._flight = flight
|
|
662
663
|
match.abortController = flight[1 /* controller */]
|
|
663
|
-
return
|
|
664
|
+
return materializeRedirect(
|
|
665
|
+
router,
|
|
666
|
+
lane,
|
|
667
|
+
route,
|
|
668
|
+
await waitFor(flight[0 /* outcome */], signal),
|
|
669
|
+
options,
|
|
670
|
+
)
|
|
664
671
|
} catch (cause) {
|
|
665
|
-
if (cause !== signal) {
|
|
672
|
+
if (cause !== signal || !signal.aborted) {
|
|
666
673
|
throw cause
|
|
667
674
|
}
|
|
668
675
|
releaseFlight(router, match)
|
|
669
|
-
return
|
|
676
|
+
return CANCELED_OUTCOME
|
|
670
677
|
} finally {
|
|
671
678
|
setFetching(router, match, false, owner)
|
|
672
679
|
}
|
|
@@ -749,7 +756,7 @@ function createLoaderTask(
|
|
|
749
756
|
): Promise<WorkMatch> {
|
|
750
757
|
const match = lane[1 /* matches */][index]!
|
|
751
758
|
const route = getRoute(router, match)
|
|
752
|
-
const preload = !!options[
|
|
759
|
+
const preload = !!options[3 /* preload */]
|
|
753
760
|
const plannedCacheMatch = router._cache.get(match.id)
|
|
754
761
|
let configured
|
|
755
762
|
let reload = false
|
|
@@ -770,9 +777,8 @@ function createLoaderTask(
|
|
|
770
777
|
),
|
|
771
778
|
)
|
|
772
779
|
}
|
|
773
|
-
if (
|
|
774
|
-
|
|
775
|
-
reloadFailure = [CANCELED]
|
|
780
|
+
if (options[0 /* controller */].signal.aborted) {
|
|
781
|
+
reloadFailure = CANCELED_OUTCOME
|
|
776
782
|
}
|
|
777
783
|
}
|
|
778
784
|
if (!reloadFailure) {
|
|
@@ -780,7 +786,7 @@ function createLoaderTask(
|
|
|
780
786
|
reload = true
|
|
781
787
|
} else {
|
|
782
788
|
const staleAge =
|
|
783
|
-
options[
|
|
789
|
+
options[3 /* preload */] || match.preload
|
|
784
790
|
? (route.options.preloadStaleTime ??
|
|
785
791
|
router.options.defaultPreloadStaleTime ??
|
|
786
792
|
30_000)
|
|
@@ -790,9 +796,9 @@ function createLoaderTask(
|
|
|
790
796
|
configured ||
|
|
791
797
|
(configured === undefined &&
|
|
792
798
|
Date.now() - match.updatedAt >= staleAge &&
|
|
793
|
-
(options[
|
|
799
|
+
(options[5 /* forceStaleReload */] ||
|
|
794
800
|
match.cause === 'enter' ||
|
|
795
|
-
options[
|
|
801
|
+
options[2 /* base */].some(
|
|
796
802
|
(candidate) =>
|
|
797
803
|
candidate.routeId === match.routeId &&
|
|
798
804
|
candidate.id !== match.id,
|
|
@@ -803,7 +809,7 @@ function createLoaderTask(
|
|
|
803
809
|
} catch (cause) {
|
|
804
810
|
match.invalid = true
|
|
805
811
|
releaseFlight(router, match)
|
|
806
|
-
reloadFailure = normalizeLaneError(route, cause, options)
|
|
812
|
+
reloadFailure = normalizeLaneError(router, lane, route, cause, options)
|
|
807
813
|
}
|
|
808
814
|
const routeLoader = route.options.loader
|
|
809
815
|
const loader =
|
|
@@ -828,7 +834,7 @@ function createLoaderTask(
|
|
|
828
834
|
reload &&
|
|
829
835
|
match.status === 'success' &&
|
|
830
836
|
!preload &&
|
|
831
|
-
!options[
|
|
837
|
+
!options[4 /* sync */] &&
|
|
832
838
|
((typeof routeLoader === 'function'
|
|
833
839
|
? undefined
|
|
834
840
|
: routeLoader?.staleReloadMode) ??
|
|
@@ -837,8 +843,8 @@ function createLoaderTask(
|
|
|
837
843
|
const loaded = reload && (!preload || route.options.preload !== false)
|
|
838
844
|
const blocking =
|
|
839
845
|
loaded && !background && (match.status !== 'success' || !!routeLoader)
|
|
840
|
-
const
|
|
841
|
-
|
|
846
|
+
const onReady = index >= retainedEnd ? options[7 /* onReady */] : undefined
|
|
847
|
+
const onLazyReady = route.lazyFn && route._lazy !== true ? onReady : undefined
|
|
842
848
|
if (loaded && !routeLoader) {
|
|
843
849
|
match.invalid = false
|
|
844
850
|
match.updatedAt = Date.now()
|
|
@@ -850,17 +856,17 @@ function createLoaderTask(
|
|
|
850
856
|
const acceptedFlight = match._flight
|
|
851
857
|
match._flight = donor
|
|
852
858
|
releaseOwnedFlight(router, match, acceptedFlight)?.abort()
|
|
853
|
-
// A
|
|
854
|
-
//
|
|
855
|
-
if (
|
|
859
|
+
// A mounted success remains renderable while its loader revalidates. Every
|
|
860
|
+
// non-retained blocking generation presents pending state.
|
|
861
|
+
if (index >= retainedEnd) {
|
|
856
862
|
match.status = 'pending'
|
|
857
863
|
}
|
|
858
|
-
|
|
864
|
+
onReady?.()
|
|
859
865
|
}
|
|
860
866
|
if (!loaded) {
|
|
861
867
|
match.isFetching = false
|
|
862
868
|
}
|
|
863
|
-
const
|
|
869
|
+
const loaderOutcome = reloadFailure
|
|
864
870
|
? Promise.resolve(reloadFailure)
|
|
865
871
|
: !blocking
|
|
866
872
|
? Promise.resolve<LoaderOutcome>([SUCCESS, match.loaderData])
|
|
@@ -871,55 +877,54 @@ function createLoaderTask(
|
|
|
871
877
|
route,
|
|
872
878
|
loader,
|
|
873
879
|
semanticParent,
|
|
874
|
-
|
|
875
|
-
options[0 /* controller */],
|
|
880
|
+
options,
|
|
876
881
|
)
|
|
877
|
-
const outcome =
|
|
882
|
+
const outcome = loaderOutcome.then((result) => {
|
|
878
883
|
if (blocking) {
|
|
879
884
|
settleInto(match, result, preload)
|
|
880
885
|
if (result[0 /* kind */] === SUCCESS) {
|
|
881
886
|
// A settled generation can outlive its lane without keeping unresolved
|
|
882
|
-
// navigation work alive.
|
|
883
|
-
if (
|
|
884
|
-
routeLoader &&
|
|
885
|
-
!options[0 /* controller */].signal.aborted &&
|
|
886
|
-
!(
|
|
887
|
-
process.env.NODE_ENV !== 'production' &&
|
|
888
|
-
!preload &&
|
|
889
|
-
router._tx?.[6 /* refresh */]
|
|
890
|
-
)
|
|
891
|
-
) {
|
|
887
|
+
// navigation work alive.
|
|
888
|
+
if (routeLoader && !options[0 /* controller */].signal.aborted) {
|
|
892
889
|
cacheLoaderMatch(router, match, plannedCacheMatch)
|
|
893
890
|
}
|
|
894
891
|
// A route is renderable only after both its data and normal component
|
|
895
892
|
// chunk are ready. Its loader data is already available to descendants.
|
|
896
|
-
|
|
893
|
+
if (index >= retainedEnd) {
|
|
894
|
+
match.status = 'pending'
|
|
895
|
+
}
|
|
897
896
|
}
|
|
898
897
|
}
|
|
899
898
|
return result
|
|
900
899
|
})
|
|
901
900
|
|
|
902
|
-
const
|
|
901
|
+
const chunkOutcome = waitFor(
|
|
903
902
|
Promise.resolve().then(() => loadRouteChunk(route, undefined, onLazyReady)),
|
|
904
903
|
options[0 /* controller */].signal,
|
|
905
904
|
).then(
|
|
906
905
|
() => undefined,
|
|
907
|
-
(cause): IndexedOutcome =>
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
906
|
+
(cause): IndexedOutcome | undefined =>
|
|
907
|
+
lane[1 /* matches */].some(
|
|
908
|
+
(candidate, candidateIndex) =>
|
|
909
|
+
candidateIndex <= index &&
|
|
910
|
+
(candidate.status === 'error' ||
|
|
911
|
+
candidate.status === 'notFound' ||
|
|
912
|
+
candidate._notFound),
|
|
913
|
+
)
|
|
914
|
+
? undefined
|
|
915
|
+
: [index, normalizeLaneError(router, lane, route, cause, options)],
|
|
911
916
|
)
|
|
912
|
-
const chunkFailure =
|
|
917
|
+
const chunkFailure = chunkOutcome.then((failure) =>
|
|
913
918
|
outcome.then((result) => {
|
|
914
919
|
if (
|
|
915
920
|
blocking &&
|
|
916
921
|
!failure &&
|
|
917
922
|
result[0 /* kind */] === SUCCESS &&
|
|
918
923
|
match.status === 'pending' &&
|
|
919
|
-
options[
|
|
924
|
+
!options[0 /* controller */].signal.aborted
|
|
920
925
|
) {
|
|
921
926
|
match.status = 'success'
|
|
922
|
-
|
|
927
|
+
onReady?.()
|
|
923
928
|
}
|
|
924
929
|
return failure
|
|
925
930
|
}),
|
|
@@ -943,8 +948,7 @@ function createLoaderTask(
|
|
|
943
948
|
route,
|
|
944
949
|
loader,
|
|
945
950
|
semanticParent,
|
|
946
|
-
|
|
947
|
-
options[0 /* controller */],
|
|
951
|
+
options,
|
|
948
952
|
).then((result) => {
|
|
949
953
|
match.isFetching = false
|
|
950
954
|
settleInto(candidate, result, false)
|
|
@@ -979,14 +983,14 @@ async function getNotFoundBoundary(
|
|
|
979
983
|
}
|
|
980
984
|
for (let i = index; i >= 0; i--) {
|
|
981
985
|
const route = getRoute(router, matches[i]!)
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
986
|
+
try {
|
|
987
|
+
const loading = loadRouteChunk(route, false)
|
|
988
|
+
if (loading) {
|
|
985
989
|
await waitFor(loading, signal)
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
+
}
|
|
991
|
+
} catch (cause) {
|
|
992
|
+
if (cause === signal && signal.aborted) {
|
|
993
|
+
throw cause
|
|
990
994
|
}
|
|
991
995
|
}
|
|
992
996
|
if (route.options.notFoundComponent) {
|
|
@@ -1054,12 +1058,50 @@ async function settleTasks(
|
|
|
1054
1058
|
return serialFailure ?? loaderFailure
|
|
1055
1059
|
}
|
|
1056
1060
|
|
|
1061
|
+
function materializeRedirect(
|
|
1062
|
+
router: AnyRouter,
|
|
1063
|
+
lane: Lane<any>,
|
|
1064
|
+
route: AnyRoute,
|
|
1065
|
+
outcome: RawLoaderOutcome,
|
|
1066
|
+
options: ExecuteLaneOptions,
|
|
1067
|
+
failed?: true,
|
|
1068
|
+
): LoaderOutcome {
|
|
1069
|
+
while (outcome[0 /* kind */] === REDIRECTED) {
|
|
1070
|
+
const redirect = outcome[1 /* redirect */]
|
|
1071
|
+
if (
|
|
1072
|
+
redirect.options.reloadDocument
|
|
1073
|
+
? options[3 /* preload */]
|
|
1074
|
+
: options[1 /* redirects */] >= 20
|
|
1075
|
+
) {
|
|
1076
|
+
return outcome
|
|
1077
|
+
}
|
|
1078
|
+
try {
|
|
1079
|
+
if (redirect.options.href && redirect.options.reloadDocument) {
|
|
1080
|
+
router.resolveRedirect(redirect)
|
|
1081
|
+
return outcome
|
|
1082
|
+
}
|
|
1083
|
+
return [
|
|
1084
|
+
REDIRECTED,
|
|
1085
|
+
redirect,
|
|
1086
|
+
router.buildLocation({
|
|
1087
|
+
...redirect.options,
|
|
1088
|
+
_fromLocation: lane[0 /* location */],
|
|
1089
|
+
_includeValidateSearch: true,
|
|
1090
|
+
}),
|
|
1091
|
+
]
|
|
1092
|
+
} catch (cause) {
|
|
1093
|
+
outcome = failed ? [ERROR, cause] : normalizeError(route, cause)
|
|
1094
|
+
failed = true
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return outcome
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1057
1100
|
async function reduceLane(
|
|
1058
1101
|
router: AnyRouter,
|
|
1059
1102
|
lane: ContextualizedLane,
|
|
1060
1103
|
tasks: Array<LoaderTask>,
|
|
1061
1104
|
controller: AbortController,
|
|
1062
|
-
redirects: number,
|
|
1063
1105
|
settlement: Promise<IndexedOutcome | undefined>,
|
|
1064
1106
|
onReady?: () => void,
|
|
1065
1107
|
): Promise<ReducedLane | ControlOutcome> {
|
|
@@ -1112,7 +1154,7 @@ async function reduceLane(
|
|
|
1112
1154
|
if (
|
|
1113
1155
|
outcome[0 /* kind */] !== REDIRECTED ||
|
|
1114
1156
|
outcome[1 /* redirect */].options.reloadDocument ||
|
|
1115
|
-
|
|
1157
|
+
outcome[2 /* location */]
|
|
1116
1158
|
) {
|
|
1117
1159
|
discardBackground(router, lane)
|
|
1118
1160
|
return outcome as ControlOutcome
|
|
@@ -1148,6 +1190,9 @@ async function reduceLane(
|
|
|
1148
1190
|
}
|
|
1149
1191
|
}
|
|
1150
1192
|
install()
|
|
1193
|
+
if (!outcome) {
|
|
1194
|
+
onReady?.()
|
|
1195
|
+
}
|
|
1151
1196
|
const route = getRoute(router, match)
|
|
1152
1197
|
try {
|
|
1153
1198
|
await waitFor<unknown>(
|
|
@@ -1165,14 +1210,13 @@ async function reduceLane(
|
|
|
1165
1210
|
controller.signal,
|
|
1166
1211
|
)
|
|
1167
1212
|
} catch (cause) {
|
|
1168
|
-
if (cause === controller.signal) {
|
|
1213
|
+
if (cause === controller.signal && controller.signal.aborted) {
|
|
1169
1214
|
discardBackground(router, lane)
|
|
1170
|
-
return
|
|
1215
|
+
return CANCELED_OUTCOME
|
|
1171
1216
|
}
|
|
1172
1217
|
}
|
|
1173
1218
|
if (!outcome) {
|
|
1174
1219
|
match.status = 'success'
|
|
1175
|
-
onReady?.()
|
|
1176
1220
|
} else if (redirectLimitExceeded) {
|
|
1177
1221
|
controller.abort()
|
|
1178
1222
|
await Promise.all([
|
|
@@ -1224,7 +1268,7 @@ export async function projectLane(
|
|
|
1224
1268
|
match.styles = head?.styles
|
|
1225
1269
|
match.scripts = scripts
|
|
1226
1270
|
} catch (cause) {
|
|
1227
|
-
if (cause === signal) {
|
|
1271
|
+
if (cause === signal && signal.aborted) {
|
|
1228
1272
|
break
|
|
1229
1273
|
}
|
|
1230
1274
|
console.error(cause)
|
|
@@ -1244,111 +1288,112 @@ async function executeClientLane(
|
|
|
1244
1288
|
options: ExecuteLaneOptions,
|
|
1245
1289
|
): Promise<LaneResult> {
|
|
1246
1290
|
const matched = [location, matches as Array<WorkMatch>] as MatchedLane
|
|
1247
|
-
const
|
|
1248
|
-
let
|
|
1249
|
-
|
|
1250
|
-
const
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
options[0 /* controller */].signal,
|
|
1255
|
-
plannedBoundary,
|
|
1256
|
-
)
|
|
1257
|
-
if (boundary !== plannedBoundary) {
|
|
1258
|
-
matches[plannedBoundary]!._notFound = undefined
|
|
1259
|
-
matches[boundary]!._notFound = true
|
|
1260
|
-
}
|
|
1261
|
-
plannedBoundary = boundary
|
|
1262
|
-
}
|
|
1263
|
-
let end = plannedBoundary < 0 ? matches.length : plannedBoundary + 1
|
|
1264
|
-
let retainedEnd = 0
|
|
1265
|
-
while (retainedEnd < end && retainedEnd !== plannedBoundary) {
|
|
1266
|
-
const match = matches[retainedEnd]!
|
|
1267
|
-
const committed = options[3 /* base */][retainedEnd]
|
|
1268
|
-
const visible = presented[retainedEnd]
|
|
1269
|
-
if (
|
|
1270
|
-
committed?.id !== match.id ||
|
|
1271
|
-
committed.status !== 'success' ||
|
|
1272
|
-
committed._notFound ||
|
|
1273
|
-
match.preload ||
|
|
1274
|
-
visible?.id !== match.id ||
|
|
1275
|
-
visible.status !== 'success' ||
|
|
1276
|
-
visible._notFound
|
|
1277
|
-
) {
|
|
1278
|
-
break
|
|
1279
|
-
}
|
|
1280
|
-
retainedEnd++
|
|
1281
|
-
}
|
|
1282
|
-
const tasks: Array<LoaderTask> = []
|
|
1283
|
-
const start = options[7 /* resolvedPrefix */] ?? 0
|
|
1284
|
-
let semanticParent = start
|
|
1285
|
-
? Promise.resolve(matched[1 /* matches */][start - 1]!)
|
|
1286
|
-
: undefined
|
|
1287
|
-
const planSuccessfulLane = () => {
|
|
1288
|
-
for (let index = start; index < end; index++) {
|
|
1289
|
-
if (options[0 /* controller */].signal.aborted) {
|
|
1290
|
-
break
|
|
1291
|
-
}
|
|
1292
|
-
semanticParent = createLoaderTask(
|
|
1293
|
-
router,
|
|
1294
|
-
matched as ContextualizedLane,
|
|
1295
|
-
index,
|
|
1296
|
-
tasks,
|
|
1297
|
-
semanticParent,
|
|
1298
|
-
options,
|
|
1299
|
-
retainedEnd,
|
|
1300
|
-
)
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
// From here on `matched` is contextualized: `contextualize` communicates
|
|
1304
|
-
// through mutation plus a failure return, so the phase brand is asserted at
|
|
1305
|
-
// the two use sites below rather than granted by a (byte-costing) return.
|
|
1306
|
-
const failure = await contextualize(
|
|
1307
|
-
router,
|
|
1308
|
-
matched,
|
|
1309
|
-
options,
|
|
1310
|
-
end,
|
|
1311
|
-
planSuccessfulLane,
|
|
1312
|
-
retainedEnd,
|
|
1313
|
-
)
|
|
1314
|
-
if (failure) {
|
|
1315
|
-
options[5 /* sync */] = true
|
|
1316
|
-
end = failure[0 /* index */]
|
|
1317
|
-
if (failure[1 /* outcome */][0 /* kind */] === NOT_FOUND) {
|
|
1318
|
-
failure[2 /* boundary */] = await getNotFoundBoundary(
|
|
1291
|
+
const signal = options[0 /* controller */].signal
|
|
1292
|
+
let reduced: ReducedLane | ControlOutcome
|
|
1293
|
+
try {
|
|
1294
|
+
const presented = router.stores.matches.get()
|
|
1295
|
+
let plannedBoundary = matches.findIndex((match) => match._notFound)
|
|
1296
|
+
if (router.options.notFoundMode !== 'root' && plannedBoundary >= 0) {
|
|
1297
|
+
const boundary = await getNotFoundBoundary(
|
|
1319
1298
|
router,
|
|
1320
1299
|
matched[1 /* matches */],
|
|
1321
|
-
|
|
1322
|
-
|
|
1300
|
+
undefined,
|
|
1301
|
+
signal,
|
|
1302
|
+
plannedBoundary,
|
|
1323
1303
|
)
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1304
|
+
if (boundary !== plannedBoundary) {
|
|
1305
|
+
matches[plannedBoundary]!._notFound = undefined
|
|
1306
|
+
matches[boundary]!._notFound = true
|
|
1307
|
+
}
|
|
1308
|
+
plannedBoundary = boundary
|
|
1309
|
+
}
|
|
1310
|
+
let end = plannedBoundary < 0 ? matches.length : plannedBoundary + 1
|
|
1311
|
+
let retainedEnd = 0
|
|
1312
|
+
while (retainedEnd < end && retainedEnd !== plannedBoundary) {
|
|
1313
|
+
const match = matches[retainedEnd]!
|
|
1314
|
+
const committed = options[2 /* base */][retainedEnd]
|
|
1315
|
+
const visible = presented[retainedEnd]
|
|
1316
|
+
if (
|
|
1317
|
+
committed?.id !== match.id ||
|
|
1318
|
+
committed.status !== 'success' ||
|
|
1319
|
+
committed._notFound ||
|
|
1320
|
+
match.preload ||
|
|
1321
|
+
visible?.id !== match.id ||
|
|
1322
|
+
visible.status !== 'success' ||
|
|
1323
|
+
visible._notFound
|
|
1324
|
+
) {
|
|
1325
|
+
break
|
|
1336
1326
|
}
|
|
1327
|
+
retainedEnd++
|
|
1337
1328
|
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1329
|
+
const tasks: Array<LoaderTask> = []
|
|
1330
|
+
const start = options[6 /* resolvedPrefix */] ?? 0
|
|
1331
|
+
let semanticParent = start
|
|
1332
|
+
? Promise.resolve(matched[1 /* matches */][start - 1]!)
|
|
1333
|
+
: undefined
|
|
1334
|
+
const planSuccessfulLane = () => {
|
|
1335
|
+
for (let index = start; index < end; index++) {
|
|
1336
|
+
if (signal.aborted) {
|
|
1337
|
+
break
|
|
1338
|
+
}
|
|
1339
|
+
semanticParent = createLoaderTask(
|
|
1340
|
+
router,
|
|
1341
|
+
matched as ContextualizedLane,
|
|
1342
|
+
index,
|
|
1343
|
+
tasks,
|
|
1344
|
+
semanticParent,
|
|
1345
|
+
options,
|
|
1346
|
+
retainedEnd,
|
|
1347
|
+
)
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
// From here on `matched` is contextualized: `contextualize` communicates
|
|
1351
|
+
// through mutation plus a failure return, so the phase brand is asserted at
|
|
1352
|
+
// the two use sites below rather than granted by a (byte-costing) return.
|
|
1353
|
+
const failure = await contextualize(
|
|
1354
|
+
router,
|
|
1355
|
+
matched,
|
|
1356
|
+
options,
|
|
1357
|
+
end,
|
|
1358
|
+
planSuccessfulLane,
|
|
1359
|
+
retainedEnd,
|
|
1360
|
+
)
|
|
1361
|
+
if (failure) {
|
|
1362
|
+
options[4 /* sync */] = true
|
|
1363
|
+
end = failure[0 /* index */]
|
|
1364
|
+
if (failure[1 /* outcome */][0 /* kind */] === NOT_FOUND) {
|
|
1365
|
+
const boundary = await getNotFoundBoundary(
|
|
1366
|
+
router,
|
|
1367
|
+
matched[1 /* matches */],
|
|
1368
|
+
failure,
|
|
1369
|
+
signal,
|
|
1370
|
+
)
|
|
1371
|
+
failure[2 /* boundary */] = boundary
|
|
1372
|
+
end = Math.min(end, boundary + 1)
|
|
1373
|
+
} else if (failure[1 /* outcome */][0 /* kind */] >= REDIRECTED) {
|
|
1374
|
+
end = 0
|
|
1375
|
+
}
|
|
1376
|
+
planSuccessfulLane()
|
|
1377
|
+
}
|
|
1378
|
+
if (!signal.aborted && !options[3 /* preload */]) {
|
|
1379
|
+
const abort: Array<AbortController> = []
|
|
1380
|
+
for (const [id, flight] of router._flights ?? []) {
|
|
1381
|
+
if (!flight[2 /* leases */]) {
|
|
1382
|
+
router._flights!.delete(id)
|
|
1383
|
+
abort.push(flight[1 /* controller */])
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
for (const controller of abort) {
|
|
1387
|
+
controller.abort()
|
|
1388
|
+
}
|
|
1340
1389
|
}
|
|
1341
|
-
}
|
|
1342
|
-
let reduced: ReducedLane | ControlOutcome
|
|
1343
|
-
try {
|
|
1344
1390
|
const reduction = reduceLane(
|
|
1345
1391
|
router,
|
|
1346
1392
|
matched as ContextualizedLane,
|
|
1347
1393
|
tasks,
|
|
1348
1394
|
options[0 /* controller */],
|
|
1349
|
-
options[1 /* redirects */],
|
|
1350
1395
|
settleTasks(tasks, failure, matched[2 /* background */]),
|
|
1351
|
-
options[
|
|
1396
|
+
options[7 /* onReady */],
|
|
1352
1397
|
)
|
|
1353
1398
|
if (matched[2 /* background */]?.length) {
|
|
1354
1399
|
matched[3 /* backgroundSettlement */] = settleTasks(
|
|
@@ -1367,6 +1412,9 @@ async function executeClientLane(
|
|
|
1367
1412
|
reduced = await reduction
|
|
1368
1413
|
} catch (cause) {
|
|
1369
1414
|
discardBackground(router, matched)
|
|
1415
|
+
if (cause === signal && signal.aborted) {
|
|
1416
|
+
return CANCELED_OUTCOME
|
|
1417
|
+
}
|
|
1370
1418
|
throw cause
|
|
1371
1419
|
}
|
|
1372
1420
|
if (isControl(reduced)) {
|
|
@@ -1375,9 +1423,9 @@ async function executeClientLane(
|
|
|
1375
1423
|
return projectLane(
|
|
1376
1424
|
router,
|
|
1377
1425
|
reduced,
|
|
1378
|
-
|
|
1379
|
-
options[
|
|
1380
|
-
? options[
|
|
1426
|
+
signal,
|
|
1427
|
+
options[6 /* resolvedPrefix */] === reduced[1 /* matches */].length
|
|
1428
|
+
? options[6 /* resolvedPrefix */]
|
|
1381
1429
|
: 0,
|
|
1382
1430
|
)
|
|
1383
1431
|
}
|
|
@@ -1392,127 +1440,146 @@ function offerPending(router: CoordinatorRouter, tx: LoadTransaction): void {
|
|
|
1392
1440
|
if (router._tx !== tx) {
|
|
1393
1441
|
return
|
|
1394
1442
|
}
|
|
1395
|
-
let session = router._pending
|
|
1396
|
-
let tookOver = false
|
|
1397
|
-
const sessionMatchId =
|
|
1398
|
-
session?.[0 /* owner */][3 /* matches */][session[1 /* boundary */]]?.id
|
|
1399
|
-
if (session?.[0 /* owner */] !== tx) {
|
|
1400
|
-
if (
|
|
1401
|
-
session &&
|
|
1402
|
-
tx[3 /* matches */][session[1 /* boundary */]]?.id === sessionMatchId
|
|
1403
|
-
) {
|
|
1404
|
-
session[0 /* owner */] = tx
|
|
1405
|
-
tookOver = true
|
|
1406
|
-
} else {
|
|
1407
|
-
clearTimeout(session?.[3 /* timer */])
|
|
1408
|
-
router._pending = session = undefined
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
1443
|
const matches = tx[3 /* matches */]
|
|
1412
1444
|
const presented = router.stores.matches.get()
|
|
1413
|
-
let
|
|
1414
|
-
let delay: number | undefined
|
|
1415
|
-
let min!: number
|
|
1416
|
-
let component: unknown
|
|
1417
|
-
let presentedPending = false
|
|
1445
|
+
let session = router._pending
|
|
1418
1446
|
for (let index = 0; index < matches.length; index++) {
|
|
1419
1447
|
const match = matches[index]!
|
|
1420
|
-
const success = match.status === 'success'
|
|
1421
|
-
presentedPending =
|
|
1448
|
+
const success = match.status === 'success' && !match._notFound
|
|
1449
|
+
const presentedPending =
|
|
1422
1450
|
presented[index]?.id === match.id &&
|
|
1423
1451
|
presented[index]?.status === 'pending'
|
|
1424
1452
|
if (success && !presentedPending) {
|
|
1425
1453
|
continue
|
|
1426
1454
|
}
|
|
1427
1455
|
const route = getRoute(router, match as WorkMatch)
|
|
1428
|
-
delay =
|
|
1456
|
+
const delay =
|
|
1429
1457
|
(success && presentedPending) || match.invalid
|
|
1430
1458
|
? 0
|
|
1431
1459
|
: (route.options.pendingMs ?? router.options.defaultPendingMs)
|
|
1432
|
-
component =
|
|
1460
|
+
const component =
|
|
1433
1461
|
route.options.pendingComponent ??
|
|
1434
1462
|
(router.options as any).defaultPendingComponent
|
|
1435
1463
|
if (!component || typeof delay !== 'number' || delay === Infinity) {
|
|
1464
|
+
if (session) {
|
|
1465
|
+
session[0 /* generation */] = tx
|
|
1466
|
+
session[2 /* deadline */] = 0
|
|
1467
|
+
session[4 /* ack */] = true
|
|
1468
|
+
}
|
|
1436
1469
|
return
|
|
1437
1470
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
session
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
!tookOver &&
|
|
1466
|
-
session[5 /* component */] === component
|
|
1467
|
-
) {
|
|
1468
|
-
return
|
|
1469
|
-
}
|
|
1470
|
-
session[5 /* component */] = component
|
|
1471
|
-
if (!session[4 /* ack */]) {
|
|
1472
|
-
clearTimeout(session[3 /* timer */])
|
|
1473
|
-
const remaining = session[2 /* deadline */] - Date.now()
|
|
1474
|
-
if (remaining > 0) {
|
|
1475
|
-
session[3 /* timer */] = setTimeout(
|
|
1476
|
-
() => offerPending(router, tx),
|
|
1477
|
-
remaining,
|
|
1478
|
-
)
|
|
1471
|
+
const min =
|
|
1472
|
+
route.options.pendingMinMs ?? router.options.defaultPendingMinMs ?? 0
|
|
1473
|
+
let tookOver = false
|
|
1474
|
+
if (session?.[1 /* boundaryId */] === match.id) {
|
|
1475
|
+
tookOver = session[0 /* generation */] !== tx
|
|
1476
|
+
session[0 /* generation */] = tx
|
|
1477
|
+
} else {
|
|
1478
|
+
clearTimeout(session?.[3 /* revealTimer */])
|
|
1479
|
+
router._pending = session = undefined
|
|
1480
|
+
}
|
|
1481
|
+
if (!session) {
|
|
1482
|
+
// Hydration and redirects can preserve pending presentation without a session.
|
|
1483
|
+
// Do not delay it again; conservatively start pendingMinMs from now.
|
|
1484
|
+
router._pending = session = [
|
|
1485
|
+
tx,
|
|
1486
|
+
match.id,
|
|
1487
|
+
presentedPending ? Date.now() + min : tx[4 /* startedAt */] + delay,
|
|
1488
|
+
undefined,
|
|
1489
|
+
presentedPending || undefined,
|
|
1490
|
+
component,
|
|
1491
|
+
]
|
|
1492
|
+
}
|
|
1493
|
+
if (
|
|
1494
|
+
session[4 /* ack */] &&
|
|
1495
|
+
!tookOver &&
|
|
1496
|
+
session[5 /* component */] === component
|
|
1497
|
+
) {
|
|
1479
1498
|
return
|
|
1480
1499
|
}
|
|
1481
|
-
session[
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
if (
|
|
1492
|
-
rendered &&
|
|
1493
|
-
router._pending === session &&
|
|
1494
|
-
session[4 /* ack */] === ack &&
|
|
1495
|
-
!session[2 /* deadline */]
|
|
1496
|
-
) {
|
|
1497
|
-
session[2 /* deadline */] = Date.now() + min
|
|
1500
|
+
session[5 /* component */] = component
|
|
1501
|
+
if (!session[4 /* ack */]) {
|
|
1502
|
+
clearTimeout(session[3 /* revealTimer */])
|
|
1503
|
+
const remaining = session[2 /* deadline */] - Date.now()
|
|
1504
|
+
if (remaining > 0) {
|
|
1505
|
+
session[3 /* revealTimer */] = setTimeout(
|
|
1506
|
+
() => offerPending(router, tx),
|
|
1507
|
+
remaining,
|
|
1508
|
+
)
|
|
1509
|
+
return
|
|
1498
1510
|
}
|
|
1499
|
-
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1511
|
+
session[2 /* deadline */] = 0
|
|
1512
|
+
}
|
|
1513
|
+
const offered = matches.map((match) => ({
|
|
1514
|
+
...match,
|
|
1515
|
+
_flight: undefined,
|
|
1516
|
+
}))
|
|
1517
|
+
offered[index]!.status = 'pending'
|
|
1518
|
+
const ack = (session[4 /* ack */] = router
|
|
1519
|
+
.startTransition(() => router.stores.setMatches(offered), offered)
|
|
1520
|
+
.then((rendered) => {
|
|
1521
|
+
if (
|
|
1522
|
+
rendered &&
|
|
1523
|
+
router._pending === session &&
|
|
1524
|
+
session![4 /* ack */] === ack &&
|
|
1525
|
+
!session![2 /* deadline */]
|
|
1526
|
+
) {
|
|
1527
|
+
session![2 /* deadline */] = Date.now() + min
|
|
1528
|
+
}
|
|
1529
|
+
return rendered
|
|
1530
|
+
}))
|
|
1531
|
+
return
|
|
1532
|
+
}
|
|
1502
1533
|
}
|
|
1503
1534
|
|
|
1504
1535
|
/**
|
|
1505
|
-
* Cancels pending UI timing
|
|
1506
|
-
*
|
|
1536
|
+
* Cancels pending UI timing unless the current successor can take over the
|
|
1537
|
+
* same boundary that remains painted.
|
|
1507
1538
|
*/
|
|
1508
1539
|
function finishPending(router: CoordinatorRouter, tx: LoadTransaction): void {
|
|
1509
1540
|
const session = router._pending
|
|
1510
|
-
if (
|
|
1511
|
-
|
|
1541
|
+
if (
|
|
1542
|
+
router._tx === tx ||
|
|
1543
|
+
!router._tx?.[3 /* matches */].some(
|
|
1544
|
+
(match) => match.id === session?.[1 /* boundaryId */],
|
|
1545
|
+
)
|
|
1546
|
+
) {
|
|
1547
|
+
clearTimeout(session?.[3 /* revealTimer */])
|
|
1512
1548
|
router._pending = undefined
|
|
1513
1549
|
}
|
|
1514
1550
|
}
|
|
1515
1551
|
|
|
1552
|
+
async function awaitPendingMinimum(
|
|
1553
|
+
router: CoordinatorRouter,
|
|
1554
|
+
tx: LoadTransaction,
|
|
1555
|
+
): Promise<void> {
|
|
1556
|
+
const session = router._pending
|
|
1557
|
+
if (!session) {
|
|
1558
|
+
return
|
|
1559
|
+
}
|
|
1560
|
+
clearTimeout(session[3 /* revealTimer */])
|
|
1561
|
+
const remaining = session[2 /* deadline */] - Date.now()
|
|
1562
|
+
if (
|
|
1563
|
+
!session[4 /* ack */] ||
|
|
1564
|
+
remaining <= 0 ||
|
|
1565
|
+
!_getRenderedMatches(tx[3 /* matches */]).some(
|
|
1566
|
+
(match) => match.id === session[1 /* boundaryId */],
|
|
1567
|
+
)
|
|
1568
|
+
) {
|
|
1569
|
+
return
|
|
1570
|
+
}
|
|
1571
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
1572
|
+
try {
|
|
1573
|
+
await waitFor(
|
|
1574
|
+
new Promise<void>((resolve) => {
|
|
1575
|
+
timer = setTimeout(resolve, remaining)
|
|
1576
|
+
}),
|
|
1577
|
+
tx[0 /* controller */].signal,
|
|
1578
|
+
)
|
|
1579
|
+
} catch {}
|
|
1580
|
+
clearTimeout(timer)
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1516
1583
|
function publishMatches(
|
|
1517
1584
|
router: CoordinatorRouter,
|
|
1518
1585
|
matches: Array<AnyRouteMatch>,
|
|
@@ -1521,11 +1588,6 @@ function publishMatches(
|
|
|
1521
1588
|
router.stores.setMatches(matches)
|
|
1522
1589
|
}
|
|
1523
1590
|
|
|
1524
|
-
function discardLane(router: AnyRouter, lane: ProjectedLane): void {
|
|
1525
|
-
transferMatchResources(router, lane[1 /* matches */])
|
|
1526
|
-
discardBackground(router, lane)
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
1591
|
function commitMatches(
|
|
1530
1592
|
router: CoordinatorRouter,
|
|
1531
1593
|
tx: LoadTransaction,
|
|
@@ -1542,46 +1604,48 @@ function commitMatches(
|
|
|
1542
1604
|
}
|
|
1543
1605
|
const cut = _getRenderedMatches(matches).length
|
|
1544
1606
|
const cached = new Map<string, AnyRouteMatch>()
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
(
|
|
1555
|
-
candidate
|
|
1556
|
-
|
|
1607
|
+
if (process.env.NODE_ENV === 'production' || !tx[6 /* refresh */]) {
|
|
1608
|
+
const now = Date.now()
|
|
1609
|
+
for (const match of [...previous, ...previousCached.values()]) {
|
|
1610
|
+
// Rendered-prefix ids and settled successes anywhere in the lane are
|
|
1611
|
+
// authoritative: retaining an older same-id generation would shadow them
|
|
1612
|
+
// at the next planning pass. Unsettled beyond-boundary matches are not —
|
|
1613
|
+
// they must not evict a newer same-id preload.
|
|
1614
|
+
if (
|
|
1615
|
+
match.status !== 'success' ||
|
|
1616
|
+
matches.some(
|
|
1617
|
+
(candidate, index) =>
|
|
1618
|
+
candidate.id === match.id &&
|
|
1619
|
+
(index < cut || candidate.status === 'success'),
|
|
1620
|
+
)
|
|
1621
|
+
) {
|
|
1622
|
+
continue
|
|
1623
|
+
}
|
|
1624
|
+
const work = match as WorkMatch
|
|
1625
|
+
const route = getRoute(router, work)
|
|
1626
|
+
if (
|
|
1627
|
+
!route.options.loader ||
|
|
1628
|
+
now - match.updatedAt >=
|
|
1629
|
+
(match.preload
|
|
1630
|
+
? (route.options.preloadGcTime ??
|
|
1631
|
+
router.options.defaultPreloadGcTime ??
|
|
1632
|
+
300_000)
|
|
1633
|
+
: (route.options.gcTime ?? router.options.defaultGcTime ?? 300_000))
|
|
1634
|
+
) {
|
|
1635
|
+
continue
|
|
1636
|
+
}
|
|
1637
|
+
cached.set(
|
|
1638
|
+
match.id,
|
|
1639
|
+
previousCached.get(match.id) === match
|
|
1640
|
+
? match
|
|
1641
|
+
: ({
|
|
1642
|
+
...match,
|
|
1643
|
+
_flight: undefined,
|
|
1644
|
+
isFetching: false,
|
|
1645
|
+
context: {},
|
|
1646
|
+
} as WorkMatch),
|
|
1557
1647
|
)
|
|
1558
|
-
) {
|
|
1559
|
-
continue
|
|
1560
|
-
}
|
|
1561
|
-
const work = match as WorkMatch
|
|
1562
|
-
const route = getRoute(router, work)
|
|
1563
|
-
if (
|
|
1564
|
-
!route.options.loader ||
|
|
1565
|
-
now - match.updatedAt >=
|
|
1566
|
-
(match.preload
|
|
1567
|
-
? (route.options.preloadGcTime ??
|
|
1568
|
-
router.options.defaultPreloadGcTime ??
|
|
1569
|
-
300_000)
|
|
1570
|
-
: (route.options.gcTime ?? router.options.defaultGcTime ?? 300_000))
|
|
1571
|
-
) {
|
|
1572
|
-
continue
|
|
1573
1648
|
}
|
|
1574
|
-
cached.set(
|
|
1575
|
-
match.id,
|
|
1576
|
-
previousCached.get(match.id) === match
|
|
1577
|
-
? match
|
|
1578
|
-
: ({
|
|
1579
|
-
...match,
|
|
1580
|
-
_flight: undefined,
|
|
1581
|
-
isFetching: false,
|
|
1582
|
-
context: {},
|
|
1583
|
-
} as WorkMatch),
|
|
1584
|
-
)
|
|
1585
1649
|
}
|
|
1586
1650
|
// The lane becomes committed before publication can synchronously reenter.
|
|
1587
1651
|
tx[3 /* matches */] = []
|
|
@@ -1592,155 +1656,13 @@ function commitMatches(
|
|
|
1592
1656
|
[...previousCached.values(), ...previous],
|
|
1593
1657
|
[...matches, ...cached.values()],
|
|
1594
1658
|
)
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
router: CoordinatorRouter,
|
|
1600
|
-
tx: LoadTransaction,
|
|
1601
|
-
matches: LaneMatches<'projected'>,
|
|
1602
|
-
checkpoint: PublicationCheckpoint,
|
|
1603
|
-
): void {
|
|
1604
|
-
const previous = router._committed
|
|
1605
|
-
const previousCached = router._cache
|
|
1606
|
-
for (const match of matches) {
|
|
1607
|
-
match.preload = false
|
|
1608
|
-
}
|
|
1609
|
-
const cached = new Map<string, AnyRouteMatch>()
|
|
1610
|
-
// Delay releasing the previous owners until the HMR render is acknowledged.
|
|
1611
|
-
// Old generations must not become reusable cache entries after refresh.
|
|
1612
|
-
tx[3 /* matches */] = []
|
|
1613
|
-
router._cache = cached
|
|
1614
|
-
checkpoint.previousMatches = previous
|
|
1615
|
-
checkpoint.previousCache = previousCached
|
|
1616
|
-
checkpoint.published = true
|
|
1617
|
-
publishMatches(router, matches)
|
|
1618
|
-
if (!checkpoint.published || router._tx !== tx) {
|
|
1619
|
-
return
|
|
1620
|
-
}
|
|
1621
|
-
runRouteLifecycle(router, previous, matches, () => router._tx === tx)
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
function settlePublication(
|
|
1625
|
-
router: CoordinatorRouter,
|
|
1626
|
-
checkpoint: PublicationCheckpoint,
|
|
1627
|
-
): void {
|
|
1628
|
-
if (!checkpoint.published) {
|
|
1629
|
-
return
|
|
1630
|
-
}
|
|
1631
|
-
checkpoint.published = false
|
|
1632
|
-
transferMatchResources(
|
|
1633
|
-
router,
|
|
1634
|
-
[...checkpoint.previousCache.values(), ...checkpoint.previousMatches],
|
|
1635
|
-
[...router._cache.values(), ...router._committed],
|
|
1636
|
-
)
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
function rollbackPublication(
|
|
1640
|
-
router: CoordinatorRouter,
|
|
1641
|
-
tx: LoadTransaction,
|
|
1642
|
-
lane: ProjectedLane,
|
|
1643
|
-
checkpoint: PublicationCheckpoint,
|
|
1644
|
-
): boolean {
|
|
1645
|
-
if (
|
|
1646
|
-
!checkpoint.published ||
|
|
1647
|
-
router._tx !== tx ||
|
|
1648
|
-
router._committed !== lane[1 /* matches */]
|
|
1649
|
-
) {
|
|
1650
|
-
settlePublication(router, checkpoint)
|
|
1651
|
-
return false
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
const discarded = [...router._cache.values(), ...router._committed]
|
|
1655
|
-
const restored = [
|
|
1656
|
-
...checkpoint.previousCache.values(),
|
|
1657
|
-
...checkpoint.previousMatches,
|
|
1658
|
-
]
|
|
1659
|
-
router._cache = checkpoint.previousCache
|
|
1660
|
-
router._committed = checkpoint.previousMatches
|
|
1661
|
-
checkpoint.published = false
|
|
1662
|
-
|
|
1663
|
-
for (const match of discarded as Array<WorkMatch>) {
|
|
1664
|
-
if (
|
|
1665
|
-
!restored.includes(match) &&
|
|
1666
|
-
match._flight &&
|
|
1667
|
-
router._flights?.get(match.id) === match._flight
|
|
1668
|
-
) {
|
|
1669
|
-
router._flights.delete(match.id)
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
finishPending(router, tx)
|
|
1674
|
-
router.batch(() => {
|
|
1675
|
-
router.stores.status.set('idle')
|
|
1676
|
-
router.stores.setMatches(checkpoint.previousPresentation)
|
|
1677
|
-
})
|
|
1678
|
-
tx[0 /* controller */].abort()
|
|
1679
|
-
transferMatchResources(router, discarded, restored)
|
|
1680
|
-
discardBackground(router, lane)
|
|
1681
|
-
if (router._tx === tx && router._commitPromise === checkpoint.commitPromise) {
|
|
1682
|
-
router._commitPromise?.resolve()
|
|
1683
|
-
router._commitPromise = undefined
|
|
1684
|
-
}
|
|
1685
|
-
return true
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
async function transitionRefresh(
|
|
1689
|
-
router: CoordinatorRouter,
|
|
1690
|
-
tx: LoadTransaction,
|
|
1691
|
-
lane: ProjectedLane,
|
|
1692
|
-
changeInfo: ReturnType<typeof getLocationChangeInfo>,
|
|
1693
|
-
): Promise<boolean | undefined> {
|
|
1694
|
-
const refresh = tx[6 /* refresh */]!
|
|
1695
|
-
const checkpoint: PublicationCheckpoint = {
|
|
1696
|
-
previousMatches: router._committed,
|
|
1697
|
-
previousPresentation: refresh[0 /* presentation */],
|
|
1698
|
-
previousCache: router._cache,
|
|
1699
|
-
commitPromise: router._commitPromise,
|
|
1700
|
-
published: false,
|
|
1701
|
-
}
|
|
1702
|
-
const commit = () => {
|
|
1703
|
-
finishPending(router, tx)
|
|
1704
|
-
refresh[2 /* rollback */] = rollback
|
|
1705
|
-
commitRefreshMatches(router, tx, lane[1 /* matches */], checkpoint)
|
|
1706
|
-
if (!checkpoint.published || router._tx !== tx) {
|
|
1707
|
-
return
|
|
1708
|
-
}
|
|
1709
|
-
router.emit({ type: 'onLoad', ...changeInfo })
|
|
1710
|
-
if (router._tx === tx) {
|
|
1711
|
-
router.emit({ type: 'onBeforeRouteMount', ...changeInfo })
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1714
|
-
const rollback = () => {
|
|
1715
|
-
if (refresh[2 /* rollback */] === rollback) {
|
|
1716
|
-
refresh[2 /* rollback */] = undefined
|
|
1717
|
-
}
|
|
1718
|
-
const restored = rollbackPublication(router, tx, lane, checkpoint)
|
|
1719
|
-
router._cancelTransition?.()
|
|
1720
|
-
return restored
|
|
1721
|
-
}
|
|
1722
|
-
try {
|
|
1723
|
-
const rendered = await router.startTransition(commit, lane[1 /* matches */])
|
|
1724
|
-
if (refresh[2 /* rollback */] === rollback) {
|
|
1725
|
-
refresh[2 /* rollback */] = undefined
|
|
1726
|
-
}
|
|
1727
|
-
if (checkpoint.published) {
|
|
1728
|
-
const handoff = refresh[1 /* handoff */]
|
|
1729
|
-
if (handoff && router._handoff === handoff) {
|
|
1730
|
-
handoff[1 /* finish */]()
|
|
1731
|
-
}
|
|
1732
|
-
if (router._tx === tx) {
|
|
1733
|
-
tx[6 /* refresh */] = undefined
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
settlePublication(router, checkpoint)
|
|
1737
|
-
return rendered
|
|
1738
|
-
} catch (cause) {
|
|
1739
|
-
if (rollback()) {
|
|
1740
|
-
return
|
|
1659
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1660
|
+
const handoff = tx[6 /* refresh */]?.[0 /* handoff */]
|
|
1661
|
+
if (handoff && router._handoff === handoff) {
|
|
1662
|
+
handoff[1 /* finish */]()
|
|
1741
1663
|
}
|
|
1742
|
-
throw cause
|
|
1743
1664
|
}
|
|
1665
|
+
runRouteLifecycle(router, previous, matches, tx)
|
|
1744
1666
|
}
|
|
1745
1667
|
|
|
1746
1668
|
async function awaitCurrent(
|
|
@@ -1760,35 +1682,44 @@ async function awaitCurrent(
|
|
|
1760
1682
|
async function followRedirect(
|
|
1761
1683
|
router: CoordinatorRouter,
|
|
1762
1684
|
tx: LoadTransaction,
|
|
1763
|
-
|
|
1685
|
+
outcome: RedirectOutcome,
|
|
1764
1686
|
): Promise<void> {
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
function restoreCommitted(
|
|
1774
|
-
router: CoordinatorRouter,
|
|
1775
|
-
tx: LoadTransaction,
|
|
1776
|
-
): void {
|
|
1777
|
-
finishPending(router, tx)
|
|
1778
|
-
tx[0 /* controller */].abort()
|
|
1779
|
-
transferMatchResources(router, tx[3 /* matches */])
|
|
1780
|
-
tx[3 /* matches */] = []
|
|
1781
|
-
if (router._tx !== tx) {
|
|
1687
|
+
const redirect = outcome[1 /* redirect */]
|
|
1688
|
+
const location = outcome[2 /* location */]
|
|
1689
|
+
if (!location) {
|
|
1690
|
+
await router.navigate({
|
|
1691
|
+
...redirect.options,
|
|
1692
|
+
replace: true,
|
|
1693
|
+
ignoreBlocker: true,
|
|
1694
|
+
} as any)
|
|
1782
1695
|
return
|
|
1783
1696
|
}
|
|
1784
|
-
|
|
1785
|
-
router.
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1697
|
+
if (redirect.options.reloadDocument) {
|
|
1698
|
+
await router.navigate({
|
|
1699
|
+
href: location.publicHref,
|
|
1700
|
+
reloadDocument: true,
|
|
1701
|
+
replace: true,
|
|
1702
|
+
ignoreBlocker: true,
|
|
1703
|
+
} as any)
|
|
1704
|
+
return
|
|
1791
1705
|
}
|
|
1706
|
+
;(location as ParsedLocation & { _redirects?: number })._redirects =
|
|
1707
|
+
tx[1 /* redirects */] + 1
|
|
1708
|
+
router._pendingLocation = location
|
|
1709
|
+
const committed = router.commitLocation({
|
|
1710
|
+
...location,
|
|
1711
|
+
viewTransition: redirect.options.viewTransition,
|
|
1712
|
+
replace: true,
|
|
1713
|
+
resetScroll: redirect.options.resetScroll,
|
|
1714
|
+
hashScrollIntoView: redirect.options.hashScrollIntoView,
|
|
1715
|
+
ignoreBlocker: true,
|
|
1716
|
+
})
|
|
1717
|
+
queueMicrotask(() => {
|
|
1718
|
+
if (router._pendingLocation === location) {
|
|
1719
|
+
router._pendingLocation = undefined
|
|
1720
|
+
}
|
|
1721
|
+
})
|
|
1722
|
+
await committed
|
|
1792
1723
|
}
|
|
1793
1724
|
|
|
1794
1725
|
async function runBackground(
|
|
@@ -1814,7 +1745,6 @@ async function runBackground(
|
|
|
1814
1745
|
lane,
|
|
1815
1746
|
tasks,
|
|
1816
1747
|
tx[0 /* controller */],
|
|
1817
|
-
tx[1 /* redirects */],
|
|
1818
1748
|
settlement,
|
|
1819
1749
|
)
|
|
1820
1750
|
} catch (cause) {
|
|
@@ -1828,7 +1758,7 @@ async function runBackground(
|
|
|
1828
1758
|
router._tx === tx &&
|
|
1829
1759
|
router._committed === base
|
|
1830
1760
|
) {
|
|
1831
|
-
await followRedirect(router, tx, reduced
|
|
1761
|
+
await followRedirect(router, tx, reduced)
|
|
1832
1762
|
}
|
|
1833
1763
|
return
|
|
1834
1764
|
}
|
|
@@ -1863,7 +1793,6 @@ async function runClientTransaction(
|
|
|
1863
1793
|
const options: ExecuteLaneOptions = [
|
|
1864
1794
|
tx[0 /* controller */],
|
|
1865
1795
|
tx[1 /* redirects */],
|
|
1866
|
-
() => router._tx === tx && !!tx[3 /* matches */].length,
|
|
1867
1796
|
router._committed,
|
|
1868
1797
|
undefined,
|
|
1869
1798
|
sync,
|
|
@@ -1879,63 +1808,38 @@ async function runClientTransaction(
|
|
|
1879
1808
|
)
|
|
1880
1809
|
|
|
1881
1810
|
if (isControl(result)) {
|
|
1882
|
-
|
|
1811
|
+
const follow = result[0 /* kind */] === REDIRECTED && router._tx === tx
|
|
1812
|
+
if (!follow || result[1 /* redirect */].options.reloadDocument) {
|
|
1883
1813
|
finishPending(router, tx)
|
|
1884
|
-
transferMatchResources(router, tx[3 /* matches */])
|
|
1885
|
-
tx[3 /* matches */] = []
|
|
1886
|
-
if (router._tx === tx) {
|
|
1887
|
-
if (process.env.NODE_ENV !== 'production' && tx[6 /* refresh */]) {
|
|
1888
|
-
router._refreshNextLoad = true
|
|
1889
|
-
}
|
|
1890
|
-
await followRedirect(router, tx, result[1 /* redirect */])
|
|
1891
|
-
}
|
|
1892
|
-
} else {
|
|
1893
|
-
restoreCommitted(router, tx)
|
|
1894
1814
|
}
|
|
1815
|
+
transferMatchResources(router, tx[3 /* matches */])
|
|
1816
|
+
tx[3 /* matches */] = []
|
|
1817
|
+
if (!follow) {
|
|
1818
|
+
return
|
|
1819
|
+
}
|
|
1820
|
+
if (router._tx !== tx) {
|
|
1821
|
+
finishPending(router, tx)
|
|
1822
|
+
return
|
|
1823
|
+
}
|
|
1824
|
+
if (process.env.NODE_ENV !== 'production' && tx[6 /* refresh */]) {
|
|
1825
|
+
router._refreshNextLoad = true
|
|
1826
|
+
}
|
|
1827
|
+
await followRedirect(router, tx, result)
|
|
1895
1828
|
return
|
|
1896
1829
|
}
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
* rendered, there is no minimum wait; if another load took it over, that
|
|
1903
|
-
* load owns the deadline.
|
|
1904
|
-
*/
|
|
1905
|
-
clearTimeout(pending[3 /* timer */])
|
|
1906
|
-
if (pending[4 /* ack */]) {
|
|
1907
|
-
const signal = tx[0 /* controller */].signal
|
|
1908
|
-
let rendered = false
|
|
1909
|
-
try {
|
|
1910
|
-
rendered = await waitFor(pending[4 /* ack */], signal)
|
|
1911
|
-
} catch (cause) {
|
|
1912
|
-
if (cause !== signal) {
|
|
1913
|
-
throw cause
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
if (
|
|
1917
|
-
rendered &&
|
|
1918
|
-
router._pending === pending &&
|
|
1919
|
-
pending[0 /* owner */] === tx
|
|
1920
|
-
) {
|
|
1921
|
-
const remaining = pending[2 /* deadline */] - Date.now()
|
|
1922
|
-
if (remaining > 0) {
|
|
1923
|
-
try {
|
|
1924
|
-
await waitFor(
|
|
1925
|
-
new Promise<void>((resolve) => {
|
|
1926
|
-
pending[3 /* timer */] = setTimeout(resolve, remaining)
|
|
1927
|
-
}),
|
|
1928
|
-
signal,
|
|
1929
|
-
)
|
|
1930
|
-
} catch {}
|
|
1931
|
-
clearTimeout(pending[3 /* timer */])
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1830
|
+
if (router._tx !== tx) {
|
|
1831
|
+
finishPending(router, tx)
|
|
1832
|
+
transferMatchResources(router, result[1 /* matches */])
|
|
1833
|
+
discardBackground(router, result)
|
|
1834
|
+
return
|
|
1935
1835
|
}
|
|
1836
|
+
// Only an acknowledged fallback owns a minimum. Recheck at the commit
|
|
1837
|
+
// boundary because native view transitions can defer their update callback.
|
|
1838
|
+
await awaitPendingMinimum(router, tx)
|
|
1936
1839
|
if (router._tx !== tx) {
|
|
1937
1840
|
finishPending(router, tx)
|
|
1938
|
-
|
|
1841
|
+
transferMatchResources(router, result[1 /* matches */])
|
|
1842
|
+
discardBackground(router, result)
|
|
1939
1843
|
return
|
|
1940
1844
|
}
|
|
1941
1845
|
const toLocation = tx[2 /* location */]
|
|
@@ -1946,7 +1850,16 @@ async function runClientTransaction(
|
|
|
1946
1850
|
const background = result[2 /* background */]
|
|
1947
1851
|
await router.startViewTransition(async () => {
|
|
1948
1852
|
if (router._tx !== tx) {
|
|
1949
|
-
|
|
1853
|
+
finishPending(router, tx)
|
|
1854
|
+
transferMatchResources(router, result[1 /* matches */])
|
|
1855
|
+
discardBackground(router, result)
|
|
1856
|
+
return
|
|
1857
|
+
}
|
|
1858
|
+
await awaitPendingMinimum(router, tx)
|
|
1859
|
+
if (router._tx !== tx) {
|
|
1860
|
+
finishPending(router, tx)
|
|
1861
|
+
transferMatchResources(router, result[1 /* matches */])
|
|
1862
|
+
discardBackground(router, result)
|
|
1950
1863
|
return
|
|
1951
1864
|
}
|
|
1952
1865
|
const commit = () => {
|
|
@@ -1960,24 +1873,20 @@ async function runClientTransaction(
|
|
|
1960
1873
|
router.emit({ type: 'onBeforeRouteMount', ...changeInfo })
|
|
1961
1874
|
}
|
|
1962
1875
|
}
|
|
1963
|
-
const rendered =
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
if (
|
|
1968
|
-
|
|
1969
|
-
tx[6 /* refresh */] &&
|
|
1970
|
-
rendered === undefined
|
|
1971
|
-
) {
|
|
1972
|
-
return
|
|
1876
|
+
const rendered = await router.startTransition(
|
|
1877
|
+
commit,
|
|
1878
|
+
result[1 /* matches */],
|
|
1879
|
+
)
|
|
1880
|
+
if (process.env.NODE_ENV !== 'production' && tx[6 /* refresh */]) {
|
|
1881
|
+
tx[6 /* refresh */] = undefined
|
|
1973
1882
|
}
|
|
1974
1883
|
if (router._tx !== tx) {
|
|
1975
1884
|
discardBackground(router, result)
|
|
1976
1885
|
return
|
|
1977
1886
|
}
|
|
1978
1887
|
if (background?.length) {
|
|
1979
|
-
// Publish
|
|
1980
|
-
// Otherwise
|
|
1888
|
+
// Publish background matches only after the foreground acknowledgement.
|
|
1889
|
+
// Otherwise fast work can replace the acknowledged generation
|
|
1981
1890
|
// before the framework commits it and strand the navigation.
|
|
1982
1891
|
runBackground(
|
|
1983
1892
|
router,
|
|
@@ -2011,12 +1920,8 @@ export async function loadClientRoute(
|
|
|
2011
1920
|
): Promise<void> {
|
|
2012
1921
|
let rematerialize = false
|
|
2013
1922
|
if (process.env.NODE_ENV !== 'production') {
|
|
2014
|
-
router._tx?.[6 /* refresh */]?.[2 /* rollback */]?.()
|
|
2015
1923
|
rematerialize = !!router._refreshNextLoad || !!router._tx?.[6 /* refresh */]
|
|
2016
1924
|
}
|
|
2017
|
-
const refreshPresentation = rematerialize
|
|
2018
|
-
? router.stores.matches.get()
|
|
2019
|
-
: undefined
|
|
2020
1925
|
const previousOwner = router._tx
|
|
2021
1926
|
const resolvedLocation = router.stores.resolvedLocation.get()
|
|
2022
1927
|
const previousLocation = resolvedLocation ?? router.stores.location.get()
|
|
@@ -2056,36 +1961,15 @@ export async function loadClientRoute(
|
|
|
2056
1961
|
return
|
|
2057
1962
|
}
|
|
2058
1963
|
const sameHref = previousLocation.href === location.href
|
|
2059
|
-
let matches: Array<AnyRouteMatch>
|
|
2060
1964
|
let controller = preflight
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
acquireMatchResources(matches)
|
|
2070
|
-
} catch (cause) {
|
|
2071
|
-
preflight.abort()
|
|
2072
|
-
if (!isRedirect(cause)) {
|
|
2073
|
-
if (process.env.NODE_ENV !== 'production' && rematerialize) {
|
|
2074
|
-
router._refreshNextLoad = undefined
|
|
2075
|
-
}
|
|
2076
|
-
await awaitCurrent(router)
|
|
2077
|
-
router._commitPromise?.resolve()
|
|
2078
|
-
router._commitPromise = undefined
|
|
2079
|
-
return
|
|
2080
|
-
}
|
|
2081
|
-
await router.navigate({
|
|
2082
|
-
...cause.options,
|
|
2083
|
-
replace: true,
|
|
2084
|
-
ignoreBlocker: true,
|
|
2085
|
-
})
|
|
2086
|
-
await awaitCurrent(router, previousOwner)
|
|
2087
|
-
return
|
|
2088
|
-
}
|
|
1965
|
+
const matches =
|
|
1966
|
+
process.env.NODE_ENV !== 'production' && rematerialize
|
|
1967
|
+
? router.matchRoutes(location, {
|
|
1968
|
+
_controller: preflight,
|
|
1969
|
+
_rematerialize: true,
|
|
1970
|
+
})
|
|
1971
|
+
: router.matchRoutes(location, { _controller: preflight })
|
|
1972
|
+
acquireMatchResources(matches)
|
|
2089
1973
|
const resolvedPrefix = hydrationController
|
|
2090
1974
|
? handoff
|
|
2091
1975
|
: undefined
|
|
@@ -2118,15 +2002,12 @@ export async function loadClientRoute(
|
|
|
2118
2002
|
resolvedPrefix,
|
|
2119
2003
|
),
|
|
2120
2004
|
)
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
}
|
|
2125
|
-
}),
|
|
2005
|
+
// Preserve the settlement turn in which immediately completed background
|
|
2006
|
+
// work can publish before callers resume from `load`.
|
|
2007
|
+
.then(),
|
|
2126
2008
|
]
|
|
2127
2009
|
if (process.env.NODE_ENV !== 'production' && rematerialize) {
|
|
2128
|
-
|
|
2129
|
-
tx[6 /* refresh */] = [refreshPresentation!, handoff]
|
|
2010
|
+
tx[6 /* refresh */] = [handoff]
|
|
2130
2011
|
router._refreshNextLoad = undefined
|
|
2131
2012
|
}
|
|
2132
2013
|
router._tx = tx
|
|
@@ -2159,20 +2040,19 @@ export async function loadClientRoute(
|
|
|
2159
2040
|
})
|
|
2160
2041
|
// Cold loads have no committed UI to retain, but provisional not-found
|
|
2161
2042
|
// matches must wait for lazy routes to place the final boundary.
|
|
2162
|
-
if (
|
|
2043
|
+
if (
|
|
2044
|
+
resolvedPrefix ||
|
|
2045
|
+
(!router._committed.length && !matches.some((match) => match._notFound))
|
|
2046
|
+
) {
|
|
2163
2047
|
offerPending(router, tx)
|
|
2164
2048
|
}
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
} finally {
|
|
2168
|
-
await awaitCurrent(router, tx)
|
|
2169
|
-
}
|
|
2049
|
+
await tx[5 /* done */]
|
|
2050
|
+
await awaitCurrent(router, tx)
|
|
2170
2051
|
}
|
|
2171
2052
|
|
|
2172
2053
|
export async function refreshClientRoute(
|
|
2173
2054
|
router: CoordinatorRouter,
|
|
2174
2055
|
): Promise<void> {
|
|
2175
|
-
router._tx?.[6 /* refresh */]?.[2 /* rollback */]?.()
|
|
2176
2056
|
const pending = router._tx
|
|
2177
2057
|
if (
|
|
2178
2058
|
pending &&
|
|
@@ -2184,7 +2064,7 @@ export async function refreshClientRoute(
|
|
|
2184
2064
|
await awaitCurrent(router, pending)
|
|
2185
2065
|
}
|
|
2186
2066
|
}
|
|
2187
|
-
// Existing owners remain
|
|
2067
|
+
// Existing owners remain presented but cannot donate stale work.
|
|
2188
2068
|
router._flights?.clear()
|
|
2189
2069
|
router.clearCache()
|
|
2190
2070
|
router._refreshNextLoad = true
|
|
@@ -2195,17 +2075,15 @@ export async function preloadClientRoute(
|
|
|
2195
2075
|
router: CoordinatorRouter,
|
|
2196
2076
|
opts: any,
|
|
2197
2077
|
redirects = 0,
|
|
2078
|
+
builtLocation?: ParsedLocation,
|
|
2198
2079
|
): Promise<Array<AnyRouteMatch> | undefined> {
|
|
2199
|
-
if (redirects > 20) {
|
|
2200
|
-
return
|
|
2201
|
-
}
|
|
2202
2080
|
if (
|
|
2203
2081
|
process.env.NODE_ENV !== 'production' &&
|
|
2204
2082
|
(router._refreshNextLoad || router._tx?.[6 /* refresh */])
|
|
2205
2083
|
) {
|
|
2206
2084
|
return
|
|
2207
2085
|
}
|
|
2208
|
-
const location =
|
|
2086
|
+
const location = builtLocation ?? router.buildLocation(opts)
|
|
2209
2087
|
const base = router._committed
|
|
2210
2088
|
const controller = new AbortController()
|
|
2211
2089
|
let matches: Array<AnyRouteMatch>
|
|
@@ -2229,9 +2107,6 @@ export async function preloadClientRoute(
|
|
|
2229
2107
|
result = await executeClientLane(router, location, matches, [
|
|
2230
2108
|
controller,
|
|
2231
2109
|
redirects,
|
|
2232
|
-
// Preload lanes run to completion even when unrelated navigations commit:
|
|
2233
|
-
// finished work seeds the cache.
|
|
2234
|
-
() => true,
|
|
2235
2110
|
base,
|
|
2236
2111
|
true,
|
|
2237
2112
|
])
|
|
@@ -2250,11 +2125,9 @@ export async function preloadClientRoute(
|
|
|
2250
2125
|
) {
|
|
2251
2126
|
return preloadClientRoute(
|
|
2252
2127
|
router,
|
|
2253
|
-
|
|
2254
|
-
...result[1 /* redirect */].options,
|
|
2255
|
-
_fromLocation: location,
|
|
2256
|
-
},
|
|
2128
|
+
result[1 /* redirect */].options,
|
|
2257
2129
|
redirects + 1,
|
|
2130
|
+
result[2 /* location */],
|
|
2258
2131
|
)
|
|
2259
2132
|
}
|
|
2260
2133
|
} catch (cause) {
|
|
@@ -2314,8 +2187,7 @@ export async function hydrate(router: AnyRouter): Promise<void> {
|
|
|
2314
2187
|
const previousPreflight = router._preflight
|
|
2315
2188
|
router._preflight = controller
|
|
2316
2189
|
previousPreflight?.abort()
|
|
2317
|
-
//
|
|
2318
|
-
// supersedes hydration.
|
|
2190
|
+
// Only a new slot owner supersedes hydration.
|
|
2319
2191
|
const isCurrent = () => router._preflight === controller
|
|
2320
2192
|
|
|
2321
2193
|
let location!: AnyRouter['latestLocation']
|
|
@@ -2515,7 +2387,11 @@ export async function hydrate(router: AnyRouter): Promise<void> {
|
|
|
2515
2387
|
params: match.params,
|
|
2516
2388
|
context: parentContext,
|
|
2517
2389
|
location,
|
|
2518
|
-
navigate:
|
|
2390
|
+
navigate: (opts: any) =>
|
|
2391
|
+
router.navigate({
|
|
2392
|
+
...opts,
|
|
2393
|
+
_fromLocation: location,
|
|
2394
|
+
}),
|
|
2519
2395
|
buildLocation: router.buildLocation,
|
|
2520
2396
|
cause: match.cause,
|
|
2521
2397
|
abortController: controller,
|