@tanstack/router-core 1.131.14 → 1.131.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/router.cjs +587 -491
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +18 -5
- package/dist/esm/router.d.ts +18 -5
- package/dist/esm/router.js +587 -491
- package/dist/esm/router.js.map +1 -1
- package/package.json +1 -1
- package/src/router.ts +843 -686
package/src/router.ts
CHANGED
|
@@ -763,6 +763,18 @@ export type CreateRouterFn = <
|
|
|
763
763
|
TDehydrated
|
|
764
764
|
>
|
|
765
765
|
|
|
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
|
+
|
|
766
778
|
export class RouterCore<
|
|
767
779
|
in out TRouteTree extends AnyRoute,
|
|
768
780
|
in out TTrailingSlashOption extends TrailingSlashOption,
|
|
@@ -2083,719 +2095,856 @@ export class RouterCore<
|
|
|
2083
2095
|
)
|
|
2084
2096
|
}
|
|
2085
2097
|
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
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
|
+
}))
|
|
2107
2143
|
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
rendered = true
|
|
2111
|
-
await onReady?.()
|
|
2144
|
+
if (isNotFound(err) && !err.routeId) {
|
|
2145
|
+
err.routeId = match.routeId
|
|
2112
2146
|
}
|
|
2147
|
+
|
|
2148
|
+
match._nonReactive.loadPromise?.resolve()
|
|
2113
2149
|
}
|
|
2114
2150
|
|
|
2115
|
-
|
|
2116
|
-
|
|
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
|
|
2117
2160
|
}
|
|
2161
|
+
}
|
|
2118
2162
|
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
if
|
|
2122
|
-
|
|
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
|
|
2123
2168
|
}
|
|
2124
2169
|
|
|
2125
|
-
|
|
2126
|
-
match
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2170
|
+
if (this.isServer) {
|
|
2171
|
+
if (match.ssr === false) {
|
|
2172
|
+
return true
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
return false
|
|
2176
|
+
}
|
|
2130
2177
|
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
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')
|
|
2135
2354
|
) {
|
|
2136
|
-
|
|
2355
|
+
this.handleRedirectAndNotFound(innerLoadContext, match, match.error)
|
|
2137
2356
|
}
|
|
2357
|
+
return shouldExecuteBeforeLoad
|
|
2358
|
+
}
|
|
2138
2359
|
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
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
|
+
}
|
|
2145
2444
|
|
|
2146
|
-
|
|
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
|
+
}
|
|
2147
2466
|
|
|
2148
|
-
|
|
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) => ({
|
|
2149
2488
|
...prev,
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2489
|
+
__beforeLoadContext: beforeLoadContext,
|
|
2490
|
+
context: {
|
|
2491
|
+
...prev.context,
|
|
2492
|
+
...beforeLoadContext,
|
|
2493
|
+
},
|
|
2153
2494
|
}))
|
|
2495
|
+
resolve()
|
|
2496
|
+
})
|
|
2497
|
+
}
|
|
2154
2498
|
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
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)
|
|
2160
2509
|
}
|
|
2510
|
+
} catch (err) {
|
|
2511
|
+
pending()
|
|
2512
|
+
this.handleSerialError(innerLoadContext, index, err, 'BEFORE_LOAD')
|
|
2513
|
+
}
|
|
2161
2514
|
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
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)
|
|
2171
2536
|
}
|
|
2537
|
+
return queueExecution()
|
|
2172
2538
|
}
|
|
2173
2539
|
|
|
2174
|
-
const
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
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)
|
|
2179
2556
|
}
|
|
2557
|
+
return
|
|
2558
|
+
}
|
|
2180
2559
|
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
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,
|
|
2185
2609
|
}
|
|
2186
|
-
|
|
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,
|
|
2187
2644
|
}
|
|
2645
|
+
}
|
|
2188
2646
|
|
|
2647
|
+
private runLoader = async (
|
|
2648
|
+
innerLoadContext: InnerLoadContext,
|
|
2649
|
+
matchId: string,
|
|
2650
|
+
index: number,
|
|
2651
|
+
route: AnyRoute,
|
|
2652
|
+
): Promise<void> => {
|
|
2189
2653
|
try {
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
err: any,
|
|
2196
|
-
routerCode: string,
|
|
2197
|
-
) => {
|
|
2198
|
-
const { id: matchId, routeId } = matches[index]!
|
|
2199
|
-
const route = this.looseRoutesById[routeId]!
|
|
2200
|
-
|
|
2201
|
-
// Much like suspense, we use a promise here to know if
|
|
2202
|
-
// we've been outdated by a new loadMatches call and
|
|
2203
|
-
// should abort the current async operation
|
|
2204
|
-
if (err instanceof Promise) {
|
|
2205
|
-
throw err
|
|
2206
|
-
}
|
|
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
|
|
2207
2659
|
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
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
|
+
}
|
|
2211
2665
|
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
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
|
+
)
|
|
2218
2682
|
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
...prev,
|
|
2226
|
-
error: err,
|
|
2227
|
-
status: 'error',
|
|
2228
|
-
isFetching: false,
|
|
2229
|
-
updatedAt: Date.now(),
|
|
2230
|
-
abortController: new AbortController(),
|
|
2231
|
-
}
|
|
2232
|
-
})
|
|
2233
|
-
}
|
|
2683
|
+
if (willLoadSomething) {
|
|
2684
|
+
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2685
|
+
...prev,
|
|
2686
|
+
isFetching: 'loader',
|
|
2687
|
+
}))
|
|
2688
|
+
}
|
|
2234
2689
|
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
? this.getMatch(parentMatchId)!
|
|
2240
|
-
: undefined
|
|
2241
|
-
|
|
2242
|
-
const route = this.looseRoutesById[routeId]!
|
|
2243
|
-
|
|
2244
|
-
const pendingMs =
|
|
2245
|
-
route.options.pendingMs ?? this.options.defaultPendingMs
|
|
2246
|
-
|
|
2247
|
-
// on the server, determine whether SSR the current match or not
|
|
2248
|
-
if (this.isServer) {
|
|
2249
|
-
let ssr: boolean | 'data-only'
|
|
2250
|
-
// in SPA mode, only SSR the root route
|
|
2251
|
-
if (this.isShell()) {
|
|
2252
|
-
ssr = matchId === rootRouteId
|
|
2253
|
-
} else {
|
|
2254
|
-
const defaultSsr = this.options.defaultSsr ?? true
|
|
2255
|
-
if (parentMatch?.ssr === false) {
|
|
2256
|
-
ssr = false
|
|
2257
|
-
} else {
|
|
2258
|
-
let tempSsr: boolean | 'data-only'
|
|
2259
|
-
if (route.options.ssr === undefined) {
|
|
2260
|
-
tempSsr = defaultSsr
|
|
2261
|
-
} else if (typeof route.options.ssr === 'function') {
|
|
2262
|
-
const { search, params } = this.getMatch(matchId)!
|
|
2263
|
-
|
|
2264
|
-
function makeMaybe(value: any, error: any) {
|
|
2265
|
-
if (error) {
|
|
2266
|
-
return { status: 'error' as const, error }
|
|
2267
|
-
}
|
|
2268
|
-
return { status: 'success' as const, value }
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
const ssrFnContext: SsrContextOptions<any, any, any> = {
|
|
2272
|
-
search: makeMaybe(search, existingMatch.searchError),
|
|
2273
|
-
params: makeMaybe(params, existingMatch.paramsError),
|
|
2274
|
-
location,
|
|
2275
|
-
matches: matches.map((match) => ({
|
|
2276
|
-
index: match.index,
|
|
2277
|
-
pathname: match.pathname,
|
|
2278
|
-
fullPath: match.fullPath,
|
|
2279
|
-
staticData: match.staticData,
|
|
2280
|
-
id: match.id,
|
|
2281
|
-
routeId: match.routeId,
|
|
2282
|
-
search: makeMaybe(match.search, match.searchError),
|
|
2283
|
-
params: makeMaybe(match.params, match.paramsError),
|
|
2284
|
-
ssr: match.ssr,
|
|
2285
|
-
})),
|
|
2286
|
-
}
|
|
2287
|
-
tempSsr =
|
|
2288
|
-
(await route.options.ssr(ssrFnContext)) ?? defaultSsr
|
|
2289
|
-
} else {
|
|
2290
|
-
tempSsr = route.options.ssr
|
|
2291
|
-
}
|
|
2690
|
+
if (route.options.loader) {
|
|
2691
|
+
const loaderData = loaderResultIsPromise
|
|
2692
|
+
? await loaderResult
|
|
2693
|
+
: loaderResult
|
|
2292
2694
|
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2695
|
+
this.handleRedirectAndNotFound(
|
|
2696
|
+
innerLoadContext,
|
|
2697
|
+
this.getMatch(matchId),
|
|
2698
|
+
loaderData,
|
|
2699
|
+
)
|
|
2700
|
+
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2701
|
+
...prev,
|
|
2702
|
+
loaderData,
|
|
2703
|
+
}))
|
|
2704
|
+
}
|
|
2302
2705
|
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2706
|
+
// Lazy option can modify the route options,
|
|
2707
|
+
// so we need to wait for it to resolve before
|
|
2708
|
+
// we can use the options
|
|
2709
|
+
if (route._lazyPromise) await route._lazyPromise
|
|
2710
|
+
const headResult = this.executeHead(innerLoadContext, matchId, route)
|
|
2711
|
+
const head = headResult ? await headResult : undefined
|
|
2712
|
+
const pendingPromise = this.potentialPendingMinPromise(matchId)
|
|
2713
|
+
if (pendingPromise) await pendingPromise
|
|
2714
|
+
|
|
2715
|
+
// Last but not least, wait for the the components
|
|
2716
|
+
// to be preloaded before we resolve the match
|
|
2717
|
+
if (route._componentsPromise) await route._componentsPromise
|
|
2718
|
+
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2719
|
+
...prev,
|
|
2720
|
+
error: undefined,
|
|
2721
|
+
status: 'success',
|
|
2722
|
+
isFetching: false,
|
|
2723
|
+
updatedAt: Date.now(),
|
|
2724
|
+
...head,
|
|
2725
|
+
}))
|
|
2726
|
+
} catch (e) {
|
|
2727
|
+
let error = e
|
|
2306
2728
|
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
!this.isServer &&
|
|
2310
|
-
!resolvePreload(matchId) &&
|
|
2311
|
-
(route.options.loader ||
|
|
2312
|
-
route.options.beforeLoad ||
|
|
2313
|
-
routeNeedsPreload(route)) &&
|
|
2314
|
-
typeof pendingMs === 'number' &&
|
|
2315
|
-
pendingMs !== Infinity &&
|
|
2316
|
-
(route.options.pendingComponent ??
|
|
2317
|
-
(this.options as any)?.defaultPendingComponent)
|
|
2318
|
-
)
|
|
2729
|
+
const pendingPromise = this.potentialPendingMinPromise(matchId)
|
|
2730
|
+
if (pendingPromise) await pendingPromise
|
|
2319
2731
|
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
isFetching: false,
|
|
2456
|
-
}
|
|
2457
|
-
})
|
|
2458
|
-
}
|
|
2732
|
+
this.handleRedirectAndNotFound(
|
|
2733
|
+
innerLoadContext,
|
|
2734
|
+
this.getMatch(matchId),
|
|
2735
|
+
e,
|
|
2736
|
+
)
|
|
2737
|
+
|
|
2738
|
+
try {
|
|
2739
|
+
route.options.onError?.(e)
|
|
2740
|
+
} catch (onErrorError) {
|
|
2741
|
+
error = onErrorError
|
|
2742
|
+
this.handleRedirectAndNotFound(
|
|
2743
|
+
innerLoadContext,
|
|
2744
|
+
this.getMatch(matchId),
|
|
2745
|
+
onErrorError,
|
|
2746
|
+
)
|
|
2747
|
+
}
|
|
2748
|
+
const headResult = this.executeHead(innerLoadContext, matchId, route)
|
|
2749
|
+
const head = headResult ? await headResult : undefined
|
|
2750
|
+
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2751
|
+
...prev,
|
|
2752
|
+
error,
|
|
2753
|
+
status: 'error',
|
|
2754
|
+
isFetching: false,
|
|
2755
|
+
...head,
|
|
2756
|
+
}))
|
|
2757
|
+
}
|
|
2758
|
+
} catch (err) {
|
|
2759
|
+
const match = this.getMatch(matchId)
|
|
2760
|
+
// in case of a redirecting match during preload, the match does not exist
|
|
2761
|
+
if (match) {
|
|
2762
|
+
const headResult = this.executeHead(innerLoadContext, matchId, route)
|
|
2763
|
+
if (headResult) {
|
|
2764
|
+
const head = await headResult
|
|
2765
|
+
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2766
|
+
...prev,
|
|
2767
|
+
...head,
|
|
2768
|
+
}))
|
|
2769
|
+
}
|
|
2770
|
+
match._nonReactive.loaderPromise = undefined
|
|
2771
|
+
}
|
|
2772
|
+
this.handleRedirectAndNotFound(innerLoadContext, match, err)
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
private loadRouteMatch = async (
|
|
2777
|
+
innerLoadContext: InnerLoadContext,
|
|
2778
|
+
index: number,
|
|
2779
|
+
): Promise<AnyRouteMatch> => {
|
|
2780
|
+
const { id: matchId, routeId } = innerLoadContext.matches[index]!
|
|
2781
|
+
let loaderShouldRunAsync = false
|
|
2782
|
+
let loaderIsRunningAsync = false
|
|
2783
|
+
const route = this.looseRoutesById[routeId]!
|
|
2784
|
+
|
|
2785
|
+
const prevMatch = this.getMatch(matchId)!
|
|
2786
|
+
if (this.shouldSkipLoader(matchId)) {
|
|
2787
|
+
if (this.isServer) {
|
|
2788
|
+
const headResult = this.executeHead(innerLoadContext, matchId, route)
|
|
2789
|
+
if (headResult) {
|
|
2790
|
+
const head = await headResult
|
|
2791
|
+
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2792
|
+
...prev,
|
|
2793
|
+
...head,
|
|
2794
|
+
}))
|
|
2795
|
+
}
|
|
2796
|
+
return this.getMatch(matchId)!
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
// there is a loaderPromise, so we are in the middle of a load
|
|
2800
|
+
else if (prevMatch._nonReactive.loaderPromise) {
|
|
2801
|
+
// do not block if we already have stale data we can show
|
|
2802
|
+
// but only if the ongoing load is not a preload since error handling is different for preloads
|
|
2803
|
+
// and we don't want to swallow errors
|
|
2804
|
+
if (
|
|
2805
|
+
prevMatch.status === 'success' &&
|
|
2806
|
+
!innerLoadContext.sync &&
|
|
2807
|
+
!prevMatch.preload
|
|
2808
|
+
) {
|
|
2809
|
+
return this.getMatch(matchId)!
|
|
2810
|
+
}
|
|
2811
|
+
await prevMatch._nonReactive.loaderPromise
|
|
2812
|
+
const match = this.getMatch(matchId)!
|
|
2813
|
+
if (match.error) {
|
|
2814
|
+
this.handleRedirectAndNotFound(innerLoadContext, match, match.error)
|
|
2815
|
+
}
|
|
2816
|
+
} else {
|
|
2817
|
+
// This is where all of the stale-while-revalidate magic happens
|
|
2818
|
+
const age = Date.now() - this.getMatch(matchId)!.updatedAt
|
|
2819
|
+
|
|
2820
|
+
const preload = this.resolvePreload(innerLoadContext, matchId)
|
|
2821
|
+
|
|
2822
|
+
const staleAge = preload
|
|
2823
|
+
? (route.options.preloadStaleTime ??
|
|
2824
|
+
this.options.defaultPreloadStaleTime ??
|
|
2825
|
+
30_000) // 30 seconds for preloads by default
|
|
2826
|
+
: (route.options.staleTime ?? this.options.defaultStaleTime ?? 0)
|
|
2827
|
+
|
|
2828
|
+
const shouldReloadOption = route.options.shouldReload
|
|
2829
|
+
|
|
2830
|
+
// Default to reloading the route all the time
|
|
2831
|
+
// Allow shouldReload to get the last say,
|
|
2832
|
+
// if provided.
|
|
2833
|
+
const shouldReload =
|
|
2834
|
+
typeof shouldReloadOption === 'function'
|
|
2835
|
+
? shouldReloadOption(
|
|
2836
|
+
this.getLoaderContext(innerLoadContext, matchId, index, route),
|
|
2837
|
+
)
|
|
2838
|
+
: shouldReloadOption
|
|
2839
|
+
|
|
2840
|
+
innerLoadContext.updateMatch(matchId, (prev) => {
|
|
2841
|
+
prev._nonReactive.loaderPromise = createControlledPromise<void>()
|
|
2842
|
+
return {
|
|
2843
|
+
...prev,
|
|
2844
|
+
preload:
|
|
2845
|
+
!!preload && !this.state.matches.some((d) => d.id === matchId),
|
|
2846
|
+
}
|
|
2847
|
+
})
|
|
2848
|
+
|
|
2849
|
+
// If the route is successful and still fresh, just resolve
|
|
2850
|
+
const { status, invalid } = this.getMatch(matchId)!
|
|
2851
|
+
loaderShouldRunAsync =
|
|
2852
|
+
status === 'success' && (invalid || (shouldReload ?? age > staleAge))
|
|
2853
|
+
if (preload && route.options.preload === false) {
|
|
2854
|
+
// Do nothing
|
|
2855
|
+
} else if (loaderShouldRunAsync && !innerLoadContext.sync) {
|
|
2856
|
+
loaderIsRunningAsync = true
|
|
2857
|
+
;(async () => {
|
|
2858
|
+
try {
|
|
2859
|
+
await this.runLoader(innerLoadContext, matchId, index, route)
|
|
2860
|
+
const match = this.getMatch(matchId)!
|
|
2861
|
+
match._nonReactive.loaderPromise?.resolve()
|
|
2862
|
+
match._nonReactive.loadPromise?.resolve()
|
|
2863
|
+
match._nonReactive.loaderPromise = undefined
|
|
2864
|
+
} catch (err) {
|
|
2865
|
+
if (isRedirect(err)) {
|
|
2866
|
+
await this.navigate(err.options)
|
|
2459
2867
|
}
|
|
2868
|
+
}
|
|
2869
|
+
})()
|
|
2870
|
+
} else if (
|
|
2871
|
+
status !== 'success' ||
|
|
2872
|
+
(loaderShouldRunAsync && innerLoadContext.sync)
|
|
2873
|
+
) {
|
|
2874
|
+
await this.runLoader(innerLoadContext, matchId, index, route)
|
|
2875
|
+
} else {
|
|
2876
|
+
// if the loader did not run, still update head.
|
|
2877
|
+
// reason: parent's beforeLoad may have changed the route context
|
|
2878
|
+
// and only now do we know the route context (and that the loader would not run)
|
|
2879
|
+
const headResult = this.executeHead(innerLoadContext, matchId, route)
|
|
2880
|
+
if (headResult) {
|
|
2881
|
+
const head = await headResult
|
|
2882
|
+
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2883
|
+
...prev,
|
|
2884
|
+
...head,
|
|
2885
|
+
}))
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
if (!loaderIsRunningAsync) {
|
|
2890
|
+
const match = this.getMatch(matchId)!
|
|
2891
|
+
match._nonReactive.loaderPromise?.resolve()
|
|
2892
|
+
match._nonReactive.loadPromise?.resolve()
|
|
2893
|
+
}
|
|
2460
2894
|
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
if (!match) {
|
|
2475
|
-
return
|
|
2476
|
-
}
|
|
2477
|
-
if (
|
|
2478
|
-
!route.options.head &&
|
|
2479
|
-
!route.options.scripts &&
|
|
2480
|
-
!route.options.headers
|
|
2481
|
-
) {
|
|
2482
|
-
return
|
|
2483
|
-
}
|
|
2484
|
-
const assetContext = {
|
|
2485
|
-
matches,
|
|
2486
|
-
match,
|
|
2487
|
-
params: match.params,
|
|
2488
|
-
loaderData: match.loaderData,
|
|
2489
|
-
}
|
|
2895
|
+
innerLoadContext.updateMatch(matchId, (prev) => {
|
|
2896
|
+
clearTimeout(prev._nonReactive.pendingTimeout)
|
|
2897
|
+
prev._nonReactive.pendingTimeout = undefined
|
|
2898
|
+
if (!loaderIsRunningAsync) prev._nonReactive.loaderPromise = undefined
|
|
2899
|
+
prev._nonReactive.dehydrated = undefined
|
|
2900
|
+
return {
|
|
2901
|
+
...prev,
|
|
2902
|
+
isFetching: loaderIsRunningAsync ? prev.isFetching : false,
|
|
2903
|
+
invalid: false,
|
|
2904
|
+
}
|
|
2905
|
+
})
|
|
2906
|
+
return this.getMatch(matchId)!
|
|
2907
|
+
}
|
|
2490
2908
|
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
meta,
|
|
2503
|
-
links,
|
|
2504
|
-
headScripts,
|
|
2505
|
-
headers,
|
|
2506
|
-
scripts,
|
|
2507
|
-
styles,
|
|
2508
|
-
}
|
|
2509
|
-
})
|
|
2510
|
-
}
|
|
2511
|
-
|
|
2512
|
-
const potentialPendingMinPromise = () => {
|
|
2513
|
-
const latestMatch = this.getMatch(matchId)!
|
|
2514
|
-
return latestMatch._nonReactive.minPendingPromise
|
|
2515
|
-
}
|
|
2516
|
-
|
|
2517
|
-
const prevMatch = this.getMatch(matchId)!
|
|
2518
|
-
if (shouldSkipLoader(matchId)) {
|
|
2519
|
-
if (this.isServer) {
|
|
2520
|
-
const headResult = executeHead()
|
|
2521
|
-
if (headResult) {
|
|
2522
|
-
const head = await headResult
|
|
2523
|
-
updateMatch(matchId, (prev) => ({
|
|
2524
|
-
...prev,
|
|
2525
|
-
...head,
|
|
2526
|
-
}))
|
|
2527
|
-
}
|
|
2528
|
-
return this.getMatch(matchId)!
|
|
2529
|
-
}
|
|
2530
|
-
}
|
|
2531
|
-
// there is a loaderPromise, so we are in the middle of a load
|
|
2532
|
-
else if (prevMatch._nonReactive.loaderPromise) {
|
|
2533
|
-
// do not block if we already have stale data we can show
|
|
2534
|
-
// but only if the ongoing load is not a preload since error handling is different for preloads
|
|
2535
|
-
// and we don't want to swallow errors
|
|
2536
|
-
if (
|
|
2537
|
-
prevMatch.status === 'success' &&
|
|
2538
|
-
!sync &&
|
|
2539
|
-
!prevMatch.preload
|
|
2540
|
-
) {
|
|
2541
|
-
return this.getMatch(matchId)!
|
|
2542
|
-
}
|
|
2543
|
-
await prevMatch._nonReactive.loaderPromise
|
|
2544
|
-
const match = this.getMatch(matchId)!
|
|
2545
|
-
if (match.error) {
|
|
2546
|
-
handleRedirectAndNotFound(match, match.error)
|
|
2547
|
-
}
|
|
2548
|
-
} else {
|
|
2549
|
-
const parentMatchPromise = matchPromises[index - 1] as any
|
|
2550
|
-
|
|
2551
|
-
const getLoaderContext = (): LoaderFnContext => {
|
|
2552
|
-
const {
|
|
2553
|
-
params,
|
|
2554
|
-
loaderDeps,
|
|
2555
|
-
abortController,
|
|
2556
|
-
context,
|
|
2557
|
-
cause,
|
|
2558
|
-
} = this.getMatch(matchId)!
|
|
2559
|
-
|
|
2560
|
-
const preload = resolvePreload(matchId)
|
|
2561
|
-
|
|
2562
|
-
return {
|
|
2563
|
-
params,
|
|
2564
|
-
deps: loaderDeps,
|
|
2565
|
-
preload: !!preload,
|
|
2566
|
-
parentMatchPromise,
|
|
2567
|
-
abortController: abortController,
|
|
2568
|
-
context,
|
|
2569
|
-
location,
|
|
2570
|
-
navigate: (opts) =>
|
|
2571
|
-
this.navigate({ ...opts, _fromLocation: location }),
|
|
2572
|
-
cause: preload ? 'preload' : cause,
|
|
2573
|
-
route,
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2909
|
+
loadMatches = async (baseContext: {
|
|
2910
|
+
location: ParsedLocation
|
|
2911
|
+
matches: Array<AnyRouteMatch>
|
|
2912
|
+
preload?: boolean
|
|
2913
|
+
onReady?: () => Promise<void>
|
|
2914
|
+
updateMatch?: UpdateMatchFn
|
|
2915
|
+
sync?: boolean
|
|
2916
|
+
}): Promise<Array<MakeRouteMatch>> => {
|
|
2917
|
+
const innerLoadContext = baseContext as InnerLoadContext
|
|
2918
|
+
innerLoadContext.updateMatch ??= this.updateMatch
|
|
2919
|
+
innerLoadContext.matchPromises = []
|
|
2576
2920
|
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
const staleAge = preload
|
|
2583
|
-
? (route.options.preloadStaleTime ??
|
|
2584
|
-
this.options.defaultPreloadStaleTime ??
|
|
2585
|
-
30_000) // 30 seconds for preloads by default
|
|
2586
|
-
: (route.options.staleTime ??
|
|
2587
|
-
this.options.defaultStaleTime ??
|
|
2588
|
-
0)
|
|
2589
|
-
|
|
2590
|
-
const shouldReloadOption = route.options.shouldReload
|
|
2591
|
-
|
|
2592
|
-
// Default to reloading the route all the time
|
|
2593
|
-
// Allow shouldReload to get the last say,
|
|
2594
|
-
// if provided.
|
|
2595
|
-
const shouldReload =
|
|
2596
|
-
typeof shouldReloadOption === 'function'
|
|
2597
|
-
? shouldReloadOption(getLoaderContext())
|
|
2598
|
-
: shouldReloadOption
|
|
2599
|
-
|
|
2600
|
-
updateMatch(matchId, (prev) => {
|
|
2601
|
-
prev._nonReactive.loaderPromise =
|
|
2602
|
-
createControlledPromise<void>()
|
|
2603
|
-
return {
|
|
2604
|
-
...prev,
|
|
2605
|
-
preload:
|
|
2606
|
-
!!preload &&
|
|
2607
|
-
!this.state.matches.some((d) => d.id === matchId),
|
|
2608
|
-
}
|
|
2609
|
-
})
|
|
2610
|
-
|
|
2611
|
-
const runLoader = async () => {
|
|
2612
|
-
try {
|
|
2613
|
-
// If the Matches component rendered
|
|
2614
|
-
// the pending component and needs to show it for
|
|
2615
|
-
// a minimum duration, we''ll wait for it to resolve
|
|
2616
|
-
// before committing to the match and resolving
|
|
2617
|
-
// the loadPromise
|
|
2618
|
-
|
|
2619
|
-
// Actually run the loader and handle the result
|
|
2620
|
-
try {
|
|
2621
|
-
if (
|
|
2622
|
-
!this.isServer ||
|
|
2623
|
-
this.getMatch(matchId)!.ssr === true
|
|
2624
|
-
) {
|
|
2625
|
-
this.loadRouteChunk(route)
|
|
2626
|
-
}
|
|
2627
|
-
|
|
2628
|
-
// Kick off the loader!
|
|
2629
|
-
const loaderResult =
|
|
2630
|
-
route.options.loader?.(getLoaderContext())
|
|
2631
|
-
const loaderResultIsPromise =
|
|
2632
|
-
route.options.loader && isPromise(loaderResult)
|
|
2633
|
-
|
|
2634
|
-
const willLoadSomething = !!(
|
|
2635
|
-
loaderResultIsPromise ||
|
|
2636
|
-
route._lazyPromise ||
|
|
2637
|
-
route._componentsPromise ||
|
|
2638
|
-
route.options.head ||
|
|
2639
|
-
route.options.scripts ||
|
|
2640
|
-
route.options.headers ||
|
|
2641
|
-
this.getMatch(matchId)!._nonReactive
|
|
2642
|
-
.minPendingPromise
|
|
2643
|
-
)
|
|
2644
|
-
|
|
2645
|
-
if (willLoadSomething) {
|
|
2646
|
-
updateMatch(matchId, (prev) => ({
|
|
2647
|
-
...prev,
|
|
2648
|
-
isFetching: 'loader',
|
|
2649
|
-
}))
|
|
2650
|
-
}
|
|
2651
|
-
|
|
2652
|
-
if (route.options.loader) {
|
|
2653
|
-
const loaderData = loaderResultIsPromise
|
|
2654
|
-
? await loaderResult
|
|
2655
|
-
: loaderResult
|
|
2656
|
-
|
|
2657
|
-
handleRedirectAndNotFound(
|
|
2658
|
-
this.getMatch(matchId),
|
|
2659
|
-
loaderData,
|
|
2660
|
-
)
|
|
2661
|
-
updateMatch(matchId, (prev) => ({
|
|
2662
|
-
...prev,
|
|
2663
|
-
loaderData,
|
|
2664
|
-
}))
|
|
2665
|
-
}
|
|
2666
|
-
|
|
2667
|
-
// Lazy option can modify the route options,
|
|
2668
|
-
// so we need to wait for it to resolve before
|
|
2669
|
-
// we can use the options
|
|
2670
|
-
if (route._lazyPromise) await route._lazyPromise
|
|
2671
|
-
const headResult = executeHead()
|
|
2672
|
-
const head = headResult ? await headResult : undefined
|
|
2673
|
-
const pendingPromise = potentialPendingMinPromise()
|
|
2674
|
-
if (pendingPromise) await pendingPromise
|
|
2675
|
-
|
|
2676
|
-
// Last but not least, wait for the the components
|
|
2677
|
-
// to be preloaded before we resolve the match
|
|
2678
|
-
if (route._componentsPromise)
|
|
2679
|
-
await route._componentsPromise
|
|
2680
|
-
updateMatch(matchId, (prev) => ({
|
|
2681
|
-
...prev,
|
|
2682
|
-
error: undefined,
|
|
2683
|
-
status: 'success',
|
|
2684
|
-
isFetching: false,
|
|
2685
|
-
updatedAt: Date.now(),
|
|
2686
|
-
...head,
|
|
2687
|
-
}))
|
|
2688
|
-
} catch (e) {
|
|
2689
|
-
let error = e
|
|
2690
|
-
|
|
2691
|
-
await potentialPendingMinPromise()
|
|
2692
|
-
|
|
2693
|
-
handleRedirectAndNotFound(this.getMatch(matchId), e)
|
|
2694
|
-
|
|
2695
|
-
try {
|
|
2696
|
-
route.options.onError?.(e)
|
|
2697
|
-
} catch (onErrorError) {
|
|
2698
|
-
error = onErrorError
|
|
2699
|
-
handleRedirectAndNotFound(
|
|
2700
|
-
this.getMatch(matchId),
|
|
2701
|
-
onErrorError,
|
|
2702
|
-
)
|
|
2703
|
-
}
|
|
2704
|
-
const headResult = executeHead()
|
|
2705
|
-
const head = headResult ? await headResult : undefined
|
|
2706
|
-
updateMatch(matchId, (prev) => ({
|
|
2707
|
-
...prev,
|
|
2708
|
-
error,
|
|
2709
|
-
status: 'error',
|
|
2710
|
-
isFetching: false,
|
|
2711
|
-
...head,
|
|
2712
|
-
}))
|
|
2713
|
-
}
|
|
2714
|
-
} catch (err) {
|
|
2715
|
-
const match = this.getMatch(matchId)
|
|
2716
|
-
// in case of a redirecting match during preload, the match does not exist
|
|
2717
|
-
if (match) {
|
|
2718
|
-
const headResult = executeHead()
|
|
2719
|
-
if (headResult) {
|
|
2720
|
-
const head = await headResult
|
|
2721
|
-
updateMatch(matchId, (prev) => ({
|
|
2722
|
-
...prev,
|
|
2723
|
-
...head,
|
|
2724
|
-
}))
|
|
2725
|
-
}
|
|
2726
|
-
match._nonReactive.loaderPromise = undefined
|
|
2727
|
-
}
|
|
2728
|
-
handleRedirectAndNotFound(match, err)
|
|
2729
|
-
}
|
|
2730
|
-
}
|
|
2921
|
+
// make sure the pending component is immediately rendered when hydrating a match that is not SSRed
|
|
2922
|
+
// the pending component was already rendered on the server and we want to keep it shown on the client until minPendingMs is reached
|
|
2923
|
+
if (!this.isServer && this.state.matches.some((d) => d._forcePending)) {
|
|
2924
|
+
this.triggerOnReady(innerLoadContext)
|
|
2925
|
+
}
|
|
2731
2926
|
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
if (isRedirect(err)) {
|
|
2750
|
-
await this.navigate(err.options)
|
|
2751
|
-
}
|
|
2752
|
-
}
|
|
2753
|
-
})()
|
|
2754
|
-
} else if (
|
|
2755
|
-
status !== 'success' ||
|
|
2756
|
-
(loaderShouldRunAsync && sync)
|
|
2757
|
-
) {
|
|
2758
|
-
await runLoader()
|
|
2759
|
-
} else {
|
|
2760
|
-
// if the loader did not run, still update head.
|
|
2761
|
-
// reason: parent's beforeLoad may have changed the route context
|
|
2762
|
-
// and only now do we know the route context (and that the loader would not run)
|
|
2763
|
-
const headResult = executeHead()
|
|
2764
|
-
if (headResult) {
|
|
2765
|
-
const head = await headResult
|
|
2766
|
-
updateMatch(matchId, (prev) => ({
|
|
2767
|
-
...prev,
|
|
2768
|
-
...head,
|
|
2769
|
-
}))
|
|
2770
|
-
}
|
|
2771
|
-
}
|
|
2772
|
-
}
|
|
2773
|
-
if (!loaderIsRunningAsync) {
|
|
2774
|
-
const match = this.getMatch(matchId)!
|
|
2775
|
-
match._nonReactive.loaderPromise?.resolve()
|
|
2776
|
-
match._nonReactive.loadPromise?.resolve()
|
|
2777
|
-
}
|
|
2778
|
-
|
|
2779
|
-
updateMatch(matchId, (prev) => {
|
|
2780
|
-
clearTimeout(prev._nonReactive.pendingTimeout)
|
|
2781
|
-
prev._nonReactive.pendingTimeout = undefined
|
|
2782
|
-
if (!loaderIsRunningAsync)
|
|
2783
|
-
prev._nonReactive.loaderPromise = undefined
|
|
2784
|
-
prev._nonReactive.dehydrated = undefined
|
|
2785
|
-
return {
|
|
2786
|
-
...prev,
|
|
2787
|
-
isFetching: loaderIsRunningAsync
|
|
2788
|
-
? prev.isFetching
|
|
2789
|
-
: false,
|
|
2790
|
-
invalid: false,
|
|
2791
|
-
}
|
|
2792
|
-
})
|
|
2793
|
-
return this.getMatch(matchId)!
|
|
2794
|
-
})(),
|
|
2927
|
+
try {
|
|
2928
|
+
await new Promise<void>((resolveAll, rejectAll) => {
|
|
2929
|
+
;(async () => {
|
|
2930
|
+
try {
|
|
2931
|
+
// Execute all beforeLoads one by one
|
|
2932
|
+
for (let i = 0; i < innerLoadContext.matches.length; i++) {
|
|
2933
|
+
const beforeLoad = this.handleBeforeLoad(innerLoadContext, i)
|
|
2934
|
+
if (isPromise(beforeLoad)) await beforeLoad
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
// Execute all loaders in parallel
|
|
2938
|
+
const max =
|
|
2939
|
+
innerLoadContext.firstBadMatchIndex ??
|
|
2940
|
+
innerLoadContext.matches.length
|
|
2941
|
+
for (let i = 0; i < max; i++) {
|
|
2942
|
+
innerLoadContext.matchPromises.push(
|
|
2943
|
+
this.loadRouteMatch(innerLoadContext, i),
|
|
2795
2944
|
)
|
|
2796
|
-
}
|
|
2945
|
+
}
|
|
2797
2946
|
|
|
2798
|
-
await Promise.all(matchPromises)
|
|
2947
|
+
await Promise.all(innerLoadContext.matchPromises)
|
|
2799
2948
|
|
|
2800
2949
|
resolveAll()
|
|
2801
2950
|
} catch (err) {
|
|
@@ -2803,18 +2952,20 @@ export class RouterCore<
|
|
|
2803
2952
|
}
|
|
2804
2953
|
})()
|
|
2805
2954
|
})
|
|
2806
|
-
|
|
2955
|
+
const readyPromise = this.triggerOnReady(innerLoadContext)
|
|
2956
|
+
if (isPromise(readyPromise)) await readyPromise
|
|
2807
2957
|
} catch (err) {
|
|
2808
|
-
if (
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2958
|
+
if (isNotFound(err) && !innerLoadContext.preload) {
|
|
2959
|
+
const readyPromise = this.triggerOnReady(innerLoadContext)
|
|
2960
|
+
if (isPromise(readyPromise)) await readyPromise
|
|
2961
|
+
throw err
|
|
2962
|
+
}
|
|
2963
|
+
if (isRedirect(err)) {
|
|
2813
2964
|
throw err
|
|
2814
2965
|
}
|
|
2815
2966
|
}
|
|
2816
2967
|
|
|
2817
|
-
return matches
|
|
2968
|
+
return innerLoadContext.matches
|
|
2818
2969
|
}
|
|
2819
2970
|
|
|
2820
2971
|
invalidate: InvalidateFn<
|
|
@@ -3088,13 +3239,9 @@ export class RouterCore<
|
|
|
3088
3239
|
|
|
3089
3240
|
serverSsr?: ServerSsr
|
|
3090
3241
|
|
|
3091
|
-
_handleNotFound = (
|
|
3092
|
-
|
|
3242
|
+
private _handleNotFound = (
|
|
3243
|
+
innerLoadContext: InnerLoadContext,
|
|
3093
3244
|
err: NotFoundError,
|
|
3094
|
-
updateMatch: (
|
|
3095
|
-
id: string,
|
|
3096
|
-
updater: (match: AnyRouteMatch) => AnyRouteMatch,
|
|
3097
|
-
) => void = this.updateMatch,
|
|
3098
3245
|
) => {
|
|
3099
3246
|
// Find the route that should handle the not found error
|
|
3100
3247
|
// First check if a specific route is requested to show the error
|
|
@@ -3102,7 +3249,7 @@ export class RouterCore<
|
|
|
3102
3249
|
const matchesByRouteId: Record<string, AnyRouteMatch> = {}
|
|
3103
3250
|
|
|
3104
3251
|
// Setup routesByRouteId object for quick access
|
|
3105
|
-
for (const match of matches) {
|
|
3252
|
+
for (const match of innerLoadContext.matches) {
|
|
3106
3253
|
matchesByRouteId[match.routeId] = match
|
|
3107
3254
|
}
|
|
3108
3255
|
|
|
@@ -3131,7 +3278,7 @@ export class RouterCore<
|
|
|
3131
3278
|
)
|
|
3132
3279
|
|
|
3133
3280
|
// Assign the error to the match - using non-null assertion since we've checked with invariant
|
|
3134
|
-
updateMatch(matchForRoute.id, (prev) => ({
|
|
3281
|
+
innerLoadContext.updateMatch(matchForRoute.id, (prev) => ({
|
|
3135
3282
|
...prev,
|
|
3136
3283
|
status: 'notFound',
|
|
3137
3284
|
error: err,
|
|
@@ -3140,7 +3287,7 @@ export class RouterCore<
|
|
|
3140
3287
|
|
|
3141
3288
|
if ((err as any).routerCode === 'BEFORE_LOAD' && routeCursor.parentRoute) {
|
|
3142
3289
|
err.routeId = routeCursor.parentRoute.id
|
|
3143
|
-
this._handleNotFound(
|
|
3290
|
+
this._handleNotFound(innerLoadContext, err)
|
|
3144
3291
|
}
|
|
3145
3292
|
}
|
|
3146
3293
|
|
|
@@ -3155,6 +3302,16 @@ export class SearchParamError extends Error {}
|
|
|
3155
3302
|
|
|
3156
3303
|
export class PathParamError extends Error {}
|
|
3157
3304
|
|
|
3305
|
+
function makeMaybe<TValue, TError>(
|
|
3306
|
+
value: TValue,
|
|
3307
|
+
error: TError,
|
|
3308
|
+
): { status: 'success'; value: TValue } | { status: 'error'; error: TError } {
|
|
3309
|
+
if (error) {
|
|
3310
|
+
return { status: 'error' as const, error }
|
|
3311
|
+
}
|
|
3312
|
+
return { status: 'success' as const, value }
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3158
3315
|
const normalize = (str: string) =>
|
|
3159
3316
|
str.endsWith('/') && str.length > 1 ? str.slice(0, -1) : str
|
|
3160
3317
|
function comparePaths(a: string, b: string) {
|