@remix-run/router 0.0.0-experimental-bcda00aaf → 0.0.0-experimental-3be88c6fb
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/router.cjs.js +152 -121
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.js +145 -118
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +152 -121
- package/dist/router.umd.js.map +1 -1
- package/dist/router.umd.min.js +2 -2
- package/dist/router.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/router.ts +181 -154
package/package.json
CHANGED
package/router.ts
CHANGED
|
@@ -890,33 +890,18 @@ export function createRouter(init: RouterInit): Router {
|
|
|
890
890
|
// were marked for explicit hydration
|
|
891
891
|
let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
|
|
892
892
|
let errors = init.hydrationData ? init.hydrationData.errors : null;
|
|
893
|
-
let isRouteInitialized = (m: AgnosticDataRouteMatch) => {
|
|
894
|
-
// No loader, nothing to initialize
|
|
895
|
-
if (!m.route.loader) {
|
|
896
|
-
return true;
|
|
897
|
-
}
|
|
898
|
-
// Explicitly opting-in to running on hydration
|
|
899
|
-
if (
|
|
900
|
-
typeof m.route.loader === "function" &&
|
|
901
|
-
m.route.loader.hydrate === true
|
|
902
|
-
) {
|
|
903
|
-
return false;
|
|
904
|
-
}
|
|
905
|
-
// Otherwise, initialized if hydrated with data or an error
|
|
906
|
-
return (
|
|
907
|
-
(loaderData && loaderData[m.route.id] !== undefined) ||
|
|
908
|
-
(errors && errors[m.route.id] !== undefined)
|
|
909
|
-
);
|
|
910
|
-
};
|
|
911
|
-
|
|
912
893
|
// If errors exist, don't consider routes below the boundary
|
|
913
894
|
if (errors) {
|
|
914
895
|
let idx = initialMatches.findIndex(
|
|
915
896
|
(m) => errors![m.route.id] !== undefined
|
|
916
897
|
);
|
|
917
|
-
initialized = initialMatches
|
|
898
|
+
initialized = initialMatches
|
|
899
|
+
.slice(0, idx + 1)
|
|
900
|
+
.every((m) => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
|
|
918
901
|
} else {
|
|
919
|
-
initialized = initialMatches.every(
|
|
902
|
+
initialized = initialMatches.every(
|
|
903
|
+
(m) => !shouldLoadRouteOnHydration(m.route, loaderData, errors)
|
|
904
|
+
);
|
|
920
905
|
}
|
|
921
906
|
} else {
|
|
922
907
|
// Without partial hydration - we're initialized if we were provided any
|
|
@@ -1525,10 +1510,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1525
1510
|
|
|
1526
1511
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
1527
1512
|
let loadingNavigation = opts && opts.overrideNavigation;
|
|
1528
|
-
let matches =
|
|
1529
|
-
isUninterruptedRevalidation && inFlightDataRoutes == null
|
|
1530
|
-
? state.matches
|
|
1531
|
-
: matchRoutes(routesToUse, location, basename);
|
|
1513
|
+
let matches = matchRoutes(routesToUse, location, basename);
|
|
1532
1514
|
let flushSync = (opts && opts.flushSync) === true;
|
|
1533
1515
|
|
|
1534
1516
|
let fogOfWar = checkFogOfWar(matches, routesToUse, location.pathname);
|
|
@@ -1558,7 +1540,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1558
1540
|
// Short circuit if it's only a hash change and not a revalidation or
|
|
1559
1541
|
// mutation submission.
|
|
1560
1542
|
//
|
|
1561
|
-
// Ignore on initial page loads because since the initial
|
|
1543
|
+
// Ignore on initial page loads because since the initial hydration will always
|
|
1562
1544
|
// be "same hash". For example, on /page#hash and submit a <Form method="post">
|
|
1563
1545
|
// which will default to a navigation to /page
|
|
1564
1546
|
if (
|
|
@@ -1982,9 +1964,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1982
1964
|
}
|
|
1983
1965
|
|
|
1984
1966
|
revalidatingFetchers.forEach((rf) => {
|
|
1985
|
-
|
|
1986
|
-
abortFetcher(rf.key);
|
|
1987
|
-
}
|
|
1967
|
+
abortFetcher(rf.key);
|
|
1988
1968
|
if (rf.controller) {
|
|
1989
1969
|
// Fetchers use an independent AbortController so that aborting a fetcher
|
|
1990
1970
|
// (via deleteFetcher) does not abort the triggering navigation that
|
|
@@ -2025,6 +2005,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2025
2005
|
abortPendingFetchRevalidations
|
|
2026
2006
|
);
|
|
2027
2007
|
}
|
|
2008
|
+
|
|
2028
2009
|
revalidatingFetchers.forEach((rf) => fetchControllers.delete(rf.key));
|
|
2029
2010
|
|
|
2030
2011
|
// If any loaders returned a redirect Response, start a new REPLACE navigation
|
|
@@ -2052,7 +2033,6 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2052
2033
|
let { loaderData, errors } = processLoaderData(
|
|
2053
2034
|
state,
|
|
2054
2035
|
matches,
|
|
2055
|
-
matchesToLoad,
|
|
2056
2036
|
loaderResults,
|
|
2057
2037
|
pendingActionResult,
|
|
2058
2038
|
revalidatingFetchers,
|
|
@@ -2072,13 +2052,9 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2072
2052
|
});
|
|
2073
2053
|
});
|
|
2074
2054
|
|
|
2075
|
-
//
|
|
2055
|
+
// Preserve SSR errors during partial hydration
|
|
2076
2056
|
if (future.v7_partialHydration && initialHydration && state.errors) {
|
|
2077
|
-
|
|
2078
|
-
.filter(([id]) => !matchesToLoad.some((m) => m.route.id === id))
|
|
2079
|
-
.forEach(([routeId, error]) => {
|
|
2080
|
-
errors = Object.assign(errors || {}, { [routeId]: error });
|
|
2081
|
-
});
|
|
2057
|
+
errors = { ...state.errors, ...errors };
|
|
2082
2058
|
}
|
|
2083
2059
|
|
|
2084
2060
|
let updatedFetchers = markFetchRedirectsDone();
|
|
@@ -2142,7 +2118,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2142
2118
|
);
|
|
2143
2119
|
}
|
|
2144
2120
|
|
|
2145
|
-
|
|
2121
|
+
abortFetcher(key);
|
|
2122
|
+
|
|
2146
2123
|
let flushSync = (opts && opts.flushSync) === true;
|
|
2147
2124
|
|
|
2148
2125
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
@@ -2187,7 +2164,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2187
2164
|
|
|
2188
2165
|
let match = getTargetMatch(matches, path);
|
|
2189
2166
|
|
|
2190
|
-
|
|
2167
|
+
let preventScrollReset = (opts && opts.preventScrollReset) === true;
|
|
2191
2168
|
|
|
2192
2169
|
if (submission && isMutationMethod(submission.formMethod)) {
|
|
2193
2170
|
handleFetcherAction(
|
|
@@ -2198,6 +2175,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2198
2175
|
matches,
|
|
2199
2176
|
fogOfWar.active,
|
|
2200
2177
|
flushSync,
|
|
2178
|
+
preventScrollReset,
|
|
2201
2179
|
submission
|
|
2202
2180
|
);
|
|
2203
2181
|
return;
|
|
@@ -2214,6 +2192,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2214
2192
|
matches,
|
|
2215
2193
|
fogOfWar.active,
|
|
2216
2194
|
flushSync,
|
|
2195
|
+
preventScrollReset,
|
|
2217
2196
|
submission
|
|
2218
2197
|
);
|
|
2219
2198
|
}
|
|
@@ -2228,6 +2207,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2228
2207
|
requestMatches: AgnosticDataRouteMatch[],
|
|
2229
2208
|
isFogOfWar: boolean,
|
|
2230
2209
|
flushSync: boolean,
|
|
2210
|
+
preventScrollReset: boolean,
|
|
2231
2211
|
submission: Submission
|
|
2232
2212
|
) {
|
|
2233
2213
|
interruptActiveLoads();
|
|
@@ -2342,6 +2322,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2342
2322
|
updateFetcherState(key, getLoadingFetcher(submission));
|
|
2343
2323
|
return startRedirectNavigation(fetchRequest, actionResult, false, {
|
|
2344
2324
|
fetcherSubmission: submission,
|
|
2325
|
+
preventScrollReset,
|
|
2345
2326
|
});
|
|
2346
2327
|
}
|
|
2347
2328
|
}
|
|
@@ -2411,9 +2392,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2411
2392
|
existingFetcher ? existingFetcher.data : undefined
|
|
2412
2393
|
);
|
|
2413
2394
|
state.fetchers.set(staleKey, revalidatingFetcher);
|
|
2414
|
-
|
|
2415
|
-
abortFetcher(staleKey);
|
|
2416
|
-
}
|
|
2395
|
+
abortFetcher(staleKey);
|
|
2417
2396
|
if (rf.controller) {
|
|
2418
2397
|
fetchControllers.set(staleKey, rf.controller);
|
|
2419
2398
|
}
|
|
@@ -2456,7 +2435,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2456
2435
|
return startRedirectNavigation(
|
|
2457
2436
|
revalidationRequest,
|
|
2458
2437
|
redirect.result,
|
|
2459
|
-
false
|
|
2438
|
+
false,
|
|
2439
|
+
{ preventScrollReset }
|
|
2460
2440
|
);
|
|
2461
2441
|
}
|
|
2462
2442
|
|
|
@@ -2469,7 +2449,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2469
2449
|
return startRedirectNavigation(
|
|
2470
2450
|
revalidationRequest,
|
|
2471
2451
|
redirect.result,
|
|
2472
|
-
false
|
|
2452
|
+
false,
|
|
2453
|
+
{ preventScrollReset }
|
|
2473
2454
|
);
|
|
2474
2455
|
}
|
|
2475
2456
|
|
|
@@ -2477,7 +2458,6 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2477
2458
|
let { loaderData, errors } = processLoaderData(
|
|
2478
2459
|
state,
|
|
2479
2460
|
matches,
|
|
2480
|
-
matchesToLoad,
|
|
2481
2461
|
loaderResults,
|
|
2482
2462
|
undefined,
|
|
2483
2463
|
revalidatingFetchers,
|
|
@@ -2537,6 +2517,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2537
2517
|
matches: AgnosticDataRouteMatch[],
|
|
2538
2518
|
isFogOfWar: boolean,
|
|
2539
2519
|
flushSync: boolean,
|
|
2520
|
+
preventScrollReset: boolean,
|
|
2540
2521
|
submission?: Submission
|
|
2541
2522
|
) {
|
|
2542
2523
|
let existingFetcher = state.fetchers.get(key);
|
|
@@ -2633,7 +2614,9 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2633
2614
|
return;
|
|
2634
2615
|
} else {
|
|
2635
2616
|
fetchRedirectIds.add(key);
|
|
2636
|
-
await startRedirectNavigation(fetchRequest, result, false
|
|
2617
|
+
await startRedirectNavigation(fetchRequest, result, false, {
|
|
2618
|
+
preventScrollReset,
|
|
2619
|
+
});
|
|
2637
2620
|
return;
|
|
2638
2621
|
}
|
|
2639
2622
|
}
|
|
@@ -2676,10 +2659,12 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2676
2659
|
{
|
|
2677
2660
|
submission,
|
|
2678
2661
|
fetcherSubmission,
|
|
2662
|
+
preventScrollReset,
|
|
2679
2663
|
replace,
|
|
2680
2664
|
}: {
|
|
2681
2665
|
submission?: Submission;
|
|
2682
2666
|
fetcherSubmission?: Submission;
|
|
2667
|
+
preventScrollReset?: boolean;
|
|
2683
2668
|
replace?: boolean;
|
|
2684
2669
|
} = {}
|
|
2685
2670
|
) {
|
|
@@ -2760,7 +2745,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2760
2745
|
formAction: location,
|
|
2761
2746
|
},
|
|
2762
2747
|
// Preserve these flags across redirects
|
|
2763
|
-
preventScrollReset: pendingPreventScrollReset,
|
|
2748
|
+
preventScrollReset: preventScrollReset || pendingPreventScrollReset,
|
|
2764
2749
|
enableViewTransition: isNavigation
|
|
2765
2750
|
? pendingViewTransitionEnabled
|
|
2766
2751
|
: undefined,
|
|
@@ -2777,7 +2762,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2777
2762
|
// Send fetcher submissions through for shouldRevalidate
|
|
2778
2763
|
fetcherSubmission,
|
|
2779
2764
|
// Preserve these flags across redirects
|
|
2780
|
-
preventScrollReset: pendingPreventScrollReset,
|
|
2765
|
+
preventScrollReset: preventScrollReset || pendingPreventScrollReset,
|
|
2781
2766
|
enableViewTransition: isNavigation
|
|
2782
2767
|
? pendingViewTransitionEnabled
|
|
2783
2768
|
: undefined,
|
|
@@ -2926,8 +2911,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2926
2911
|
fetchLoadMatches.forEach((_, key) => {
|
|
2927
2912
|
if (fetchControllers.has(key)) {
|
|
2928
2913
|
cancelledFetcherLoads.add(key);
|
|
2929
|
-
abortFetcher(key);
|
|
2930
2914
|
}
|
|
2915
|
+
abortFetcher(key);
|
|
2931
2916
|
});
|
|
2932
2917
|
}
|
|
2933
2918
|
|
|
@@ -3010,9 +2995,10 @@ export function createRouter(init: RouterInit): Router {
|
|
|
3010
2995
|
|
|
3011
2996
|
function abortFetcher(key: string) {
|
|
3012
2997
|
let controller = fetchControllers.get(key);
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
2998
|
+
if (controller) {
|
|
2999
|
+
controller.abort();
|
|
3000
|
+
fetchControllers.delete(key);
|
|
3001
|
+
}
|
|
3016
3002
|
}
|
|
3017
3003
|
|
|
3018
3004
|
function markFetchersDone(keys: string[]) {
|
|
@@ -3290,21 +3276,30 @@ export function createRouter(init: RouterInit): Router {
|
|
|
3290
3276
|
pathname: string,
|
|
3291
3277
|
signal: AbortSignal
|
|
3292
3278
|
): Promise<DiscoverRoutesResult> {
|
|
3279
|
+
if (!patchRoutesOnNavigationImpl) {
|
|
3280
|
+
return { type: "success", matches };
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3293
3283
|
let partialMatches: AgnosticDataRouteMatch[] | null = matches;
|
|
3294
3284
|
while (true) {
|
|
3295
3285
|
let isNonHMR = inFlightDataRoutes == null;
|
|
3296
3286
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
3287
|
+
let localManifest = manifest;
|
|
3297
3288
|
try {
|
|
3298
|
-
await
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3289
|
+
await patchRoutesOnNavigationImpl({
|
|
3290
|
+
path: pathname,
|
|
3291
|
+
matches: partialMatches,
|
|
3292
|
+
patch: (routeId, children) => {
|
|
3293
|
+
if (signal.aborted) return;
|
|
3294
|
+
patchRoutesImpl(
|
|
3295
|
+
routeId,
|
|
3296
|
+
children,
|
|
3297
|
+
routesToUse,
|
|
3298
|
+
localManifest,
|
|
3299
|
+
mapRouteProperties
|
|
3300
|
+
);
|
|
3301
|
+
},
|
|
3302
|
+
});
|
|
3308
3303
|
} catch (e) {
|
|
3309
3304
|
return { type: "error", error: e, partialMatches };
|
|
3310
3305
|
} finally {
|
|
@@ -3314,7 +3309,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
3314
3309
|
// trigger a re-run of memoized `router.routes` dependencies.
|
|
3315
3310
|
// HMR will already update the identity and reflow when it lands
|
|
3316
3311
|
// `inFlightDataRoutes` in `completeNavigation`
|
|
3317
|
-
if (isNonHMR) {
|
|
3312
|
+
if (isNonHMR && !signal.aborted) {
|
|
3318
3313
|
dataRoutes = [...dataRoutes];
|
|
3319
3314
|
}
|
|
3320
3315
|
}
|
|
@@ -4161,16 +4156,23 @@ function normalizeTo(
|
|
|
4161
4156
|
path.hash = location.hash;
|
|
4162
4157
|
}
|
|
4163
4158
|
|
|
4164
|
-
//
|
|
4165
|
-
if (
|
|
4166
|
-
|
|
4167
|
-
activeRouteMatch &&
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4159
|
+
// Account for `?index` params when routing to the current location
|
|
4160
|
+
if ((to == null || to === "" || to === ".") && activeRouteMatch) {
|
|
4161
|
+
let nakedIndex = hasNakedIndexQuery(path.search);
|
|
4162
|
+
if (activeRouteMatch.route.index && !nakedIndex) {
|
|
4163
|
+
// Add one when we're targeting an index route
|
|
4164
|
+
path.search = path.search
|
|
4165
|
+
? path.search.replace(/^\?/, "?index&")
|
|
4166
|
+
: "?index";
|
|
4167
|
+
} else if (!activeRouteMatch.route.index && nakedIndex) {
|
|
4168
|
+
// Remove existing ones when we're not
|
|
4169
|
+
let params = new URLSearchParams(path.search);
|
|
4170
|
+
let indexValues = params.getAll("index");
|
|
4171
|
+
params.delete("index");
|
|
4172
|
+
indexValues.filter((v) => v).forEach((v) => params.append("index", v));
|
|
4173
|
+
let qs = params.toString();
|
|
4174
|
+
path.search = qs ? `?${qs}` : "";
|
|
4175
|
+
}
|
|
4174
4176
|
}
|
|
4175
4177
|
|
|
4176
4178
|
// If we're operating within a basename, prepend it to the pathname. If
|
|
@@ -4334,20 +4336,18 @@ function normalizeNavigateOptions(
|
|
|
4334
4336
|
return { path: createPath(parsedPath), submission };
|
|
4335
4337
|
}
|
|
4336
4338
|
|
|
4337
|
-
// Filter out all routes below any caught error as they aren't going to
|
|
4339
|
+
// Filter out all routes at/below any caught error as they aren't going to
|
|
4338
4340
|
// render so we don't need to load them
|
|
4339
4341
|
function getLoaderMatchesUntilBoundary(
|
|
4340
4342
|
matches: AgnosticDataRouteMatch[],
|
|
4341
|
-
boundaryId: string
|
|
4343
|
+
boundaryId: string,
|
|
4344
|
+
includeBoundary = false
|
|
4342
4345
|
) {
|
|
4343
|
-
let
|
|
4344
|
-
if (
|
|
4345
|
-
|
|
4346
|
-
if (index >= 0) {
|
|
4347
|
-
boundaryMatches = matches.slice(0, index);
|
|
4348
|
-
}
|
|
4346
|
+
let index = matches.findIndex((m) => m.route.id === boundaryId);
|
|
4347
|
+
if (index >= 0) {
|
|
4348
|
+
return matches.slice(0, includeBoundary ? index + 1 : index);
|
|
4349
4349
|
}
|
|
4350
|
-
return
|
|
4350
|
+
return matches;
|
|
4351
4351
|
}
|
|
4352
4352
|
|
|
4353
4353
|
function getMatchesToLoad(
|
|
@@ -4356,7 +4356,7 @@ function getMatchesToLoad(
|
|
|
4356
4356
|
matches: AgnosticDataRouteMatch[],
|
|
4357
4357
|
submission: Submission | undefined,
|
|
4358
4358
|
location: Location,
|
|
4359
|
-
|
|
4359
|
+
initialHydration: boolean,
|
|
4360
4360
|
skipActionErrorRevalidation: boolean,
|
|
4361
4361
|
isRevalidationRequired: boolean,
|
|
4362
4362
|
cancelledDeferredRoutes: string[],
|
|
@@ -4377,13 +4377,26 @@ function getMatchesToLoad(
|
|
|
4377
4377
|
let nextUrl = history.createURL(location);
|
|
4378
4378
|
|
|
4379
4379
|
// Pick navigation matches that are net-new or qualify for revalidation
|
|
4380
|
-
let
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4380
|
+
let boundaryMatches = matches;
|
|
4381
|
+
if (initialHydration && state.errors) {
|
|
4382
|
+
// On initial hydration, only consider matches up to _and including_ the boundary.
|
|
4383
|
+
// This is inclusive to handle cases where a server loader ran successfully,
|
|
4384
|
+
// a child server loader bubbled up to this route, but this route has
|
|
4385
|
+
// `clientLoader.hydrate` so we want to still run the `clientLoader` so that
|
|
4386
|
+
// we have a complete version of `loaderData`
|
|
4387
|
+
boundaryMatches = getLoaderMatchesUntilBoundary(
|
|
4388
|
+
matches,
|
|
4389
|
+
Object.keys(state.errors)[0],
|
|
4390
|
+
true
|
|
4391
|
+
);
|
|
4392
|
+
} else if (pendingActionResult && isErrorResult(pendingActionResult[1])) {
|
|
4393
|
+
// If an action threw an error, we call loaders up to, but not including the
|
|
4394
|
+
// boundary
|
|
4395
|
+
boundaryMatches = getLoaderMatchesUntilBoundary(
|
|
4396
|
+
matches,
|
|
4397
|
+
pendingActionResult[0]
|
|
4398
|
+
);
|
|
4399
|
+
}
|
|
4387
4400
|
|
|
4388
4401
|
// Don't revalidate loaders by default after action 4xx/5xx responses
|
|
4389
4402
|
// when the flag is enabled. They can still opt-into revalidation via
|
|
@@ -4405,15 +4418,8 @@ function getMatchesToLoad(
|
|
|
4405
4418
|
return false;
|
|
4406
4419
|
}
|
|
4407
4420
|
|
|
4408
|
-
if (
|
|
4409
|
-
|
|
4410
|
-
return true;
|
|
4411
|
-
}
|
|
4412
|
-
return (
|
|
4413
|
-
state.loaderData[route.id] === undefined &&
|
|
4414
|
-
// Don't re-run if the loader ran and threw an error
|
|
4415
|
-
(!state.errors || state.errors[route.id] === undefined)
|
|
4416
|
-
);
|
|
4421
|
+
if (initialHydration) {
|
|
4422
|
+
return shouldLoadRouteOnHydration(route, state.loaderData, state.errors);
|
|
4417
4423
|
}
|
|
4418
4424
|
|
|
4419
4425
|
// Always call the loader on new route instances and pending defer cancellations
|
|
@@ -4455,12 +4461,12 @@ function getMatchesToLoad(
|
|
|
4455
4461
|
let revalidatingFetchers: RevalidatingFetcher[] = [];
|
|
4456
4462
|
fetchLoadMatches.forEach((f, key) => {
|
|
4457
4463
|
// Don't revalidate:
|
|
4458
|
-
// - on initial
|
|
4464
|
+
// - on initial hydration (shouldn't be any fetchers then anyway)
|
|
4459
4465
|
// - if fetcher won't be present in the subsequent render
|
|
4460
4466
|
// - no longer matches the URL (v7_fetcherPersist=false)
|
|
4461
4467
|
// - was unmounted but persisted due to v7_fetcherPersist=true
|
|
4462
4468
|
if (
|
|
4463
|
-
|
|
4469
|
+
initialHydration ||
|
|
4464
4470
|
!matches.some((m) => m.route.id === f.routeId) ||
|
|
4465
4471
|
deletedFetchers.has(key)
|
|
4466
4472
|
) {
|
|
@@ -4540,6 +4546,38 @@ function getMatchesToLoad(
|
|
|
4540
4546
|
return [navigationMatches, revalidatingFetchers];
|
|
4541
4547
|
}
|
|
4542
4548
|
|
|
4549
|
+
function shouldLoadRouteOnHydration(
|
|
4550
|
+
route: AgnosticDataRouteObject,
|
|
4551
|
+
loaderData: RouteData | null | undefined,
|
|
4552
|
+
errors: RouteData | null | undefined
|
|
4553
|
+
) {
|
|
4554
|
+
// We dunno if we have a loader - gotta find out!
|
|
4555
|
+
if (route.lazy) {
|
|
4556
|
+
return true;
|
|
4557
|
+
}
|
|
4558
|
+
|
|
4559
|
+
// No loader, nothing to initialize
|
|
4560
|
+
if (!route.loader) {
|
|
4561
|
+
return false;
|
|
4562
|
+
}
|
|
4563
|
+
|
|
4564
|
+
let hasData = loaderData != null && loaderData[route.id] !== undefined;
|
|
4565
|
+
let hasError = errors != null && errors[route.id] !== undefined;
|
|
4566
|
+
|
|
4567
|
+
// Don't run if we error'd during SSR
|
|
4568
|
+
if (!hasData && hasError) {
|
|
4569
|
+
return false;
|
|
4570
|
+
}
|
|
4571
|
+
|
|
4572
|
+
// Explicitly opting-in to running on hydration
|
|
4573
|
+
if (typeof route.loader === "function" && route.loader.hydrate === true) {
|
|
4574
|
+
return true;
|
|
4575
|
+
}
|
|
4576
|
+
|
|
4577
|
+
// Otherwise, run if we're not yet initialized with anything
|
|
4578
|
+
return !hasData && !hasError;
|
|
4579
|
+
}
|
|
4580
|
+
|
|
4543
4581
|
function isNewLoader(
|
|
4544
4582
|
currentLoaderData: RouteData,
|
|
4545
4583
|
currentMatch: AgnosticDataRouteMatch,
|
|
@@ -4589,53 +4627,6 @@ function shouldRevalidateLoader(
|
|
|
4589
4627
|
return arg.defaultShouldRevalidate;
|
|
4590
4628
|
}
|
|
4591
4629
|
|
|
4592
|
-
/**
|
|
4593
|
-
* Idempotent utility to execute patchRoutesOnNavigation() to lazily load route
|
|
4594
|
-
* definitions and update the routes/routeManifest
|
|
4595
|
-
*/
|
|
4596
|
-
async function loadLazyRouteChildren(
|
|
4597
|
-
patchRoutesOnNavigationImpl: AgnosticPatchRoutesOnNavigationFunction,
|
|
4598
|
-
path: string,
|
|
4599
|
-
matches: AgnosticDataRouteMatch[],
|
|
4600
|
-
routes: AgnosticDataRouteObject[],
|
|
4601
|
-
manifest: RouteManifest,
|
|
4602
|
-
mapRouteProperties: MapRoutePropertiesFunction,
|
|
4603
|
-
pendingRouteChildren: Map<
|
|
4604
|
-
string,
|
|
4605
|
-
ReturnType<typeof patchRoutesOnNavigationImpl>
|
|
4606
|
-
>,
|
|
4607
|
-
signal: AbortSignal
|
|
4608
|
-
) {
|
|
4609
|
-
let key = [path, ...matches.map((m) => m.route.id)].join("-");
|
|
4610
|
-
try {
|
|
4611
|
-
let pending = pendingRouteChildren.get(key);
|
|
4612
|
-
if (!pending) {
|
|
4613
|
-
pending = patchRoutesOnNavigationImpl({
|
|
4614
|
-
path,
|
|
4615
|
-
matches,
|
|
4616
|
-
patch: (routeId, children) => {
|
|
4617
|
-
if (!signal.aborted) {
|
|
4618
|
-
patchRoutesImpl(
|
|
4619
|
-
routeId,
|
|
4620
|
-
children,
|
|
4621
|
-
routes,
|
|
4622
|
-
manifest,
|
|
4623
|
-
mapRouteProperties
|
|
4624
|
-
);
|
|
4625
|
-
}
|
|
4626
|
-
},
|
|
4627
|
-
});
|
|
4628
|
-
pendingRouteChildren.set(key, pending);
|
|
4629
|
-
}
|
|
4630
|
-
|
|
4631
|
-
if (pending && isPromise<AgnosticRouteObject[]>(pending)) {
|
|
4632
|
-
await pending;
|
|
4633
|
-
}
|
|
4634
|
-
} finally {
|
|
4635
|
-
pendingRouteChildren.delete(key);
|
|
4636
|
-
}
|
|
4637
|
-
}
|
|
4638
|
-
|
|
4639
4630
|
function patchRoutesImpl(
|
|
4640
4631
|
routeId: string | null,
|
|
4641
4632
|
children: AgnosticRouteObject[],
|
|
@@ -4662,12 +4653,9 @@ function patchRoutesImpl(
|
|
|
4662
4653
|
// to simplify user-land code. This is useful because we re-call the
|
|
4663
4654
|
// `patchRoutesOnNavigation` function for matched routes with params.
|
|
4664
4655
|
let uniqueChildren = children.filter(
|
|
4665
|
-
(
|
|
4666
|
-
!childrenToPatch.some(
|
|
4667
|
-
(
|
|
4668
|
-
a.index === b.index &&
|
|
4669
|
-
a.path === b.path &&
|
|
4670
|
-
a.caseSensitive === b.caseSensitive
|
|
4656
|
+
(newRoute) =>
|
|
4657
|
+
!childrenToPatch.some((existingRoute) =>
|
|
4658
|
+
isSameRoute(newRoute, existingRoute)
|
|
4671
4659
|
)
|
|
4672
4660
|
);
|
|
4673
4661
|
|
|
@@ -4681,6 +4669,46 @@ function patchRoutesImpl(
|
|
|
4681
4669
|
childrenToPatch.push(...newRoutes);
|
|
4682
4670
|
}
|
|
4683
4671
|
|
|
4672
|
+
function isSameRoute(
|
|
4673
|
+
newRoute: AgnosticRouteObject,
|
|
4674
|
+
existingRoute: AgnosticRouteObject
|
|
4675
|
+
): boolean {
|
|
4676
|
+
// Most optimal check is by id
|
|
4677
|
+
if (
|
|
4678
|
+
"id" in newRoute &&
|
|
4679
|
+
"id" in existingRoute &&
|
|
4680
|
+
newRoute.id === existingRoute.id
|
|
4681
|
+
) {
|
|
4682
|
+
return true;
|
|
4683
|
+
}
|
|
4684
|
+
|
|
4685
|
+
// Second is by pathing differences
|
|
4686
|
+
if (
|
|
4687
|
+
!(
|
|
4688
|
+
newRoute.index === existingRoute.index &&
|
|
4689
|
+
newRoute.path === existingRoute.path &&
|
|
4690
|
+
newRoute.caseSensitive === existingRoute.caseSensitive
|
|
4691
|
+
)
|
|
4692
|
+
) {
|
|
4693
|
+
return false;
|
|
4694
|
+
}
|
|
4695
|
+
|
|
4696
|
+
// Pathless layout routes are trickier since we need to check children.
|
|
4697
|
+
// If they have no children then they're the same as far as we can tell
|
|
4698
|
+
if (
|
|
4699
|
+
(!newRoute.children || newRoute.children.length === 0) &&
|
|
4700
|
+
(!existingRoute.children || existingRoute.children.length === 0)
|
|
4701
|
+
) {
|
|
4702
|
+
return true;
|
|
4703
|
+
}
|
|
4704
|
+
|
|
4705
|
+
// Otherwise, we look to see if every child in the new route is already
|
|
4706
|
+
// represented in the existing route's children
|
|
4707
|
+
return newRoute.children!.every((aChild, i) =>
|
|
4708
|
+
existingRoute.children?.some((bChild) => isSameRoute(aChild, bChild))
|
|
4709
|
+
);
|
|
4710
|
+
}
|
|
4711
|
+
|
|
4684
4712
|
/**
|
|
4685
4713
|
* Execute route.lazy() methods to lazily load route modules (loader, action,
|
|
4686
4714
|
* shouldRevalidate) and update the routeManifest in place which shares objects
|
|
@@ -5302,7 +5330,6 @@ function processRouteLoaderData(
|
|
|
5302
5330
|
function processLoaderData(
|
|
5303
5331
|
state: RouterState,
|
|
5304
5332
|
matches: AgnosticDataRouteMatch[],
|
|
5305
|
-
matchesToLoad: AgnosticDataRouteMatch[],
|
|
5306
5333
|
results: Record<string, DataResult>,
|
|
5307
5334
|
pendingActionResult: PendingActionResult | undefined,
|
|
5308
5335
|
revalidatingFetchers: RevalidatingFetcher[],
|