@tanstack/router-core 1.131.19 → 1.131.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +2 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +2 -1
- package/dist/cjs/load-matches.cjs +667 -0
- package/dist/cjs/load-matches.cjs.map +1 -0
- package/dist/cjs/load-matches.d.cts +16 -0
- package/dist/cjs/router.cjs +8 -694
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +3 -27
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/load-matches.d.ts +16 -0
- package/dist/esm/load-matches.js +667 -0
- package/dist/esm/load-matches.js.map +1 -0
- package/dist/esm/router.d.ts +3 -27
- package/dist/esm/router.js +8 -694
- package/dist/esm/router.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -1
- package/src/load-matches.ts +972 -0
- package/src/router.ts +7 -1004
package/src/router.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
createControlledPromise,
|
|
10
10
|
deepEqual,
|
|
11
11
|
functionalUpdate,
|
|
12
|
-
isPromise,
|
|
13
12
|
last,
|
|
14
13
|
pick,
|
|
15
14
|
replaceEqualDeep,
|
|
@@ -35,6 +34,7 @@ import { defaultParseSearch, defaultStringifySearch } from './searchParams'
|
|
|
35
34
|
import { rootRouteId } from './root'
|
|
36
35
|
import { isRedirect, redirect } from './redirect'
|
|
37
36
|
import { createLRUCache } from './lru-cache'
|
|
37
|
+
import { loadMatches, loadRouteChunk, routeNeedsPreload } from './load-matches'
|
|
38
38
|
import type { ParsePathnameCache, Segment } from './path'
|
|
39
39
|
import type { SearchParser, SearchSerializer } from './searchParams'
|
|
40
40
|
import type { AnyRedirect, ResolvedRedirect } from './redirect'
|
|
@@ -57,13 +57,10 @@ import type {
|
|
|
57
57
|
AnyContext,
|
|
58
58
|
AnyRoute,
|
|
59
59
|
AnyRouteWithContext,
|
|
60
|
-
BeforeLoadContextOptions,
|
|
61
|
-
LoaderFnContext,
|
|
62
60
|
MakeRemountDepsOptionsUnion,
|
|
63
61
|
RouteContextOptions,
|
|
64
62
|
RouteMask,
|
|
65
63
|
SearchMiddleware,
|
|
66
|
-
SsrContextOptions,
|
|
67
64
|
} from './route'
|
|
68
65
|
import type {
|
|
69
66
|
FullSearchSchema,
|
|
@@ -763,18 +760,6 @@ export type CreateRouterFn = <
|
|
|
763
760
|
TDehydrated
|
|
764
761
|
>
|
|
765
762
|
|
|
766
|
-
type InnerLoadContext = {
|
|
767
|
-
location: ParsedLocation
|
|
768
|
-
firstBadMatchIndex?: number
|
|
769
|
-
rendered?: boolean
|
|
770
|
-
updateMatch: UpdateMatchFn
|
|
771
|
-
matches: Array<AnyRouteMatch>
|
|
772
|
-
preload?: boolean
|
|
773
|
-
onReady?: () => Promise<void>
|
|
774
|
-
sync?: boolean
|
|
775
|
-
matchPromises: Array<Promise<AnyRouteMatch>>
|
|
776
|
-
}
|
|
777
|
-
|
|
778
763
|
export class RouterCore<
|
|
779
764
|
in out TRouteTree extends AnyRoute,
|
|
780
765
|
in out TTrailingSlashOption extends TrailingSlashOption,
|
|
@@ -1902,10 +1887,12 @@ export class RouterCore<
|
|
|
1902
1887
|
}),
|
|
1903
1888
|
})
|
|
1904
1889
|
|
|
1905
|
-
await
|
|
1890
|
+
await loadMatches({
|
|
1891
|
+
router: this,
|
|
1906
1892
|
sync: opts?.sync,
|
|
1907
1893
|
matches: this.state.pendingMatches as Array<AnyRouteMatch>,
|
|
1908
1894
|
location: next,
|
|
1895
|
+
updateMatch: this.updateMatch,
|
|
1909
1896
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1910
1897
|
onReady: async () => {
|
|
1911
1898
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
@@ -2095,873 +2082,6 @@ export class RouterCore<
|
|
|
2095
2082
|
)
|
|
2096
2083
|
}
|
|
2097
2084
|
|
|
2098
|
-
private triggerOnReady = (
|
|
2099
|
-
innerLoadContext: InnerLoadContext,
|
|
2100
|
-
): void | Promise<void> => {
|
|
2101
|
-
if (!innerLoadContext.rendered) {
|
|
2102
|
-
innerLoadContext.rendered = true
|
|
2103
|
-
return innerLoadContext.onReady?.()
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
|
-
private resolvePreload = (
|
|
2108
|
-
innerLoadContext: InnerLoadContext,
|
|
2109
|
-
matchId: string,
|
|
2110
|
-
): boolean => {
|
|
2111
|
-
return !!(
|
|
2112
|
-
innerLoadContext.preload &&
|
|
2113
|
-
!this.state.matches.some((d) => d.id === matchId)
|
|
2114
|
-
)
|
|
2115
|
-
}
|
|
2116
|
-
|
|
2117
|
-
private handleRedirectAndNotFound = (
|
|
2118
|
-
innerLoadContext: InnerLoadContext,
|
|
2119
|
-
match: AnyRouteMatch | undefined,
|
|
2120
|
-
err: unknown,
|
|
2121
|
-
): void => {
|
|
2122
|
-
if (!isRedirect(err) && !isNotFound(err)) return
|
|
2123
|
-
|
|
2124
|
-
if (isRedirect(err) && err.redirectHandled && !err.options.reloadDocument) {
|
|
2125
|
-
throw err
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
|
-
// in case of a redirecting match during preload, the match does not exist
|
|
2129
|
-
if (match) {
|
|
2130
|
-
match._nonReactive.beforeLoadPromise?.resolve()
|
|
2131
|
-
match._nonReactive.loaderPromise?.resolve()
|
|
2132
|
-
match._nonReactive.beforeLoadPromise = undefined
|
|
2133
|
-
match._nonReactive.loaderPromise = undefined
|
|
2134
|
-
|
|
2135
|
-
const status = isRedirect(err) ? 'redirected' : 'notFound'
|
|
2136
|
-
|
|
2137
|
-
innerLoadContext.updateMatch(match.id, (prev) => ({
|
|
2138
|
-
...prev,
|
|
2139
|
-
status,
|
|
2140
|
-
isFetching: false,
|
|
2141
|
-
error: err,
|
|
2142
|
-
}))
|
|
2143
|
-
|
|
2144
|
-
if (isNotFound(err) && !err.routeId) {
|
|
2145
|
-
err.routeId = match.routeId
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
match._nonReactive.loadPromise?.resolve()
|
|
2149
|
-
}
|
|
2150
|
-
|
|
2151
|
-
if (isRedirect(err)) {
|
|
2152
|
-
innerLoadContext.rendered = true
|
|
2153
|
-
err.options._fromLocation = innerLoadContext.location
|
|
2154
|
-
err.redirectHandled = true
|
|
2155
|
-
err = this.resolveRedirect(err)
|
|
2156
|
-
throw err
|
|
2157
|
-
} else {
|
|
2158
|
-
this._handleNotFound(innerLoadContext, err)
|
|
2159
|
-
throw err
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
|
|
2163
|
-
private shouldSkipLoader = (matchId: string): boolean => {
|
|
2164
|
-
const match = this.getMatch(matchId)!
|
|
2165
|
-
// upon hydration, we skip the loader if the match has been dehydrated on the server
|
|
2166
|
-
if (!this.isServer && match._nonReactive.dehydrated) {
|
|
2167
|
-
return true
|
|
2168
|
-
}
|
|
2169
|
-
|
|
2170
|
-
if (this.isServer) {
|
|
2171
|
-
if (match.ssr === false) {
|
|
2172
|
-
return true
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
return false
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
private handleSerialError = (
|
|
2179
|
-
innerLoadContext: InnerLoadContext,
|
|
2180
|
-
index: number,
|
|
2181
|
-
err: any,
|
|
2182
|
-
routerCode: string,
|
|
2183
|
-
): void => {
|
|
2184
|
-
const { id: matchId, routeId } = innerLoadContext.matches[index]!
|
|
2185
|
-
const route = this.looseRoutesById[routeId]!
|
|
2186
|
-
|
|
2187
|
-
// Much like suspense, we use a promise here to know if
|
|
2188
|
-
// we've been outdated by a new loadMatches call and
|
|
2189
|
-
// should abort the current async operation
|
|
2190
|
-
if (err instanceof Promise) {
|
|
2191
|
-
throw err
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
err.routerCode = routerCode
|
|
2195
|
-
innerLoadContext.firstBadMatchIndex ??= index
|
|
2196
|
-
this.handleRedirectAndNotFound(
|
|
2197
|
-
innerLoadContext,
|
|
2198
|
-
this.getMatch(matchId),
|
|
2199
|
-
err,
|
|
2200
|
-
)
|
|
2201
|
-
|
|
2202
|
-
try {
|
|
2203
|
-
route.options.onError?.(err)
|
|
2204
|
-
} catch (errorHandlerErr) {
|
|
2205
|
-
err = errorHandlerErr
|
|
2206
|
-
this.handleRedirectAndNotFound(
|
|
2207
|
-
innerLoadContext,
|
|
2208
|
-
this.getMatch(matchId),
|
|
2209
|
-
err,
|
|
2210
|
-
)
|
|
2211
|
-
}
|
|
2212
|
-
|
|
2213
|
-
innerLoadContext.updateMatch(matchId, (prev) => {
|
|
2214
|
-
prev._nonReactive.beforeLoadPromise?.resolve()
|
|
2215
|
-
prev._nonReactive.beforeLoadPromise = undefined
|
|
2216
|
-
prev._nonReactive.loadPromise?.resolve()
|
|
2217
|
-
|
|
2218
|
-
return {
|
|
2219
|
-
...prev,
|
|
2220
|
-
error: err,
|
|
2221
|
-
status: 'error',
|
|
2222
|
-
isFetching: false,
|
|
2223
|
-
updatedAt: Date.now(),
|
|
2224
|
-
abortController: new AbortController(),
|
|
2225
|
-
}
|
|
2226
|
-
})
|
|
2227
|
-
}
|
|
2228
|
-
|
|
2229
|
-
private isBeforeLoadSsr = (
|
|
2230
|
-
innerLoadContext: InnerLoadContext,
|
|
2231
|
-
matchId: string,
|
|
2232
|
-
index: number,
|
|
2233
|
-
route: AnyRoute,
|
|
2234
|
-
): void | Promise<void> => {
|
|
2235
|
-
const existingMatch = this.getMatch(matchId)!
|
|
2236
|
-
const parentMatchId = innerLoadContext.matches[index - 1]?.id
|
|
2237
|
-
const parentMatch = parentMatchId
|
|
2238
|
-
? this.getMatch(parentMatchId)!
|
|
2239
|
-
: undefined
|
|
2240
|
-
|
|
2241
|
-
// in SPA mode, only SSR the root route
|
|
2242
|
-
if (this.isShell()) {
|
|
2243
|
-
existingMatch.ssr = matchId === rootRouteId
|
|
2244
|
-
return
|
|
2245
|
-
}
|
|
2246
|
-
|
|
2247
|
-
if (parentMatch?.ssr === false) {
|
|
2248
|
-
existingMatch.ssr = false
|
|
2249
|
-
return
|
|
2250
|
-
}
|
|
2251
|
-
|
|
2252
|
-
const parentOverride = (tempSsr: boolean | 'data-only') => {
|
|
2253
|
-
if (tempSsr === true && parentMatch?.ssr === 'data-only') {
|
|
2254
|
-
return 'data-only'
|
|
2255
|
-
}
|
|
2256
|
-
return tempSsr
|
|
2257
|
-
}
|
|
2258
|
-
|
|
2259
|
-
const defaultSsr = this.options.defaultSsr ?? true
|
|
2260
|
-
|
|
2261
|
-
if (route.options.ssr === undefined) {
|
|
2262
|
-
existingMatch.ssr = parentOverride(defaultSsr)
|
|
2263
|
-
return
|
|
2264
|
-
}
|
|
2265
|
-
|
|
2266
|
-
if (typeof route.options.ssr !== 'function') {
|
|
2267
|
-
existingMatch.ssr = parentOverride(route.options.ssr)
|
|
2268
|
-
return
|
|
2269
|
-
}
|
|
2270
|
-
const { search, params } = this.getMatch(matchId)!
|
|
2271
|
-
|
|
2272
|
-
const ssrFnContext: SsrContextOptions<any, any, any> = {
|
|
2273
|
-
search: makeMaybe(search, existingMatch.searchError),
|
|
2274
|
-
params: makeMaybe(params, existingMatch.paramsError),
|
|
2275
|
-
location: innerLoadContext.location,
|
|
2276
|
-
matches: innerLoadContext.matches.map((match) => ({
|
|
2277
|
-
index: match.index,
|
|
2278
|
-
pathname: match.pathname,
|
|
2279
|
-
fullPath: match.fullPath,
|
|
2280
|
-
staticData: match.staticData,
|
|
2281
|
-
id: match.id,
|
|
2282
|
-
routeId: match.routeId,
|
|
2283
|
-
search: makeMaybe(match.search, match.searchError),
|
|
2284
|
-
params: makeMaybe(match.params, match.paramsError),
|
|
2285
|
-
ssr: match.ssr,
|
|
2286
|
-
})),
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
|
-
const tempSsr = route.options.ssr(ssrFnContext)
|
|
2290
|
-
if (isPromise(tempSsr)) {
|
|
2291
|
-
return tempSsr.then((ssr) => {
|
|
2292
|
-
existingMatch.ssr = parentOverride(ssr ?? defaultSsr)
|
|
2293
|
-
})
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
existingMatch.ssr = parentOverride(tempSsr ?? defaultSsr)
|
|
2297
|
-
return
|
|
2298
|
-
}
|
|
2299
|
-
|
|
2300
|
-
private setupPendingTimeout = (
|
|
2301
|
-
innerLoadContext: InnerLoadContext,
|
|
2302
|
-
matchId: string,
|
|
2303
|
-
route: AnyRoute,
|
|
2304
|
-
): void => {
|
|
2305
|
-
const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs
|
|
2306
|
-
const shouldPending = !!(
|
|
2307
|
-
innerLoadContext.onReady &&
|
|
2308
|
-
!this.isServer &&
|
|
2309
|
-
!this.resolvePreload(innerLoadContext, matchId) &&
|
|
2310
|
-
(route.options.loader ||
|
|
2311
|
-
route.options.beforeLoad ||
|
|
2312
|
-
routeNeedsPreload(route)) &&
|
|
2313
|
-
typeof pendingMs === 'number' &&
|
|
2314
|
-
pendingMs !== Infinity &&
|
|
2315
|
-
(route.options.pendingComponent ??
|
|
2316
|
-
(this.options as any)?.defaultPendingComponent)
|
|
2317
|
-
)
|
|
2318
|
-
const match = this.getMatch(matchId)!
|
|
2319
|
-
if (shouldPending && match._nonReactive.pendingTimeout === undefined) {
|
|
2320
|
-
const pendingTimeout = setTimeout(() => {
|
|
2321
|
-
// Update the match and prematurely resolve the loadMatches promise so that
|
|
2322
|
-
// the pending component can start rendering
|
|
2323
|
-
this.triggerOnReady(innerLoadContext)
|
|
2324
|
-
}, pendingMs)
|
|
2325
|
-
match._nonReactive.pendingTimeout = pendingTimeout
|
|
2326
|
-
}
|
|
2327
|
-
}
|
|
2328
|
-
|
|
2329
|
-
private shouldExecuteBeforeLoad = (
|
|
2330
|
-
innerLoadContext: InnerLoadContext,
|
|
2331
|
-
matchId: string,
|
|
2332
|
-
route: AnyRoute,
|
|
2333
|
-
): boolean | Promise<boolean> => {
|
|
2334
|
-
const existingMatch = this.getMatch(matchId)!
|
|
2335
|
-
|
|
2336
|
-
// If we are in the middle of a load, either of these will be present
|
|
2337
|
-
// (not to be confused with `loadPromise`, which is always defined)
|
|
2338
|
-
if (
|
|
2339
|
-
!existingMatch._nonReactive.beforeLoadPromise &&
|
|
2340
|
-
!existingMatch._nonReactive.loaderPromise
|
|
2341
|
-
)
|
|
2342
|
-
return true
|
|
2343
|
-
|
|
2344
|
-
this.setupPendingTimeout(innerLoadContext, matchId, route)
|
|
2345
|
-
|
|
2346
|
-
const then = () => {
|
|
2347
|
-
let shouldExecuteBeforeLoad = true
|
|
2348
|
-
const match = this.getMatch(matchId)!
|
|
2349
|
-
if (match.status === 'error') {
|
|
2350
|
-
shouldExecuteBeforeLoad = true
|
|
2351
|
-
} else if (
|
|
2352
|
-
match.preload &&
|
|
2353
|
-
(match.status === 'redirected' || match.status === 'notFound')
|
|
2354
|
-
) {
|
|
2355
|
-
this.handleRedirectAndNotFound(innerLoadContext, match, match.error)
|
|
2356
|
-
}
|
|
2357
|
-
return shouldExecuteBeforeLoad
|
|
2358
|
-
}
|
|
2359
|
-
|
|
2360
|
-
// Wait for the beforeLoad to resolve before we continue
|
|
2361
|
-
return existingMatch._nonReactive.beforeLoadPromise
|
|
2362
|
-
? existingMatch._nonReactive.beforeLoadPromise.then(then)
|
|
2363
|
-
: then()
|
|
2364
|
-
}
|
|
2365
|
-
|
|
2366
|
-
private executeBeforeLoad = (
|
|
2367
|
-
innerLoadContext: InnerLoadContext,
|
|
2368
|
-
matchId: string,
|
|
2369
|
-
index: number,
|
|
2370
|
-
route: AnyRoute,
|
|
2371
|
-
): void | Promise<void> => {
|
|
2372
|
-
const match = this.getMatch(matchId)!
|
|
2373
|
-
|
|
2374
|
-
match._nonReactive.beforeLoadPromise = createControlledPromise<void>()
|
|
2375
|
-
// explicitly capture the previous loadPromise
|
|
2376
|
-
const prevLoadPromise = match._nonReactive.loadPromise
|
|
2377
|
-
match._nonReactive.loadPromise = createControlledPromise<void>(() => {
|
|
2378
|
-
prevLoadPromise?.resolve()
|
|
2379
|
-
})
|
|
2380
|
-
|
|
2381
|
-
const { paramsError, searchError } = match
|
|
2382
|
-
|
|
2383
|
-
if (paramsError) {
|
|
2384
|
-
this.handleSerialError(
|
|
2385
|
-
innerLoadContext,
|
|
2386
|
-
index,
|
|
2387
|
-
paramsError,
|
|
2388
|
-
'PARSE_PARAMS',
|
|
2389
|
-
)
|
|
2390
|
-
}
|
|
2391
|
-
|
|
2392
|
-
if (searchError) {
|
|
2393
|
-
this.handleSerialError(
|
|
2394
|
-
innerLoadContext,
|
|
2395
|
-
index,
|
|
2396
|
-
searchError,
|
|
2397
|
-
'VALIDATE_SEARCH',
|
|
2398
|
-
)
|
|
2399
|
-
}
|
|
2400
|
-
|
|
2401
|
-
this.setupPendingTimeout(innerLoadContext, matchId, route)
|
|
2402
|
-
|
|
2403
|
-
const abortController = new AbortController()
|
|
2404
|
-
|
|
2405
|
-
const parentMatchId = innerLoadContext.matches[index - 1]?.id
|
|
2406
|
-
const parentMatch = parentMatchId
|
|
2407
|
-
? this.getMatch(parentMatchId)!
|
|
2408
|
-
: undefined
|
|
2409
|
-
const parentMatchContext =
|
|
2410
|
-
parentMatch?.context ?? this.options.context ?? undefined
|
|
2411
|
-
|
|
2412
|
-
const context = { ...parentMatchContext, ...match.__routeContext }
|
|
2413
|
-
|
|
2414
|
-
let isPending = false
|
|
2415
|
-
const pending = () => {
|
|
2416
|
-
if (isPending) return
|
|
2417
|
-
isPending = true
|
|
2418
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2419
|
-
...prev,
|
|
2420
|
-
isFetching: 'beforeLoad',
|
|
2421
|
-
fetchCount: prev.fetchCount + 1,
|
|
2422
|
-
abortController,
|
|
2423
|
-
context,
|
|
2424
|
-
}))
|
|
2425
|
-
}
|
|
2426
|
-
|
|
2427
|
-
const resolve = () => {
|
|
2428
|
-
match._nonReactive.beforeLoadPromise?.resolve()
|
|
2429
|
-
match._nonReactive.beforeLoadPromise = undefined
|
|
2430
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2431
|
-
...prev,
|
|
2432
|
-
isFetching: false,
|
|
2433
|
-
}))
|
|
2434
|
-
}
|
|
2435
|
-
|
|
2436
|
-
// if there is no `beforeLoad` option, skip everything, batch update the store, return early
|
|
2437
|
-
if (!route.options.beforeLoad) {
|
|
2438
|
-
batch(() => {
|
|
2439
|
-
pending()
|
|
2440
|
-
resolve()
|
|
2441
|
-
})
|
|
2442
|
-
return
|
|
2443
|
-
}
|
|
2444
|
-
|
|
2445
|
-
const { search, params, cause } = match
|
|
2446
|
-
const preload = this.resolvePreload(innerLoadContext, matchId)
|
|
2447
|
-
const beforeLoadFnContext: BeforeLoadContextOptions<
|
|
2448
|
-
any,
|
|
2449
|
-
any,
|
|
2450
|
-
any,
|
|
2451
|
-
any,
|
|
2452
|
-
any
|
|
2453
|
-
> = {
|
|
2454
|
-
search,
|
|
2455
|
-
abortController,
|
|
2456
|
-
params,
|
|
2457
|
-
preload,
|
|
2458
|
-
context,
|
|
2459
|
-
location: innerLoadContext.location,
|
|
2460
|
-
navigate: (opts: any) =>
|
|
2461
|
-
this.navigate({ ...opts, _fromLocation: innerLoadContext.location }),
|
|
2462
|
-
buildLocation: this.buildLocation,
|
|
2463
|
-
cause: preload ? 'preload' : cause,
|
|
2464
|
-
matches: innerLoadContext.matches,
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
const updateContext = (beforeLoadContext: any) => {
|
|
2468
|
-
if (beforeLoadContext === undefined) {
|
|
2469
|
-
batch(() => {
|
|
2470
|
-
pending()
|
|
2471
|
-
resolve()
|
|
2472
|
-
})
|
|
2473
|
-
return
|
|
2474
|
-
}
|
|
2475
|
-
if (isRedirect(beforeLoadContext) || isNotFound(beforeLoadContext)) {
|
|
2476
|
-
pending()
|
|
2477
|
-
this.handleSerialError(
|
|
2478
|
-
innerLoadContext,
|
|
2479
|
-
index,
|
|
2480
|
-
beforeLoadContext,
|
|
2481
|
-
'BEFORE_LOAD',
|
|
2482
|
-
)
|
|
2483
|
-
}
|
|
2484
|
-
|
|
2485
|
-
batch(() => {
|
|
2486
|
-
pending()
|
|
2487
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2488
|
-
...prev,
|
|
2489
|
-
__beforeLoadContext: beforeLoadContext,
|
|
2490
|
-
context: {
|
|
2491
|
-
...prev.context,
|
|
2492
|
-
...beforeLoadContext,
|
|
2493
|
-
},
|
|
2494
|
-
}))
|
|
2495
|
-
resolve()
|
|
2496
|
-
})
|
|
2497
|
-
}
|
|
2498
|
-
|
|
2499
|
-
let beforeLoadContext
|
|
2500
|
-
try {
|
|
2501
|
-
beforeLoadContext = route.options.beforeLoad(beforeLoadFnContext)
|
|
2502
|
-
if (isPromise(beforeLoadContext)) {
|
|
2503
|
-
pending()
|
|
2504
|
-
return beforeLoadContext
|
|
2505
|
-
.catch((err) => {
|
|
2506
|
-
this.handleSerialError(innerLoadContext, index, err, 'BEFORE_LOAD')
|
|
2507
|
-
})
|
|
2508
|
-
.then(updateContext)
|
|
2509
|
-
}
|
|
2510
|
-
} catch (err) {
|
|
2511
|
-
pending()
|
|
2512
|
-
this.handleSerialError(innerLoadContext, index, err, 'BEFORE_LOAD')
|
|
2513
|
-
}
|
|
2514
|
-
|
|
2515
|
-
updateContext(beforeLoadContext)
|
|
2516
|
-
return
|
|
2517
|
-
}
|
|
2518
|
-
|
|
2519
|
-
private handleBeforeLoad = (
|
|
2520
|
-
innerLoadContext: InnerLoadContext,
|
|
2521
|
-
index: number,
|
|
2522
|
-
): void | Promise<void> => {
|
|
2523
|
-
const { id: matchId, routeId } = innerLoadContext.matches[index]!
|
|
2524
|
-
const route = this.looseRoutesById[routeId]!
|
|
2525
|
-
|
|
2526
|
-
const serverSsr = () => {
|
|
2527
|
-
// on the server, determine whether SSR the current match or not
|
|
2528
|
-
if (this.isServer) {
|
|
2529
|
-
const maybePromise = this.isBeforeLoadSsr(
|
|
2530
|
-
innerLoadContext,
|
|
2531
|
-
matchId,
|
|
2532
|
-
index,
|
|
2533
|
-
route,
|
|
2534
|
-
)
|
|
2535
|
-
if (isPromise(maybePromise)) return maybePromise.then(queueExecution)
|
|
2536
|
-
}
|
|
2537
|
-
return queueExecution()
|
|
2538
|
-
}
|
|
2539
|
-
|
|
2540
|
-
const queueExecution = () => {
|
|
2541
|
-
if (this.shouldSkipLoader(matchId)) return
|
|
2542
|
-
const shouldExecuteBeforeLoadResult = this.shouldExecuteBeforeLoad(
|
|
2543
|
-
innerLoadContext,
|
|
2544
|
-
matchId,
|
|
2545
|
-
route,
|
|
2546
|
-
)
|
|
2547
|
-
return isPromise(shouldExecuteBeforeLoadResult)
|
|
2548
|
-
? shouldExecuteBeforeLoadResult.then(execute)
|
|
2549
|
-
: execute(shouldExecuteBeforeLoadResult)
|
|
2550
|
-
}
|
|
2551
|
-
|
|
2552
|
-
const execute = (shouldExecuteBeforeLoad: boolean) => {
|
|
2553
|
-
if (shouldExecuteBeforeLoad) {
|
|
2554
|
-
// If we are not in the middle of a load OR the previous load failed, start it
|
|
2555
|
-
return this.executeBeforeLoad(innerLoadContext, matchId, index, route)
|
|
2556
|
-
}
|
|
2557
|
-
return
|
|
2558
|
-
}
|
|
2559
|
-
|
|
2560
|
-
return serverSsr()
|
|
2561
|
-
}
|
|
2562
|
-
|
|
2563
|
-
private executeHead = (
|
|
2564
|
-
innerLoadContext: InnerLoadContext,
|
|
2565
|
-
matchId: string,
|
|
2566
|
-
route: AnyRoute,
|
|
2567
|
-
): void | Promise<
|
|
2568
|
-
Pick<
|
|
2569
|
-
AnyRouteMatch,
|
|
2570
|
-
'meta' | 'links' | 'headScripts' | 'headers' | 'scripts' | 'styles'
|
|
2571
|
-
>
|
|
2572
|
-
> => {
|
|
2573
|
-
const match = this.getMatch(matchId)
|
|
2574
|
-
// in case of a redirecting match during preload, the match does not exist
|
|
2575
|
-
if (!match) {
|
|
2576
|
-
return
|
|
2577
|
-
}
|
|
2578
|
-
if (
|
|
2579
|
-
!route.options.head &&
|
|
2580
|
-
!route.options.scripts &&
|
|
2581
|
-
!route.options.headers
|
|
2582
|
-
) {
|
|
2583
|
-
return
|
|
2584
|
-
}
|
|
2585
|
-
const assetContext = {
|
|
2586
|
-
matches: innerLoadContext.matches,
|
|
2587
|
-
match,
|
|
2588
|
-
params: match.params,
|
|
2589
|
-
loaderData: match.loaderData,
|
|
2590
|
-
}
|
|
2591
|
-
|
|
2592
|
-
return Promise.all([
|
|
2593
|
-
route.options.head?.(assetContext),
|
|
2594
|
-
route.options.scripts?.(assetContext),
|
|
2595
|
-
route.options.headers?.(assetContext),
|
|
2596
|
-
]).then(([headFnContent, scripts, headers]) => {
|
|
2597
|
-
const meta = headFnContent?.meta
|
|
2598
|
-
const links = headFnContent?.links
|
|
2599
|
-
const headScripts = headFnContent?.scripts
|
|
2600
|
-
const styles = headFnContent?.styles
|
|
2601
|
-
|
|
2602
|
-
return {
|
|
2603
|
-
meta,
|
|
2604
|
-
links,
|
|
2605
|
-
headScripts,
|
|
2606
|
-
headers,
|
|
2607
|
-
scripts,
|
|
2608
|
-
styles,
|
|
2609
|
-
}
|
|
2610
|
-
})
|
|
2611
|
-
}
|
|
2612
|
-
|
|
2613
|
-
private potentialPendingMinPromise = (
|
|
2614
|
-
matchId: string,
|
|
2615
|
-
): void | ControlledPromise<void> => {
|
|
2616
|
-
const latestMatch = this.getMatch(matchId)!
|
|
2617
|
-
return latestMatch._nonReactive.minPendingPromise
|
|
2618
|
-
}
|
|
2619
|
-
|
|
2620
|
-
private getLoaderContext = (
|
|
2621
|
-
innerLoadContext: InnerLoadContext,
|
|
2622
|
-
matchId: string,
|
|
2623
|
-
index: number,
|
|
2624
|
-
route: AnyRoute,
|
|
2625
|
-
): LoaderFnContext => {
|
|
2626
|
-
const parentMatchPromise = innerLoadContext.matchPromises[index - 1] as any
|
|
2627
|
-
const { params, loaderDeps, abortController, context, cause } =
|
|
2628
|
-
this.getMatch(matchId)!
|
|
2629
|
-
|
|
2630
|
-
const preload = this.resolvePreload(innerLoadContext, matchId)
|
|
2631
|
-
|
|
2632
|
-
return {
|
|
2633
|
-
params,
|
|
2634
|
-
deps: loaderDeps,
|
|
2635
|
-
preload: !!preload,
|
|
2636
|
-
parentMatchPromise,
|
|
2637
|
-
abortController: abortController,
|
|
2638
|
-
context,
|
|
2639
|
-
location: innerLoadContext.location,
|
|
2640
|
-
navigate: (opts) =>
|
|
2641
|
-
this.navigate({ ...opts, _fromLocation: innerLoadContext.location }),
|
|
2642
|
-
cause: preload ? 'preload' : cause,
|
|
2643
|
-
route,
|
|
2644
|
-
}
|
|
2645
|
-
}
|
|
2646
|
-
|
|
2647
|
-
private runLoader = async (
|
|
2648
|
-
innerLoadContext: InnerLoadContext,
|
|
2649
|
-
matchId: string,
|
|
2650
|
-
index: number,
|
|
2651
|
-
route: AnyRoute,
|
|
2652
|
-
): Promise<void> => {
|
|
2653
|
-
try {
|
|
2654
|
-
// If the Matches component rendered
|
|
2655
|
-
// the pending component and needs to show it for
|
|
2656
|
-
// a minimum duration, we''ll wait for it to resolve
|
|
2657
|
-
// before committing to the match and resolving
|
|
2658
|
-
// the loadPromise
|
|
2659
|
-
|
|
2660
|
-
// Actually run the loader and handle the result
|
|
2661
|
-
try {
|
|
2662
|
-
if (!this.isServer || this.getMatch(matchId)!.ssr === true) {
|
|
2663
|
-
this.loadRouteChunk(route)
|
|
2664
|
-
}
|
|
2665
|
-
|
|
2666
|
-
// Kick off the loader!
|
|
2667
|
-
const loaderResult = route.options.loader?.(
|
|
2668
|
-
this.getLoaderContext(innerLoadContext, matchId, index, route),
|
|
2669
|
-
)
|
|
2670
|
-
const loaderResultIsPromise =
|
|
2671
|
-
route.options.loader && isPromise(loaderResult)
|
|
2672
|
-
|
|
2673
|
-
const willLoadSomething = !!(
|
|
2674
|
-
loaderResultIsPromise ||
|
|
2675
|
-
route._lazyPromise ||
|
|
2676
|
-
route._componentsPromise ||
|
|
2677
|
-
route.options.head ||
|
|
2678
|
-
route.options.scripts ||
|
|
2679
|
-
route.options.headers ||
|
|
2680
|
-
this.getMatch(matchId)!._nonReactive.minPendingPromise
|
|
2681
|
-
)
|
|
2682
|
-
|
|
2683
|
-
if (willLoadSomething) {
|
|
2684
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2685
|
-
...prev,
|
|
2686
|
-
isFetching: 'loader',
|
|
2687
|
-
}))
|
|
2688
|
-
}
|
|
2689
|
-
|
|
2690
|
-
if (route.options.loader) {
|
|
2691
|
-
const loaderData = loaderResultIsPromise
|
|
2692
|
-
? await loaderResult
|
|
2693
|
-
: loaderResult
|
|
2694
|
-
|
|
2695
|
-
this.handleRedirectAndNotFound(
|
|
2696
|
-
innerLoadContext,
|
|
2697
|
-
this.getMatch(matchId),
|
|
2698
|
-
loaderData,
|
|
2699
|
-
)
|
|
2700
|
-
if (loaderData !== undefined) {
|
|
2701
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2702
|
-
...prev,
|
|
2703
|
-
loaderData,
|
|
2704
|
-
}))
|
|
2705
|
-
}
|
|
2706
|
-
}
|
|
2707
|
-
|
|
2708
|
-
// Lazy option can modify the route options,
|
|
2709
|
-
// so we need to wait for it to resolve before
|
|
2710
|
-
// we can use the options
|
|
2711
|
-
if (route._lazyPromise) await route._lazyPromise
|
|
2712
|
-
const headResult = this.executeHead(innerLoadContext, matchId, route)
|
|
2713
|
-
const head = headResult ? await headResult : undefined
|
|
2714
|
-
const pendingPromise = this.potentialPendingMinPromise(matchId)
|
|
2715
|
-
if (pendingPromise) await pendingPromise
|
|
2716
|
-
|
|
2717
|
-
// Last but not least, wait for the the components
|
|
2718
|
-
// to be preloaded before we resolve the match
|
|
2719
|
-
if (route._componentsPromise) await route._componentsPromise
|
|
2720
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2721
|
-
...prev,
|
|
2722
|
-
error: undefined,
|
|
2723
|
-
status: 'success',
|
|
2724
|
-
isFetching: false,
|
|
2725
|
-
updatedAt: Date.now(),
|
|
2726
|
-
...head,
|
|
2727
|
-
}))
|
|
2728
|
-
} catch (e) {
|
|
2729
|
-
let error = e
|
|
2730
|
-
|
|
2731
|
-
const pendingPromise = this.potentialPendingMinPromise(matchId)
|
|
2732
|
-
if (pendingPromise) await pendingPromise
|
|
2733
|
-
|
|
2734
|
-
this.handleRedirectAndNotFound(
|
|
2735
|
-
innerLoadContext,
|
|
2736
|
-
this.getMatch(matchId),
|
|
2737
|
-
e,
|
|
2738
|
-
)
|
|
2739
|
-
|
|
2740
|
-
try {
|
|
2741
|
-
route.options.onError?.(e)
|
|
2742
|
-
} catch (onErrorError) {
|
|
2743
|
-
error = onErrorError
|
|
2744
|
-
this.handleRedirectAndNotFound(
|
|
2745
|
-
innerLoadContext,
|
|
2746
|
-
this.getMatch(matchId),
|
|
2747
|
-
onErrorError,
|
|
2748
|
-
)
|
|
2749
|
-
}
|
|
2750
|
-
const headResult = this.executeHead(innerLoadContext, matchId, route)
|
|
2751
|
-
const head = headResult ? await headResult : undefined
|
|
2752
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2753
|
-
...prev,
|
|
2754
|
-
error,
|
|
2755
|
-
status: 'error',
|
|
2756
|
-
isFetching: false,
|
|
2757
|
-
...head,
|
|
2758
|
-
}))
|
|
2759
|
-
}
|
|
2760
|
-
} catch (err) {
|
|
2761
|
-
const match = this.getMatch(matchId)
|
|
2762
|
-
// in case of a redirecting match during preload, the match does not exist
|
|
2763
|
-
if (match) {
|
|
2764
|
-
const headResult = this.executeHead(innerLoadContext, matchId, route)
|
|
2765
|
-
if (headResult) {
|
|
2766
|
-
const head = await headResult
|
|
2767
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2768
|
-
...prev,
|
|
2769
|
-
...head,
|
|
2770
|
-
}))
|
|
2771
|
-
}
|
|
2772
|
-
match._nonReactive.loaderPromise = undefined
|
|
2773
|
-
}
|
|
2774
|
-
this.handleRedirectAndNotFound(innerLoadContext, match, err)
|
|
2775
|
-
}
|
|
2776
|
-
}
|
|
2777
|
-
|
|
2778
|
-
private loadRouteMatch = async (
|
|
2779
|
-
innerLoadContext: InnerLoadContext,
|
|
2780
|
-
index: number,
|
|
2781
|
-
): Promise<AnyRouteMatch> => {
|
|
2782
|
-
const { id: matchId, routeId } = innerLoadContext.matches[index]!
|
|
2783
|
-
let loaderShouldRunAsync = false
|
|
2784
|
-
let loaderIsRunningAsync = false
|
|
2785
|
-
const route = this.looseRoutesById[routeId]!
|
|
2786
|
-
|
|
2787
|
-
const prevMatch = this.getMatch(matchId)!
|
|
2788
|
-
if (this.shouldSkipLoader(matchId)) {
|
|
2789
|
-
if (this.isServer) {
|
|
2790
|
-
const headResult = this.executeHead(innerLoadContext, matchId, route)
|
|
2791
|
-
if (headResult) {
|
|
2792
|
-
const head = await headResult
|
|
2793
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2794
|
-
...prev,
|
|
2795
|
-
...head,
|
|
2796
|
-
}))
|
|
2797
|
-
}
|
|
2798
|
-
return this.getMatch(matchId)!
|
|
2799
|
-
}
|
|
2800
|
-
}
|
|
2801
|
-
// there is a loaderPromise, so we are in the middle of a load
|
|
2802
|
-
else if (prevMatch._nonReactive.loaderPromise) {
|
|
2803
|
-
// do not block if we already have stale data we can show
|
|
2804
|
-
// but only if the ongoing load is not a preload since error handling is different for preloads
|
|
2805
|
-
// and we don't want to swallow errors
|
|
2806
|
-
if (
|
|
2807
|
-
prevMatch.status === 'success' &&
|
|
2808
|
-
!innerLoadContext.sync &&
|
|
2809
|
-
!prevMatch.preload
|
|
2810
|
-
) {
|
|
2811
|
-
return this.getMatch(matchId)!
|
|
2812
|
-
}
|
|
2813
|
-
await prevMatch._nonReactive.loaderPromise
|
|
2814
|
-
const match = this.getMatch(matchId)!
|
|
2815
|
-
if (match.error) {
|
|
2816
|
-
this.handleRedirectAndNotFound(innerLoadContext, match, match.error)
|
|
2817
|
-
}
|
|
2818
|
-
} else {
|
|
2819
|
-
// This is where all of the stale-while-revalidate magic happens
|
|
2820
|
-
const age = Date.now() - this.getMatch(matchId)!.updatedAt
|
|
2821
|
-
|
|
2822
|
-
const preload = this.resolvePreload(innerLoadContext, matchId)
|
|
2823
|
-
|
|
2824
|
-
const staleAge = preload
|
|
2825
|
-
? (route.options.preloadStaleTime ??
|
|
2826
|
-
this.options.defaultPreloadStaleTime ??
|
|
2827
|
-
30_000) // 30 seconds for preloads by default
|
|
2828
|
-
: (route.options.staleTime ?? this.options.defaultStaleTime ?? 0)
|
|
2829
|
-
|
|
2830
|
-
const shouldReloadOption = route.options.shouldReload
|
|
2831
|
-
|
|
2832
|
-
// Default to reloading the route all the time
|
|
2833
|
-
// Allow shouldReload to get the last say,
|
|
2834
|
-
// if provided.
|
|
2835
|
-
const shouldReload =
|
|
2836
|
-
typeof shouldReloadOption === 'function'
|
|
2837
|
-
? shouldReloadOption(
|
|
2838
|
-
this.getLoaderContext(innerLoadContext, matchId, index, route),
|
|
2839
|
-
)
|
|
2840
|
-
: shouldReloadOption
|
|
2841
|
-
|
|
2842
|
-
const nextPreload =
|
|
2843
|
-
!!preload && !this.state.matches.some((d) => d.id === matchId)
|
|
2844
|
-
const match = this.getMatch(matchId)!
|
|
2845
|
-
match._nonReactive.loaderPromise = createControlledPromise<void>()
|
|
2846
|
-
if (nextPreload !== match.preload) {
|
|
2847
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2848
|
-
...prev,
|
|
2849
|
-
preload: nextPreload,
|
|
2850
|
-
}))
|
|
2851
|
-
}
|
|
2852
|
-
|
|
2853
|
-
// If the route is successful and still fresh, just resolve
|
|
2854
|
-
const { status, invalid } = this.getMatch(matchId)!
|
|
2855
|
-
loaderShouldRunAsync =
|
|
2856
|
-
status === 'success' && (invalid || (shouldReload ?? age > staleAge))
|
|
2857
|
-
if (preload && route.options.preload === false) {
|
|
2858
|
-
// Do nothing
|
|
2859
|
-
} else if (loaderShouldRunAsync && !innerLoadContext.sync) {
|
|
2860
|
-
loaderIsRunningAsync = true
|
|
2861
|
-
;(async () => {
|
|
2862
|
-
try {
|
|
2863
|
-
await this.runLoader(innerLoadContext, matchId, index, route)
|
|
2864
|
-
const match = this.getMatch(matchId)!
|
|
2865
|
-
match._nonReactive.loaderPromise?.resolve()
|
|
2866
|
-
match._nonReactive.loadPromise?.resolve()
|
|
2867
|
-
match._nonReactive.loaderPromise = undefined
|
|
2868
|
-
} catch (err) {
|
|
2869
|
-
if (isRedirect(err)) {
|
|
2870
|
-
await this.navigate(err.options)
|
|
2871
|
-
}
|
|
2872
|
-
}
|
|
2873
|
-
})()
|
|
2874
|
-
} else if (
|
|
2875
|
-
status !== 'success' ||
|
|
2876
|
-
(loaderShouldRunAsync && innerLoadContext.sync)
|
|
2877
|
-
) {
|
|
2878
|
-
await this.runLoader(innerLoadContext, matchId, index, route)
|
|
2879
|
-
} else {
|
|
2880
|
-
// if the loader did not run, still update head.
|
|
2881
|
-
// reason: parent's beforeLoad may have changed the route context
|
|
2882
|
-
// and only now do we know the route context (and that the loader would not run)
|
|
2883
|
-
const headResult = this.executeHead(innerLoadContext, matchId, route)
|
|
2884
|
-
if (headResult) {
|
|
2885
|
-
const head = await headResult
|
|
2886
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2887
|
-
...prev,
|
|
2888
|
-
...head,
|
|
2889
|
-
}))
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
|
-
}
|
|
2893
|
-
const match = this.getMatch(matchId)!
|
|
2894
|
-
if (!loaderIsRunningAsync) {
|
|
2895
|
-
match._nonReactive.loaderPromise?.resolve()
|
|
2896
|
-
match._nonReactive.loadPromise?.resolve()
|
|
2897
|
-
}
|
|
2898
|
-
|
|
2899
|
-
clearTimeout(match._nonReactive.pendingTimeout)
|
|
2900
|
-
match._nonReactive.pendingTimeout = undefined
|
|
2901
|
-
if (!loaderIsRunningAsync) match._nonReactive.loaderPromise = undefined
|
|
2902
|
-
match._nonReactive.dehydrated = undefined
|
|
2903
|
-
const nextIsFetching = loaderIsRunningAsync ? match.isFetching : false
|
|
2904
|
-
if (nextIsFetching !== match.isFetching || match.invalid !== false) {
|
|
2905
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2906
|
-
...prev,
|
|
2907
|
-
isFetching: nextIsFetching,
|
|
2908
|
-
invalid: false,
|
|
2909
|
-
}))
|
|
2910
|
-
}
|
|
2911
|
-
return this.getMatch(matchId)!
|
|
2912
|
-
}
|
|
2913
|
-
|
|
2914
|
-
loadMatches = async (baseContext: {
|
|
2915
|
-
location: ParsedLocation
|
|
2916
|
-
matches: Array<AnyRouteMatch>
|
|
2917
|
-
preload?: boolean
|
|
2918
|
-
onReady?: () => Promise<void>
|
|
2919
|
-
updateMatch?: UpdateMatchFn
|
|
2920
|
-
sync?: boolean
|
|
2921
|
-
}): Promise<Array<MakeRouteMatch>> => {
|
|
2922
|
-
const innerLoadContext = baseContext as InnerLoadContext
|
|
2923
|
-
innerLoadContext.updateMatch ??= this.updateMatch
|
|
2924
|
-
innerLoadContext.matchPromises = []
|
|
2925
|
-
|
|
2926
|
-
// make sure the pending component is immediately rendered when hydrating a match that is not SSRed
|
|
2927
|
-
// the pending component was already rendered on the server and we want to keep it shown on the client until minPendingMs is reached
|
|
2928
|
-
if (!this.isServer && this.state.matches.some((d) => d._forcePending)) {
|
|
2929
|
-
this.triggerOnReady(innerLoadContext)
|
|
2930
|
-
}
|
|
2931
|
-
|
|
2932
|
-
try {
|
|
2933
|
-
// Execute all beforeLoads one by one
|
|
2934
|
-
for (let i = 0; i < innerLoadContext.matches.length; i++) {
|
|
2935
|
-
const beforeLoad = this.handleBeforeLoad(innerLoadContext, i)
|
|
2936
|
-
if (isPromise(beforeLoad)) await beforeLoad
|
|
2937
|
-
}
|
|
2938
|
-
|
|
2939
|
-
// Execute all loaders in parallel
|
|
2940
|
-
const max =
|
|
2941
|
-
innerLoadContext.firstBadMatchIndex ?? innerLoadContext.matches.length
|
|
2942
|
-
for (let i = 0; i < max; i++) {
|
|
2943
|
-
innerLoadContext.matchPromises.push(
|
|
2944
|
-
this.loadRouteMatch(innerLoadContext, i),
|
|
2945
|
-
)
|
|
2946
|
-
}
|
|
2947
|
-
await Promise.all(innerLoadContext.matchPromises)
|
|
2948
|
-
|
|
2949
|
-
const readyPromise = this.triggerOnReady(innerLoadContext)
|
|
2950
|
-
if (isPromise(readyPromise)) await readyPromise
|
|
2951
|
-
} catch (err) {
|
|
2952
|
-
if (isNotFound(err) && !innerLoadContext.preload) {
|
|
2953
|
-
const readyPromise = this.triggerOnReady(innerLoadContext)
|
|
2954
|
-
if (isPromise(readyPromise)) await readyPromise
|
|
2955
|
-
throw err
|
|
2956
|
-
}
|
|
2957
|
-
if (isRedirect(err)) {
|
|
2958
|
-
throw err
|
|
2959
|
-
}
|
|
2960
|
-
}
|
|
2961
|
-
|
|
2962
|
-
return innerLoadContext.matches
|
|
2963
|
-
}
|
|
2964
|
-
|
|
2965
2085
|
invalidate: InvalidateFn<
|
|
2966
2086
|
RouterCore<
|
|
2967
2087
|
TRouteTree,
|
|
@@ -3055,47 +2175,7 @@ export class RouterCore<
|
|
|
3055
2175
|
this.clearCache({ filter })
|
|
3056
2176
|
}
|
|
3057
2177
|
|
|
3058
|
-
loadRouteChunk =
|
|
3059
|
-
if (!route._lazyLoaded && route._lazyPromise === undefined) {
|
|
3060
|
-
if (route.lazyFn) {
|
|
3061
|
-
route._lazyPromise = route.lazyFn().then((lazyRoute) => {
|
|
3062
|
-
// explicitly don't copy over the lazy route's id
|
|
3063
|
-
const { id: _id, ...options } = lazyRoute.options
|
|
3064
|
-
Object.assign(route.options, options)
|
|
3065
|
-
route._lazyLoaded = true
|
|
3066
|
-
route._lazyPromise = undefined // gc promise, we won't need it anymore
|
|
3067
|
-
})
|
|
3068
|
-
} else {
|
|
3069
|
-
route._lazyLoaded = true
|
|
3070
|
-
}
|
|
3071
|
-
}
|
|
3072
|
-
|
|
3073
|
-
// If for some reason lazy resolves more lazy components...
|
|
3074
|
-
// We'll wait for that before we attempt to preload the
|
|
3075
|
-
// components themselves.
|
|
3076
|
-
if (!route._componentsLoaded && route._componentsPromise === undefined) {
|
|
3077
|
-
const loadComponents = () => {
|
|
3078
|
-
const preloads = []
|
|
3079
|
-
for (const type of componentTypes) {
|
|
3080
|
-
const preload = (route.options[type] as any)?.preload
|
|
3081
|
-
if (preload) preloads.push(preload())
|
|
3082
|
-
}
|
|
3083
|
-
if (preloads.length)
|
|
3084
|
-
return Promise.all(preloads).then(() => {
|
|
3085
|
-
route._componentsLoaded = true
|
|
3086
|
-
route._componentsPromise = undefined // gc promise, we won't need it anymore
|
|
3087
|
-
})
|
|
3088
|
-
route._componentsLoaded = true
|
|
3089
|
-
route._componentsPromise = undefined // gc promise, we won't need it anymore
|
|
3090
|
-
return
|
|
3091
|
-
}
|
|
3092
|
-
route._componentsPromise = route._lazyPromise
|
|
3093
|
-
? route._lazyPromise.then(loadComponents)
|
|
3094
|
-
: loadComponents()
|
|
3095
|
-
}
|
|
3096
|
-
|
|
3097
|
-
return route._componentsPromise
|
|
3098
|
-
}
|
|
2178
|
+
loadRouteChunk = loadRouteChunk
|
|
3099
2179
|
|
|
3100
2180
|
preloadRoute: PreloadRouteFn<
|
|
3101
2181
|
TRouteTree,
|
|
@@ -3135,7 +2215,8 @@ export class RouterCore<
|
|
|
3135
2215
|
})
|
|
3136
2216
|
|
|
3137
2217
|
try {
|
|
3138
|
-
matches = await
|
|
2218
|
+
matches = await loadMatches({
|
|
2219
|
+
router: this,
|
|
3139
2220
|
matches,
|
|
3140
2221
|
location: next,
|
|
3141
2222
|
preload: true,
|
|
@@ -3233,58 +2314,6 @@ export class RouterCore<
|
|
|
3233
2314
|
|
|
3234
2315
|
serverSsr?: ServerSsr
|
|
3235
2316
|
|
|
3236
|
-
private _handleNotFound = (
|
|
3237
|
-
innerLoadContext: InnerLoadContext,
|
|
3238
|
-
err: NotFoundError,
|
|
3239
|
-
) => {
|
|
3240
|
-
// Find the route that should handle the not found error
|
|
3241
|
-
// First check if a specific route is requested to show the error
|
|
3242
|
-
const routeCursor = this.routesById[err.routeId ?? ''] ?? this.routeTree
|
|
3243
|
-
const matchesByRouteId: Record<string, AnyRouteMatch> = {}
|
|
3244
|
-
|
|
3245
|
-
// Setup routesByRouteId object for quick access
|
|
3246
|
-
for (const match of innerLoadContext.matches) {
|
|
3247
|
-
matchesByRouteId[match.routeId] = match
|
|
3248
|
-
}
|
|
3249
|
-
|
|
3250
|
-
// Ensure a NotFoundComponent exists on the route
|
|
3251
|
-
if (
|
|
3252
|
-
!routeCursor.options.notFoundComponent &&
|
|
3253
|
-
(this.options as any)?.defaultNotFoundComponent
|
|
3254
|
-
) {
|
|
3255
|
-
routeCursor.options.notFoundComponent = (
|
|
3256
|
-
this.options as any
|
|
3257
|
-
).defaultNotFoundComponent
|
|
3258
|
-
}
|
|
3259
|
-
|
|
3260
|
-
// Ensure we have a notFoundComponent
|
|
3261
|
-
invariant(
|
|
3262
|
-
routeCursor.options.notFoundComponent,
|
|
3263
|
-
'No notFoundComponent found. Please set a notFoundComponent on your route or provide a defaultNotFoundComponent to the router.',
|
|
3264
|
-
)
|
|
3265
|
-
|
|
3266
|
-
// Find the match for this route
|
|
3267
|
-
const matchForRoute = matchesByRouteId[routeCursor.id]
|
|
3268
|
-
|
|
3269
|
-
invariant(
|
|
3270
|
-
matchForRoute,
|
|
3271
|
-
'Could not find match for route: ' + routeCursor.id,
|
|
3272
|
-
)
|
|
3273
|
-
|
|
3274
|
-
// Assign the error to the match - using non-null assertion since we've checked with invariant
|
|
3275
|
-
innerLoadContext.updateMatch(matchForRoute.id, (prev) => ({
|
|
3276
|
-
...prev,
|
|
3277
|
-
status: 'notFound',
|
|
3278
|
-
error: err,
|
|
3279
|
-
isFetching: false,
|
|
3280
|
-
}))
|
|
3281
|
-
|
|
3282
|
-
if ((err as any).routerCode === 'BEFORE_LOAD' && routeCursor.parentRoute) {
|
|
3283
|
-
err.routeId = routeCursor.parentRoute.id
|
|
3284
|
-
this._handleNotFound(innerLoadContext, err)
|
|
3285
|
-
}
|
|
3286
|
-
}
|
|
3287
|
-
|
|
3288
2317
|
hasNotFoundMatch = () => {
|
|
3289
2318
|
return this.__store.state.matches.some(
|
|
3290
2319
|
(d) => d.status === 'notFound' || d.globalNotFound,
|
|
@@ -3296,16 +2325,6 @@ export class SearchParamError extends Error {}
|
|
|
3296
2325
|
|
|
3297
2326
|
export class PathParamError extends Error {}
|
|
3298
2327
|
|
|
3299
|
-
function makeMaybe<TValue, TError>(
|
|
3300
|
-
value: TValue,
|
|
3301
|
-
error: TError,
|
|
3302
|
-
): { status: 'success'; value: TValue } | { status: 'error'; error: TError } {
|
|
3303
|
-
if (error) {
|
|
3304
|
-
return { status: 'error' as const, error }
|
|
3305
|
-
}
|
|
3306
|
-
return { status: 'success' as const, value }
|
|
3307
|
-
}
|
|
3308
|
-
|
|
3309
2328
|
const normalize = (str: string) =>
|
|
3310
2329
|
str.endsWith('/') && str.length > 1 ? str.slice(0, -1) : str
|
|
3311
2330
|
function comparePaths(a: string, b: string) {
|
|
@@ -3372,22 +2391,6 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
|
|
|
3372
2391
|
return {}
|
|
3373
2392
|
}
|
|
3374
2393
|
|
|
3375
|
-
export const componentTypes = [
|
|
3376
|
-
'component',
|
|
3377
|
-
'errorComponent',
|
|
3378
|
-
'pendingComponent',
|
|
3379
|
-
'notFoundComponent',
|
|
3380
|
-
] as const
|
|
3381
|
-
|
|
3382
|
-
function routeNeedsPreload(route: AnyRoute) {
|
|
3383
|
-
for (const componentType of componentTypes) {
|
|
3384
|
-
if ((route.options[componentType] as any)?.preload) {
|
|
3385
|
-
return true
|
|
3386
|
-
}
|
|
3387
|
-
}
|
|
3388
|
-
return false
|
|
3389
|
-
}
|
|
3390
|
-
|
|
3391
2394
|
interface RouteLike {
|
|
3392
2395
|
id: string
|
|
3393
2396
|
isRoot?: boolean
|