@tanstack/react-router 1.31.0 → 1.31.2
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/Matches.cjs +1 -5
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/Matches.d.cts +1 -1
- package/dist/cjs/RouterProvider.cjs +2 -2
- package/dist/cjs/RouterProvider.cjs.map +1 -1
- package/dist/cjs/redirects.cjs +4 -0
- package/dist/cjs/redirects.cjs.map +1 -1
- package/dist/cjs/redirects.d.cts +1 -0
- package/dist/cjs/router.cjs +209 -201
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +8 -7
- package/dist/esm/Matches.d.ts +1 -1
- package/dist/esm/Matches.js +1 -5
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/RouterProvider.js +2 -2
- package/dist/esm/RouterProvider.js.map +1 -1
- package/dist/esm/redirects.d.ts +1 -0
- package/dist/esm/redirects.js +4 -0
- package/dist/esm/redirects.js.map +1 -1
- package/dist/esm/router.d.ts +8 -7
- package/dist/esm/router.js +210 -202
- package/dist/esm/router.js.map +1 -1
- package/package.json +1 -1
- package/src/Matches.tsx +6 -6
- package/src/RouterProvider.tsx +2 -2
- package/src/redirects.ts +4 -0
- package/src/router.ts +288 -294
package/src/router.ts
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
trimPathLeft,
|
|
26
26
|
trimPathRight,
|
|
27
27
|
} from './path'
|
|
28
|
-
import { isRedirect } from './redirects'
|
|
28
|
+
import { isRedirect, isResolvedRedirect } from './redirects'
|
|
29
29
|
import { isNotFound } from './not-found'
|
|
30
30
|
import type * as React from 'react'
|
|
31
31
|
import type {
|
|
@@ -205,7 +205,7 @@ export interface BuildNextOptions {
|
|
|
205
205
|
unmaskOnReload?: boolean
|
|
206
206
|
}
|
|
207
207
|
from?: string
|
|
208
|
-
|
|
208
|
+
fromSearch?: unknown
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
export interface DehydratedRouterState {
|
|
@@ -305,7 +305,6 @@ export class Router<
|
|
|
305
305
|
)}`
|
|
306
306
|
resetNextScroll = true
|
|
307
307
|
shouldViewTransition?: true = undefined
|
|
308
|
-
navigateTimeout: Timeout | null = null
|
|
309
308
|
latestLoadPromise: Promise<void> = Promise.resolve()
|
|
310
309
|
subscribers = new Set<RouterListener<RouterEvent>>()
|
|
311
310
|
injectedHtml: Array<InjectedHtmlEntry> = []
|
|
@@ -584,10 +583,10 @@ export class Router<
|
|
|
584
583
|
})
|
|
585
584
|
}
|
|
586
585
|
|
|
587
|
-
checkLatest = (promise: Promise<void>):
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
586
|
+
checkLatest = (promise: Promise<void>): void => {
|
|
587
|
+
if (this.latestLoadPromise !== promise) {
|
|
588
|
+
throw this.latestLoadPromise
|
|
589
|
+
}
|
|
591
590
|
}
|
|
592
591
|
|
|
593
592
|
parseLocation = (
|
|
@@ -920,25 +919,25 @@ export class Router<
|
|
|
920
919
|
} = {},
|
|
921
920
|
matches?: Array<MakeRouteMatch<TRouteTree>>,
|
|
922
921
|
): ParsedLocation => {
|
|
923
|
-
|
|
924
|
-
let fromSearch = dest.
|
|
922
|
+
let fromPath = this.latestLocation.pathname
|
|
923
|
+
let fromSearch = dest.fromSearch || this.latestLocation.search
|
|
925
924
|
|
|
926
|
-
const fromMatches = this.matchRoutes(
|
|
925
|
+
const fromMatches = this.matchRoutes(
|
|
926
|
+
this.latestLocation.pathname,
|
|
927
|
+
fromSearch,
|
|
928
|
+
)
|
|
927
929
|
|
|
930
|
+
fromPath =
|
|
931
|
+
fromMatches.find((d) => d.id === dest.from)?.pathname || fromPath
|
|
928
932
|
fromSearch = last(fromMatches)?.search || this.latestLocation.search
|
|
929
933
|
|
|
930
934
|
const stayingMatches = matches?.filter((d) =>
|
|
931
935
|
fromMatches.find((e) => e.routeId === d.routeId),
|
|
932
936
|
)
|
|
933
937
|
|
|
934
|
-
const fromRoute = this.looseRoutesById[last(fromMatches)?.routeId]
|
|
935
|
-
|
|
936
938
|
let pathname = dest.to
|
|
937
|
-
? this.resolvePathWithBase(
|
|
938
|
-
|
|
939
|
-
`${dest.to}`,
|
|
940
|
-
)
|
|
941
|
-
: this.resolvePathWithBase(fromRoute?.fullPath, fromRoute?.fullPath)
|
|
939
|
+
? this.resolvePathWithBase(fromPath, `${dest.to}`)
|
|
940
|
+
: this.resolvePathWithBase(fromPath, fromPath)
|
|
942
941
|
|
|
943
942
|
const prevParams = { ...last(fromMatches)?.params }
|
|
944
943
|
|
|
@@ -1113,8 +1112,6 @@ export class Router<
|
|
|
1113
1112
|
viewTransition,
|
|
1114
1113
|
...next
|
|
1115
1114
|
}: ParsedLocation & CommitLocationOptions) => {
|
|
1116
|
-
if (this.navigateTimeout) clearTimeout(this.navigateTimeout)
|
|
1117
|
-
|
|
1118
1115
|
const isSameUrl = this.latestLocation.href === next.href
|
|
1119
1116
|
|
|
1120
1117
|
// If the next urls are the same and we're not replacing,
|
|
@@ -1209,19 +1206,180 @@ export class Router<
|
|
|
1209
1206
|
})
|
|
1210
1207
|
}
|
|
1211
1208
|
|
|
1209
|
+
load = async (): Promise<void> => {
|
|
1210
|
+
const promise = createControlledPromise<void>()
|
|
1211
|
+
this.latestLoadPromise = promise
|
|
1212
|
+
let redirect: ResolvedRedirect | undefined
|
|
1213
|
+
let notFound: NotFoundError | undefined
|
|
1214
|
+
|
|
1215
|
+
this.startReactTransition(async () => {
|
|
1216
|
+
try {
|
|
1217
|
+
const next = this.latestLocation
|
|
1218
|
+
const prevLocation = this.state.resolvedLocation
|
|
1219
|
+
const pathDidChange = prevLocation.href !== next.href
|
|
1220
|
+
|
|
1221
|
+
// Cancel any pending matches
|
|
1222
|
+
this.cancelMatches()
|
|
1223
|
+
|
|
1224
|
+
this.emit({
|
|
1225
|
+
type: 'onBeforeLoad',
|
|
1226
|
+
fromLocation: prevLocation,
|
|
1227
|
+
toLocation: next,
|
|
1228
|
+
pathChanged: pathDidChange,
|
|
1229
|
+
})
|
|
1230
|
+
|
|
1231
|
+
let pendingMatches!: Array<AnyRouteMatch>
|
|
1232
|
+
|
|
1233
|
+
this.__store.batch(() => {
|
|
1234
|
+
this.cleanCache()
|
|
1235
|
+
|
|
1236
|
+
// Match the routes
|
|
1237
|
+
pendingMatches = this.matchRoutes(next.pathname, next.search)
|
|
1238
|
+
|
|
1239
|
+
// Ingest the new matches
|
|
1240
|
+
this.__store.setState((s) => ({
|
|
1241
|
+
...s,
|
|
1242
|
+
status: 'pending',
|
|
1243
|
+
isLoading: true,
|
|
1244
|
+
location: next,
|
|
1245
|
+
pendingMatches,
|
|
1246
|
+
// If a cached moved to pendingMatches, remove it from cachedMatches
|
|
1247
|
+
cachedMatches: s.cachedMatches.filter((d) => {
|
|
1248
|
+
return !pendingMatches.find((e) => e.id === d.id)
|
|
1249
|
+
}),
|
|
1250
|
+
}))
|
|
1251
|
+
})
|
|
1252
|
+
|
|
1253
|
+
await this.loadMatches({
|
|
1254
|
+
matches: pendingMatches,
|
|
1255
|
+
location: next,
|
|
1256
|
+
checkLatest: () => this.checkLatest(promise),
|
|
1257
|
+
onReady: async () => {
|
|
1258
|
+
await this.startViewTransition(async () => {
|
|
1259
|
+
// this.viewTransitionPromise = createControlledPromise<true>()
|
|
1260
|
+
|
|
1261
|
+
// Commit the pending matches. If a previous match was
|
|
1262
|
+
// removed, place it in the cachedMatches
|
|
1263
|
+
let exitingMatches!: Array<AnyRouteMatch>
|
|
1264
|
+
let enteringMatches!: Array<AnyRouteMatch>
|
|
1265
|
+
let stayingMatches!: Array<AnyRouteMatch>
|
|
1266
|
+
|
|
1267
|
+
this.__store.batch(() => {
|
|
1268
|
+
this.__store.setState((s) => {
|
|
1269
|
+
const previousMatches = s.matches
|
|
1270
|
+
const newMatches = s.pendingMatches || s.matches
|
|
1271
|
+
|
|
1272
|
+
exitingMatches = previousMatches.filter(
|
|
1273
|
+
(match) => !newMatches.find((d) => d.id === match.id),
|
|
1274
|
+
)
|
|
1275
|
+
enteringMatches = newMatches.filter(
|
|
1276
|
+
(match) => !previousMatches.find((d) => d.id === match.id),
|
|
1277
|
+
)
|
|
1278
|
+
stayingMatches = previousMatches.filter((match) =>
|
|
1279
|
+
newMatches.find((d) => d.id === match.id),
|
|
1280
|
+
)
|
|
1281
|
+
|
|
1282
|
+
return {
|
|
1283
|
+
...s,
|
|
1284
|
+
isLoading: false,
|
|
1285
|
+
matches: newMatches,
|
|
1286
|
+
pendingMatches: undefined,
|
|
1287
|
+
cachedMatches: [
|
|
1288
|
+
...s.cachedMatches,
|
|
1289
|
+
...exitingMatches.filter((d) => d.status !== 'error'),
|
|
1290
|
+
],
|
|
1291
|
+
}
|
|
1292
|
+
})
|
|
1293
|
+
this.cleanCache()
|
|
1294
|
+
})
|
|
1295
|
+
|
|
1296
|
+
//
|
|
1297
|
+
;(
|
|
1298
|
+
[
|
|
1299
|
+
[exitingMatches, 'onLeave'],
|
|
1300
|
+
[enteringMatches, 'onEnter'],
|
|
1301
|
+
[stayingMatches, 'onStay'],
|
|
1302
|
+
] as const
|
|
1303
|
+
).forEach(([matches, hook]) => {
|
|
1304
|
+
matches.forEach((match) => {
|
|
1305
|
+
this.looseRoutesById[match.routeId]!.options[hook]?.(match)
|
|
1306
|
+
})
|
|
1307
|
+
})
|
|
1308
|
+
})
|
|
1309
|
+
},
|
|
1310
|
+
})
|
|
1311
|
+
} catch (err) {
|
|
1312
|
+
if (isResolvedRedirect(err)) {
|
|
1313
|
+
redirect = err
|
|
1314
|
+
if (!this.isServer) {
|
|
1315
|
+
this.navigate({ ...err, replace: true })
|
|
1316
|
+
this.load()
|
|
1317
|
+
}
|
|
1318
|
+
} else if (isNotFound(err)) {
|
|
1319
|
+
notFound = err
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
this.__store.setState((s) => ({
|
|
1323
|
+
...s,
|
|
1324
|
+
statusCode:
|
|
1325
|
+
redirect?.statusCode || notFound
|
|
1326
|
+
? 404
|
|
1327
|
+
: s.matches.some((d) => d.status === 'error')
|
|
1328
|
+
? 500
|
|
1329
|
+
: 200,
|
|
1330
|
+
redirect,
|
|
1331
|
+
}))
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
promise.resolve()
|
|
1335
|
+
})
|
|
1336
|
+
|
|
1337
|
+
return this.latestLoadPromise
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
startViewTransition = async (fn: () => Promise<void>) => {
|
|
1341
|
+
// Determine if we should start a view transition from the navigation
|
|
1342
|
+
// or from the router default
|
|
1343
|
+
const shouldViewTransition =
|
|
1344
|
+
this.shouldViewTransition ?? this.options.defaultViewTransition
|
|
1345
|
+
|
|
1346
|
+
// Reset the view transition flag
|
|
1347
|
+
delete this.shouldViewTransition
|
|
1348
|
+
// Attempt to start a view transition (or just apply the changes if we can't)
|
|
1349
|
+
;(shouldViewTransition && typeof document !== 'undefined'
|
|
1350
|
+
? document
|
|
1351
|
+
: undefined
|
|
1352
|
+
)
|
|
1353
|
+
// @ts-expect-error
|
|
1354
|
+
?.startViewTransition?.(fn) || fn()
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1212
1357
|
loadMatches = async ({
|
|
1213
1358
|
checkLatest,
|
|
1214
1359
|
location,
|
|
1215
1360
|
matches,
|
|
1216
1361
|
preload,
|
|
1362
|
+
onReady,
|
|
1217
1363
|
}: {
|
|
1218
|
-
checkLatest: () =>
|
|
1364
|
+
checkLatest: () => void
|
|
1219
1365
|
location: ParsedLocation
|
|
1220
1366
|
matches: Array<AnyRouteMatch>
|
|
1221
1367
|
preload?: boolean
|
|
1368
|
+
onReady?: () => Promise<void>
|
|
1222
1369
|
}): Promise<Array<MakeRouteMatch>> => {
|
|
1223
|
-
let latestPromise
|
|
1224
1370
|
let firstBadMatchIndex: number | undefined
|
|
1371
|
+
let rendered = false
|
|
1372
|
+
|
|
1373
|
+
const triggerOnReady = async () => {
|
|
1374
|
+
if (!rendered) {
|
|
1375
|
+
rendered = true
|
|
1376
|
+
await onReady?.()
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
if (!this.isServer && !this.state.matches.length) {
|
|
1381
|
+
triggerOnReady()
|
|
1382
|
+
}
|
|
1225
1383
|
|
|
1226
1384
|
const updateMatch = (
|
|
1227
1385
|
id: string,
|
|
@@ -1250,51 +1408,95 @@ export class Router<
|
|
|
1250
1408
|
return updated
|
|
1251
1409
|
}
|
|
1252
1410
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
;(async () => {
|
|
1256
|
-
try {
|
|
1257
|
-
const handleRedirectAndNotFound = (
|
|
1258
|
-
match: AnyRouteMatch,
|
|
1259
|
-
err: any,
|
|
1260
|
-
) => {
|
|
1261
|
-
if (isRedirect(err) || isNotFound(err)) {
|
|
1262
|
-
updateMatch(match.id, (prev) => ({
|
|
1263
|
-
...prev,
|
|
1264
|
-
status: isRedirect(err)
|
|
1265
|
-
? 'redirected'
|
|
1266
|
-
: isNotFound(err)
|
|
1267
|
-
? 'notFound'
|
|
1268
|
-
: 'error',
|
|
1269
|
-
isFetching: false,
|
|
1270
|
-
error: err,
|
|
1271
|
-
}))
|
|
1411
|
+
const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => {
|
|
1412
|
+
if (isResolvedRedirect(err)) throw err
|
|
1272
1413
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1414
|
+
if (isRedirect(err) || isNotFound(err)) {
|
|
1415
|
+
// if (!rendered) {
|
|
1416
|
+
updateMatch(match.id, (prev) => ({
|
|
1417
|
+
...prev,
|
|
1418
|
+
status: isRedirect(err)
|
|
1419
|
+
? 'redirected'
|
|
1420
|
+
: isNotFound(err)
|
|
1421
|
+
? 'notFound'
|
|
1422
|
+
: 'error',
|
|
1423
|
+
isFetching: false,
|
|
1424
|
+
error: err,
|
|
1425
|
+
}))
|
|
1426
|
+
// }
|
|
1276
1427
|
|
|
1277
|
-
|
|
1278
|
-
const redirect = this.resolveRedirect(err)
|
|
1428
|
+
rendered = true
|
|
1279
1429
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1430
|
+
if (!(err as any).routeId) {
|
|
1431
|
+
;(err as any).routeId = match.routeId
|
|
1432
|
+
}
|
|
1283
1433
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1434
|
+
if (isRedirect(err)) {
|
|
1435
|
+
err = this.resolveRedirect(err)
|
|
1436
|
+
throw err
|
|
1437
|
+
} else if (isNotFound(err)) {
|
|
1438
|
+
this.handleNotFound(matches, err)
|
|
1439
|
+
throw err
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1291
1443
|
|
|
1444
|
+
try {
|
|
1445
|
+
await new Promise<void>((resolveAll, rejectAll) => {
|
|
1446
|
+
;(async () => {
|
|
1447
|
+
try {
|
|
1292
1448
|
// Check each match middleware to see if the route can be accessed
|
|
1293
1449
|
// eslint-disable-next-line prefer-const
|
|
1294
1450
|
for (let [index, match] of matches.entries()) {
|
|
1295
1451
|
const parentMatch = matches[index - 1]
|
|
1296
1452
|
const route = this.looseRoutesById[match.routeId]!
|
|
1297
1453
|
const abortController = new AbortController()
|
|
1454
|
+
let loadPromise = match.loadPromise
|
|
1455
|
+
|
|
1456
|
+
const pendingMs =
|
|
1457
|
+
route.options.pendingMs ?? this.options.defaultPendingMs
|
|
1458
|
+
|
|
1459
|
+
const shouldPending = !!(
|
|
1460
|
+
onReady &&
|
|
1461
|
+
!this.isServer &&
|
|
1462
|
+
!preload &&
|
|
1463
|
+
(route.options.loader || route.options.beforeLoad) &&
|
|
1464
|
+
typeof pendingMs === 'number' &&
|
|
1465
|
+
pendingMs !== Infinity &&
|
|
1466
|
+
(route.options.pendingComponent ??
|
|
1467
|
+
this.options.defaultPendingComponent)
|
|
1468
|
+
)
|
|
1469
|
+
|
|
1470
|
+
if (shouldPending) {
|
|
1471
|
+
// If we might show a pending component, we need to wait for the
|
|
1472
|
+
// pending promise to resolve before we start showing that state
|
|
1473
|
+
setTimeout(() => {
|
|
1474
|
+
try {
|
|
1475
|
+
checkLatest()
|
|
1476
|
+
// Update the match and prematurely resolve the loadMatches promise so that
|
|
1477
|
+
// the pending component can start rendering
|
|
1478
|
+
triggerOnReady()
|
|
1479
|
+
} catch {}
|
|
1480
|
+
}, pendingMs)
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
if (match.isFetching) {
|
|
1484
|
+
continue
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
const previousResolve = loadPromise.resolve
|
|
1488
|
+
// Create a new one
|
|
1489
|
+
loadPromise = createControlledPromise<void>(
|
|
1490
|
+
// Resolve the old when we we resolve the new one
|
|
1491
|
+
previousResolve,
|
|
1492
|
+
)
|
|
1493
|
+
|
|
1494
|
+
// Otherwise, load the route
|
|
1495
|
+
matches[index] = match = updateMatch(match.id, (prev) => ({
|
|
1496
|
+
...prev,
|
|
1497
|
+
isFetching: 'beforeLoad',
|
|
1498
|
+
loadPromise,
|
|
1499
|
+
}))
|
|
1298
1500
|
|
|
1299
1501
|
const handleSerialError = (err: any, routerCode: string) => {
|
|
1300
1502
|
err.routerCode = routerCode
|
|
@@ -1325,42 +1527,10 @@ export class Router<
|
|
|
1325
1527
|
handleSerialError(match.searchError, 'VALIDATE_SEARCH')
|
|
1326
1528
|
}
|
|
1327
1529
|
|
|
1328
|
-
// if (match.globalNotFound && !preload) {
|
|
1329
|
-
// handleSerialError(notFound({ _global: true }), 'NOT_FOUND')
|
|
1330
|
-
// }
|
|
1331
|
-
|
|
1332
1530
|
try {
|
|
1333
1531
|
const parentContext =
|
|
1334
1532
|
parentMatch?.context ?? this.options.context ?? {}
|
|
1335
1533
|
|
|
1336
|
-
const pendingMs =
|
|
1337
|
-
route.options.pendingMs ?? this.options.defaultPendingMs
|
|
1338
|
-
const pendingPromise =
|
|
1339
|
-
typeof pendingMs !== 'number' || pendingMs <= 0
|
|
1340
|
-
? Promise.resolve()
|
|
1341
|
-
: new Promise<void>((r) => {
|
|
1342
|
-
if (pendingMs !== Infinity) setTimeout(r, pendingMs)
|
|
1343
|
-
})
|
|
1344
|
-
|
|
1345
|
-
const shouldPending =
|
|
1346
|
-
!this.isServer &&
|
|
1347
|
-
!preload &&
|
|
1348
|
-
(route.options.loader || route.options.beforeLoad) &&
|
|
1349
|
-
typeof pendingMs === 'number' &&
|
|
1350
|
-
(route.options.pendingComponent ??
|
|
1351
|
-
this.options.defaultPendingComponent)
|
|
1352
|
-
|
|
1353
|
-
if (shouldPending) {
|
|
1354
|
-
// If we might show a pending component, we need to wait for the
|
|
1355
|
-
// pending promise to resolve before we start showing that state
|
|
1356
|
-
pendingPromise.then(async () => {
|
|
1357
|
-
if ((latestPromise = checkLatest())) return latestPromise
|
|
1358
|
-
// Update the match and prematurely resolve the loadMatches promise so that
|
|
1359
|
-
// the pending component can start rendering
|
|
1360
|
-
resolveAll()
|
|
1361
|
-
})
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
1534
|
const beforeLoadContext =
|
|
1365
1535
|
(await route.options.beforeLoad?.({
|
|
1366
1536
|
search: match.search,
|
|
@@ -1375,6 +1545,8 @@ export class Router<
|
|
|
1375
1545
|
cause: preload ? 'preload' : match.cause,
|
|
1376
1546
|
})) ?? ({} as any)
|
|
1377
1547
|
|
|
1548
|
+
checkLatest()
|
|
1549
|
+
|
|
1378
1550
|
if (
|
|
1379
1551
|
isRedirect(beforeLoadContext) ||
|
|
1380
1552
|
isNotFound(beforeLoadContext)
|
|
@@ -1404,6 +1576,8 @@ export class Router<
|
|
|
1404
1576
|
}
|
|
1405
1577
|
}
|
|
1406
1578
|
|
|
1579
|
+
checkLatest()
|
|
1580
|
+
|
|
1407
1581
|
const validResolvedMatches = matches.slice(0, firstBadMatchIndex)
|
|
1408
1582
|
const matchPromises: Array<Promise<any>> = []
|
|
1409
1583
|
|
|
@@ -1431,7 +1605,6 @@ export class Router<
|
|
|
1431
1605
|
let lazyPromise = Promise.resolve()
|
|
1432
1606
|
let componentsPromise = Promise.resolve() as Promise<any>
|
|
1433
1607
|
let loaderPromise = existing.loaderPromise
|
|
1434
|
-
let loadPromise = existing.loadPromise
|
|
1435
1608
|
|
|
1436
1609
|
// If the Matches component rendered
|
|
1437
1610
|
// the pending component and needs to show it for
|
|
@@ -1444,8 +1617,7 @@ export class Router<
|
|
|
1444
1617
|
if (latestMatch?.minPendingPromise) {
|
|
1445
1618
|
await latestMatch.minPendingPromise
|
|
1446
1619
|
|
|
1447
|
-
|
|
1448
|
-
return await latestPromise
|
|
1620
|
+
checkLatest()
|
|
1449
1621
|
|
|
1450
1622
|
updateMatch(latestMatch.id, (prev) => ({
|
|
1451
1623
|
...prev,
|
|
@@ -1455,7 +1627,7 @@ export class Router<
|
|
|
1455
1627
|
}
|
|
1456
1628
|
|
|
1457
1629
|
try {
|
|
1458
|
-
if (
|
|
1630
|
+
if (match.isFetching === 'beforeLoad') {
|
|
1459
1631
|
// If the user doesn't want the route to reload, just
|
|
1460
1632
|
// resolve with the existing loader data
|
|
1461
1633
|
|
|
@@ -1464,11 +1636,14 @@ export class Router<
|
|
|
1464
1636
|
// }
|
|
1465
1637
|
|
|
1466
1638
|
// Otherwise, load the route
|
|
1467
|
-
matches[index] = match =
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1639
|
+
matches[index] = match = updateMatch(
|
|
1640
|
+
match.id,
|
|
1641
|
+
(prev) => ({
|
|
1642
|
+
...prev,
|
|
1643
|
+
isFetching: 'loader',
|
|
1644
|
+
fetchCount: match.fetchCount + 1,
|
|
1645
|
+
}),
|
|
1646
|
+
)
|
|
1472
1647
|
|
|
1473
1648
|
lazyPromise =
|
|
1474
1649
|
route.lazyFn?.().then((lazyRoute) => {
|
|
@@ -1495,38 +1670,27 @@ export class Router<
|
|
|
1495
1670
|
// we can use the options
|
|
1496
1671
|
await lazyPromise
|
|
1497
1672
|
|
|
1498
|
-
|
|
1499
|
-
return await latestPromise
|
|
1673
|
+
checkLatest()
|
|
1500
1674
|
|
|
1501
1675
|
// Kick off the loader!
|
|
1502
1676
|
loaderPromise = route.options.loader?.(loaderContext)
|
|
1503
1677
|
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1678
|
+
matches[index] = match = updateMatch(
|
|
1679
|
+
match.id,
|
|
1680
|
+
(prev) => ({
|
|
1681
|
+
...prev,
|
|
1682
|
+
loaderPromise,
|
|
1683
|
+
}),
|
|
1509
1684
|
)
|
|
1510
1685
|
}
|
|
1511
1686
|
|
|
1512
|
-
matches[index] = match = updateMatch(match.id, (prev) => ({
|
|
1513
|
-
...prev,
|
|
1514
|
-
loaderPromise,
|
|
1515
|
-
loadPromise,
|
|
1516
|
-
}))
|
|
1517
|
-
|
|
1518
1687
|
const loaderData = await loaderPromise
|
|
1519
|
-
|
|
1520
|
-
return await latestPromise
|
|
1688
|
+
checkLatest()
|
|
1521
1689
|
|
|
1522
1690
|
handleRedirectAndNotFound(match, loaderData)
|
|
1523
1691
|
|
|
1524
|
-
if ((latestPromise = checkLatest()))
|
|
1525
|
-
return await latestPromise
|
|
1526
|
-
|
|
1527
1692
|
await potentialPendingMinPromise()
|
|
1528
|
-
|
|
1529
|
-
return await latestPromise
|
|
1693
|
+
checkLatest()
|
|
1530
1694
|
|
|
1531
1695
|
const meta = route.options.meta?.({
|
|
1532
1696
|
params: match.params,
|
|
@@ -1548,13 +1712,11 @@ export class Router<
|
|
|
1548
1712
|
headers,
|
|
1549
1713
|
}))
|
|
1550
1714
|
} catch (e) {
|
|
1715
|
+
checkLatest()
|
|
1551
1716
|
let error = e
|
|
1552
|
-
if ((latestPromise = checkLatest()))
|
|
1553
|
-
return await latestPromise
|
|
1554
1717
|
|
|
1555
1718
|
await potentialPendingMinPromise()
|
|
1556
|
-
|
|
1557
|
-
return await latestPromise
|
|
1719
|
+
checkLatest()
|
|
1558
1720
|
|
|
1559
1721
|
handleRedirectAndNotFound(match, e)
|
|
1560
1722
|
|
|
@@ -1577,10 +1739,9 @@ export class Router<
|
|
|
1577
1739
|
// to be preloaded before we resolve the match
|
|
1578
1740
|
await componentsPromise
|
|
1579
1741
|
|
|
1580
|
-
|
|
1581
|
-
return await latestPromise
|
|
1742
|
+
checkLatest()
|
|
1582
1743
|
|
|
1583
|
-
loadPromise.resolve()
|
|
1744
|
+
match.loadPromise.resolve()
|
|
1584
1745
|
}
|
|
1585
1746
|
|
|
1586
1747
|
// This is where all of the stale-while-revalidate magic happens
|
|
@@ -1615,8 +1776,7 @@ export class Router<
|
|
|
1615
1776
|
try {
|
|
1616
1777
|
await fetch()
|
|
1617
1778
|
} catch (err) {
|
|
1618
|
-
|
|
1619
|
-
return await latestPromise
|
|
1779
|
+
checkLatest()
|
|
1620
1780
|
handleRedirectAndNotFound(match, err)
|
|
1621
1781
|
}
|
|
1622
1782
|
}
|
|
@@ -1636,7 +1796,7 @@ export class Router<
|
|
|
1636
1796
|
}),
|
|
1637
1797
|
)
|
|
1638
1798
|
|
|
1639
|
-
|
|
1799
|
+
checkLatest()
|
|
1640
1800
|
|
|
1641
1801
|
resolveAll()
|
|
1642
1802
|
} catch (err) {
|
|
@@ -1644,6 +1804,7 @@ export class Router<
|
|
|
1644
1804
|
}
|
|
1645
1805
|
})()
|
|
1646
1806
|
})
|
|
1807
|
+
await triggerOnReady()
|
|
1647
1808
|
} catch (err) {
|
|
1648
1809
|
if (isRedirect(err) || isNotFound(err)) {
|
|
1649
1810
|
throw err
|
|
@@ -1670,173 +1831,6 @@ export class Router<
|
|
|
1670
1831
|
return this.load()
|
|
1671
1832
|
}
|
|
1672
1833
|
|
|
1673
|
-
load = async (): Promise<void> => {
|
|
1674
|
-
let resolveLoad!: (value: void) => void
|
|
1675
|
-
let rejectLoad!: (reason: any) => void
|
|
1676
|
-
|
|
1677
|
-
const promise = new Promise<void>((resolve, reject) => {
|
|
1678
|
-
resolveLoad = resolve
|
|
1679
|
-
rejectLoad = reject
|
|
1680
|
-
})
|
|
1681
|
-
|
|
1682
|
-
this.latestLoadPromise = promise
|
|
1683
|
-
|
|
1684
|
-
let latestPromise: Promise<void> | undefined | null
|
|
1685
|
-
|
|
1686
|
-
this.startReactTransition(async () => {
|
|
1687
|
-
try {
|
|
1688
|
-
const next = this.latestLocation
|
|
1689
|
-
const prevLocation = this.state.resolvedLocation
|
|
1690
|
-
const pathDidChange = prevLocation.href !== next.href
|
|
1691
|
-
|
|
1692
|
-
// Cancel any pending matches
|
|
1693
|
-
this.cancelMatches()
|
|
1694
|
-
|
|
1695
|
-
this.emit({
|
|
1696
|
-
type: 'onBeforeLoad',
|
|
1697
|
-
fromLocation: prevLocation,
|
|
1698
|
-
toLocation: next,
|
|
1699
|
-
pathChanged: pathDidChange,
|
|
1700
|
-
})
|
|
1701
|
-
|
|
1702
|
-
let pendingMatches!: Array<AnyRouteMatch>
|
|
1703
|
-
const previousMatches = this.state.matches
|
|
1704
|
-
|
|
1705
|
-
this.__store.batch(() => {
|
|
1706
|
-
this.cleanCache()
|
|
1707
|
-
|
|
1708
|
-
// Match the routes
|
|
1709
|
-
pendingMatches = this.matchRoutes(next.pathname, next.search)
|
|
1710
|
-
|
|
1711
|
-
// Ingest the new matches
|
|
1712
|
-
// If a cached moved to pendingMatches, remove it from cachedMatches
|
|
1713
|
-
this.__store.setState((s) => ({
|
|
1714
|
-
...s,
|
|
1715
|
-
status: 'pending',
|
|
1716
|
-
isLoading: true,
|
|
1717
|
-
location: next,
|
|
1718
|
-
pendingMatches,
|
|
1719
|
-
cachedMatches: s.cachedMatches.filter((d) => {
|
|
1720
|
-
return !pendingMatches.find((e) => e.id === d.id)
|
|
1721
|
-
}),
|
|
1722
|
-
}))
|
|
1723
|
-
})
|
|
1724
|
-
|
|
1725
|
-
let redirect: ResolvedRedirect | undefined
|
|
1726
|
-
let notFound: NotFoundError | undefined
|
|
1727
|
-
|
|
1728
|
-
try {
|
|
1729
|
-
// Load the matches
|
|
1730
|
-
const loadMatchesPromise = this.loadMatches({
|
|
1731
|
-
matches: pendingMatches,
|
|
1732
|
-
location: next,
|
|
1733
|
-
checkLatest: () => this.checkLatest(promise),
|
|
1734
|
-
})
|
|
1735
|
-
|
|
1736
|
-
if (previousMatches.length || this.isServer) {
|
|
1737
|
-
await loadMatchesPromise
|
|
1738
|
-
}
|
|
1739
|
-
} catch (err) {
|
|
1740
|
-
if (isRedirect(err)) {
|
|
1741
|
-
redirect = err as ResolvedRedirect
|
|
1742
|
-
} else if (isNotFound(err)) {
|
|
1743
|
-
notFound = err
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
// Swallow all other errors that happen inside
|
|
1747
|
-
// of loadMatches. These errors will be handled
|
|
1748
|
-
// as state on each match.
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
// Only apply the latest transition
|
|
1752
|
-
if ((latestPromise = this.checkLatest(promise))) {
|
|
1753
|
-
return latestPromise
|
|
1754
|
-
}
|
|
1755
|
-
|
|
1756
|
-
const exitingMatches = previousMatches.filter(
|
|
1757
|
-
(match) => !pendingMatches.find((d) => d.id === match.id),
|
|
1758
|
-
)
|
|
1759
|
-
const enteringMatches = pendingMatches.filter(
|
|
1760
|
-
(match) => !previousMatches.find((d) => d.id === match.id),
|
|
1761
|
-
)
|
|
1762
|
-
const stayingMatches = previousMatches.filter((match) =>
|
|
1763
|
-
pendingMatches.find((d) => d.id === match.id),
|
|
1764
|
-
)
|
|
1765
|
-
|
|
1766
|
-
// Determine if we should start a view transition from the navigation
|
|
1767
|
-
// or from the router default
|
|
1768
|
-
const shouldViewTransition =
|
|
1769
|
-
this.shouldViewTransition ?? this.options.defaultViewTransition
|
|
1770
|
-
|
|
1771
|
-
// Reset the view transition flag
|
|
1772
|
-
delete this.shouldViewTransition
|
|
1773
|
-
|
|
1774
|
-
const apply = () => {
|
|
1775
|
-
// this.viewTransitionPromise = createControlledPromise<true>()
|
|
1776
|
-
|
|
1777
|
-
// Commit the pending matches. If a previous match was
|
|
1778
|
-
// removed, place it in the cachedMatches
|
|
1779
|
-
this.__store.batch(() => {
|
|
1780
|
-
this.__store.setState((s) => ({
|
|
1781
|
-
...s,
|
|
1782
|
-
isLoading: false,
|
|
1783
|
-
matches: s.pendingMatches!,
|
|
1784
|
-
pendingMatches: undefined,
|
|
1785
|
-
cachedMatches: [
|
|
1786
|
-
...s.cachedMatches,
|
|
1787
|
-
...exitingMatches.filter((d) => d.status !== 'error'),
|
|
1788
|
-
],
|
|
1789
|
-
statusCode:
|
|
1790
|
-
redirect?.statusCode || notFound
|
|
1791
|
-
? 404
|
|
1792
|
-
: s.matches.some((d) => d.status === 'error')
|
|
1793
|
-
? 500
|
|
1794
|
-
: 200,
|
|
1795
|
-
redirect,
|
|
1796
|
-
}))
|
|
1797
|
-
this.cleanCache()
|
|
1798
|
-
})
|
|
1799
|
-
|
|
1800
|
-
//
|
|
1801
|
-
;(
|
|
1802
|
-
[
|
|
1803
|
-
[exitingMatches, 'onLeave'],
|
|
1804
|
-
[enteringMatches, 'onEnter'],
|
|
1805
|
-
[stayingMatches, 'onStay'],
|
|
1806
|
-
] as const
|
|
1807
|
-
).forEach(([matches, hook]) => {
|
|
1808
|
-
matches.forEach((match) => {
|
|
1809
|
-
this.looseRoutesById[match.routeId]!.options[hook]?.(match)
|
|
1810
|
-
})
|
|
1811
|
-
})
|
|
1812
|
-
|
|
1813
|
-
resolveLoad()
|
|
1814
|
-
|
|
1815
|
-
// return this.viewTransitionPromise
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
// Attempt to start a view transition (or just apply the changes if we can't)
|
|
1819
|
-
;(shouldViewTransition && typeof document !== 'undefined'
|
|
1820
|
-
? document
|
|
1821
|
-
: undefined
|
|
1822
|
-
)
|
|
1823
|
-
// @ts-expect-error
|
|
1824
|
-
?.startViewTransition?.(apply) || apply()
|
|
1825
|
-
} catch (err) {
|
|
1826
|
-
// Only apply the latest transition
|
|
1827
|
-
if ((latestPromise = this.checkLatest(promise))) {
|
|
1828
|
-
return latestPromise
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
console.error('Load Error', err)
|
|
1832
|
-
|
|
1833
|
-
rejectLoad(err)
|
|
1834
|
-
}
|
|
1835
|
-
})
|
|
1836
|
-
|
|
1837
|
-
return this.latestLoadPromise
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
1834
|
resolveRedirect = (err: AnyRedirect): ResolvedRedirect => {
|
|
1841
1835
|
const redirect = err as ResolvedRedirect
|
|
1842
1836
|
|
|
@@ -1942,7 +1936,7 @@ export class Router<
|
|
|
1942
1936
|
} catch (err) {
|
|
1943
1937
|
if (isRedirect(err)) {
|
|
1944
1938
|
return await this.preloadRoute({
|
|
1945
|
-
|
|
1939
|
+
fromSearch: next.search,
|
|
1946
1940
|
from: next.pathname,
|
|
1947
1941
|
...(err as any),
|
|
1948
1942
|
})
|