@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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v0.0.0-experimental-bcda00aaf
2
+ * @remix-run/router v0.0.0-experimental-3be88c6fb
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1797,25 +1797,12 @@ function createRouter(init) {
1797
1797
  // were marked for explicit hydration
1798
1798
  let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
1799
1799
  let errors = init.hydrationData ? init.hydrationData.errors : null;
1800
- let isRouteInitialized = m => {
1801
- // No loader, nothing to initialize
1802
- if (!m.route.loader) {
1803
- return true;
1804
- }
1805
- // Explicitly opting-in to running on hydration
1806
- if (typeof m.route.loader === "function" && m.route.loader.hydrate === true) {
1807
- return false;
1808
- }
1809
- // Otherwise, initialized if hydrated with data or an error
1810
- return loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined;
1811
- };
1812
-
1813
1800
  // If errors exist, don't consider routes below the boundary
1814
1801
  if (errors) {
1815
1802
  let idx = initialMatches.findIndex(m => errors[m.route.id] !== undefined);
1816
- initialized = initialMatches.slice(0, idx + 1).every(isRouteInitialized);
1803
+ initialized = initialMatches.slice(0, idx + 1).every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
1817
1804
  } else {
1818
- initialized = initialMatches.every(isRouteInitialized);
1805
+ initialized = initialMatches.every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
1819
1806
  }
1820
1807
  } else {
1821
1808
  // Without partial hydration - we're initialized if we were provided any
@@ -1915,10 +1902,6 @@ function createRouter(init) {
1915
1902
  // we don't need to update UI state if they change
1916
1903
  let blockerFunctions = new Map();
1917
1904
 
1918
- // Map of pending patchRoutesOnNavigation() promises (keyed by path/matches) so
1919
- // that we only kick them off once for a given combo
1920
- let pendingPatchRoutes = new Map();
1921
-
1922
1905
  // Flag to ignore the next history update, so we can revert the URL change on
1923
1906
  // a POP navigation that was blocked by the user without touching router state
1924
1907
  let unblockBlockerHistoryUpdate = undefined;
@@ -2314,7 +2297,7 @@ function createRouter(init) {
2314
2297
  pendingViewTransitionEnabled = (opts && opts.enableViewTransition) === true;
2315
2298
  let routesToUse = inFlightDataRoutes || dataRoutes;
2316
2299
  let loadingNavigation = opts && opts.overrideNavigation;
2317
- let matches = isUninterruptedRevalidation && inFlightDataRoutes == null ? state.matches : matchRoutes(routesToUse, location, basename);
2300
+ let matches = matchRoutes(routesToUse, location, basename);
2318
2301
  let flushSync = (opts && opts.flushSync) === true;
2319
2302
  let fogOfWar = checkFogOfWar(matches, routesToUse, location.pathname);
2320
2303
  if (fogOfWar.active && fogOfWar.matches) {
@@ -2343,7 +2326,7 @@ function createRouter(init) {
2343
2326
  // Short circuit if it's only a hash change and not a revalidation or
2344
2327
  // mutation submission.
2345
2328
  //
2346
- // Ignore on initial page loads because since the initial load will always
2329
+ // Ignore on initial page loads because since the initial hydration will always
2347
2330
  // be "same hash". For example, on /page#hash and submit a <Form method="post">
2348
2331
  // which will default to a navigation to /page
2349
2332
  if (state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
@@ -2662,9 +2645,7 @@ function createRouter(init) {
2662
2645
  });
2663
2646
  }
2664
2647
  revalidatingFetchers.forEach(rf => {
2665
- if (fetchControllers.has(rf.key)) {
2666
- abortFetcher(rf.key);
2667
- }
2648
+ abortFetcher(rf.key);
2668
2649
  if (rf.controller) {
2669
2650
  // Fetchers use an independent AbortController so that aborting a fetcher
2670
2651
  // (via deleteFetcher) does not abort the triggering navigation that
@@ -2724,7 +2705,7 @@ function createRouter(init) {
2724
2705
  let {
2725
2706
  loaderData,
2726
2707
  errors
2727
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
2708
+ } = processLoaderData(state, matches, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
2728
2709
 
2729
2710
  // Wire up subscribers to update loaderData as promises settle
2730
2711
  activeDeferreds.forEach((deferredData, routeId) => {
@@ -2738,17 +2719,9 @@ function createRouter(init) {
2738
2719
  });
2739
2720
  });
2740
2721
 
2741
- // During partial hydration, preserve SSR errors for routes that don't re-run
2722
+ // Preserve SSR errors during partial hydration
2742
2723
  if (future.v7_partialHydration && initialHydration && state.errors) {
2743
- Object.entries(state.errors).filter(_ref2 => {
2744
- let [id] = _ref2;
2745
- return !matchesToLoad.some(m => m.route.id === id);
2746
- }).forEach(_ref3 => {
2747
- let [routeId, error] = _ref3;
2748
- errors = Object.assign(errors || {}, {
2749
- [routeId]: error
2750
- });
2751
- });
2724
+ errors = _extends({}, state.errors, errors);
2752
2725
  }
2753
2726
  let updatedFetchers = markFetchRedirectsDone();
2754
2727
  let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
@@ -2791,7 +2764,7 @@ function createRouter(init) {
2791
2764
  if (isServer) {
2792
2765
  throw new Error("router.fetch() was called during the server render, but it shouldn't be. " + "You are likely calling a useFetcher() method in the body of your component. " + "Try moving it to a useEffect or a callback.");
2793
2766
  }
2794
- if (fetchControllers.has(key)) abortFetcher(key);
2767
+ abortFetcher(key);
2795
2768
  let flushSync = (opts && opts.flushSync) === true;
2796
2769
  let routesToUse = inFlightDataRoutes || dataRoutes;
2797
2770
  let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, future.v7_relativeSplatPath, routeId, opts == null ? void 0 : opts.relative);
@@ -2820,9 +2793,9 @@ function createRouter(init) {
2820
2793
  return;
2821
2794
  }
2822
2795
  let match = getTargetMatch(matches, path);
2823
- pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2796
+ let preventScrollReset = (opts && opts.preventScrollReset) === true;
2824
2797
  if (submission && isMutationMethod(submission.formMethod)) {
2825
- handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2798
+ handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
2826
2799
  return;
2827
2800
  }
2828
2801
 
@@ -2832,12 +2805,12 @@ function createRouter(init) {
2832
2805
  routeId,
2833
2806
  path
2834
2807
  });
2835
- handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2808
+ handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
2836
2809
  }
2837
2810
 
2838
2811
  // Call the action for the matched fetcher.submit(), and then handle redirects,
2839
2812
  // errors, and revalidation
2840
- async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, submission) {
2813
+ async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, preventScrollReset, submission) {
2841
2814
  interruptActiveLoads();
2842
2815
  fetchLoadMatches.delete(key);
2843
2816
  function detectAndHandle405Error(m) {
@@ -2930,7 +2903,8 @@ function createRouter(init) {
2930
2903
  fetchRedirectIds.add(key);
2931
2904
  updateFetcherState(key, getLoadingFetcher(submission));
2932
2905
  return startRedirectNavigation(fetchRequest, actionResult, false, {
2933
- fetcherSubmission: submission
2906
+ fetcherSubmission: submission,
2907
+ preventScrollReset
2934
2908
  });
2935
2909
  }
2936
2910
  }
@@ -2968,9 +2942,7 @@ function createRouter(init) {
2968
2942
  let existingFetcher = state.fetchers.get(staleKey);
2969
2943
  let revalidatingFetcher = getLoadingFetcher(undefined, existingFetcher ? existingFetcher.data : undefined);
2970
2944
  state.fetchers.set(staleKey, revalidatingFetcher);
2971
- if (fetchControllers.has(staleKey)) {
2972
- abortFetcher(staleKey);
2973
- }
2945
+ abortFetcher(staleKey);
2974
2946
  if (rf.controller) {
2975
2947
  fetchControllers.set(staleKey, rf.controller);
2976
2948
  }
@@ -2993,7 +2965,9 @@ function createRouter(init) {
2993
2965
  revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
2994
2966
  let redirect = findRedirect(loaderResults);
2995
2967
  if (redirect) {
2996
- return startRedirectNavigation(revalidationRequest, redirect.result, false);
2968
+ return startRedirectNavigation(revalidationRequest, redirect.result, false, {
2969
+ preventScrollReset
2970
+ });
2997
2971
  }
2998
2972
  redirect = findRedirect(fetcherResults);
2999
2973
  if (redirect) {
@@ -3001,14 +2975,16 @@ function createRouter(init) {
3001
2975
  // fetchRedirectIds so it doesn't get revalidated on the next set of
3002
2976
  // loader executions
3003
2977
  fetchRedirectIds.add(redirect.key);
3004
- return startRedirectNavigation(revalidationRequest, redirect.result, false);
2978
+ return startRedirectNavigation(revalidationRequest, redirect.result, false, {
2979
+ preventScrollReset
2980
+ });
3005
2981
  }
3006
2982
 
3007
2983
  // Process and commit output from loaders
3008
2984
  let {
3009
2985
  loaderData,
3010
2986
  errors
3011
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
2987
+ } = processLoaderData(state, matches, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
3012
2988
 
3013
2989
  // Since we let revalidations complete even if the submitting fetcher was
3014
2990
  // deleted, only put it back to idle if it hasn't been deleted
@@ -3044,7 +3020,7 @@ function createRouter(init) {
3044
3020
  }
3045
3021
 
3046
3022
  // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
3047
- async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, submission) {
3023
+ async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, preventScrollReset, submission) {
3048
3024
  let existingFetcher = state.fetchers.get(key);
3049
3025
  updateFetcherState(key, getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined), {
3050
3026
  flushSync
@@ -3115,7 +3091,9 @@ function createRouter(init) {
3115
3091
  return;
3116
3092
  } else {
3117
3093
  fetchRedirectIds.add(key);
3118
- await startRedirectNavigation(fetchRequest, result, false);
3094
+ await startRedirectNavigation(fetchRequest, result, false, {
3095
+ preventScrollReset
3096
+ });
3119
3097
  return;
3120
3098
  }
3121
3099
  }
@@ -3154,6 +3132,7 @@ function createRouter(init) {
3154
3132
  let {
3155
3133
  submission,
3156
3134
  fetcherSubmission,
3135
+ preventScrollReset,
3157
3136
  replace
3158
3137
  } = _temp2 === void 0 ? {} : _temp2;
3159
3138
  if (redirect.response.headers.has("X-Remix-Revalidate")) {
@@ -3214,7 +3193,7 @@ function createRouter(init) {
3214
3193
  formAction: location
3215
3194
  }),
3216
3195
  // Preserve these flags across redirects
3217
- preventScrollReset: pendingPreventScrollReset,
3196
+ preventScrollReset: preventScrollReset || pendingPreventScrollReset,
3218
3197
  enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
3219
3198
  });
3220
3199
  } else {
@@ -3226,7 +3205,7 @@ function createRouter(init) {
3226
3205
  // Send fetcher submissions through for shouldRevalidate
3227
3206
  fetcherSubmission,
3228
3207
  // Preserve these flags across redirects
3229
- preventScrollReset: pendingPreventScrollReset,
3208
+ preventScrollReset: preventScrollReset || pendingPreventScrollReset,
3230
3209
  enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
3231
3210
  });
3232
3211
  }
@@ -3307,8 +3286,8 @@ function createRouter(init) {
3307
3286
  fetchLoadMatches.forEach((_, key) => {
3308
3287
  if (fetchControllers.has(key)) {
3309
3288
  cancelledFetcherLoads.add(key);
3310
- abortFetcher(key);
3311
3289
  }
3290
+ abortFetcher(key);
3312
3291
  });
3313
3292
  }
3314
3293
  function updateFetcherState(key, fetcher, opts) {
@@ -3381,9 +3360,10 @@ function createRouter(init) {
3381
3360
  }
3382
3361
  function abortFetcher(key) {
3383
3362
  let controller = fetchControllers.get(key);
3384
- invariant(controller, "Expected fetch controller: " + key);
3385
- controller.abort();
3386
- fetchControllers.delete(key);
3363
+ if (controller) {
3364
+ controller.abort();
3365
+ fetchControllers.delete(key);
3366
+ }
3387
3367
  }
3388
3368
  function markFetchersDone(keys) {
3389
3369
  for (let key of keys) {
@@ -3448,12 +3428,12 @@ function createRouter(init) {
3448
3428
  blockers
3449
3429
  });
3450
3430
  }
3451
- function shouldBlockNavigation(_ref4) {
3431
+ function shouldBlockNavigation(_ref2) {
3452
3432
  let {
3453
3433
  currentLocation,
3454
3434
  nextLocation,
3455
3435
  historyAction
3456
- } = _ref4;
3436
+ } = _ref2;
3457
3437
  if (blockerFunctions.size === 0) {
3458
3438
  return;
3459
3439
  }
@@ -3600,12 +3580,26 @@ function createRouter(init) {
3600
3580
  };
3601
3581
  }
3602
3582
  async function discoverRoutes(matches, pathname, signal) {
3583
+ if (!patchRoutesOnNavigationImpl) {
3584
+ return {
3585
+ type: "success",
3586
+ matches
3587
+ };
3588
+ }
3603
3589
  let partialMatches = matches;
3604
3590
  while (true) {
3605
3591
  let isNonHMR = inFlightDataRoutes == null;
3606
3592
  let routesToUse = inFlightDataRoutes || dataRoutes;
3593
+ let localManifest = manifest;
3607
3594
  try {
3608
- await loadLazyRouteChildren(patchRoutesOnNavigationImpl, pathname, partialMatches, routesToUse, manifest, mapRouteProperties, pendingPatchRoutes, signal);
3595
+ await patchRoutesOnNavigationImpl({
3596
+ path: pathname,
3597
+ matches: partialMatches,
3598
+ patch: (routeId, children) => {
3599
+ if (signal.aborted) return;
3600
+ patchRoutesImpl(routeId, children, routesToUse, localManifest, mapRouteProperties);
3601
+ }
3602
+ });
3609
3603
  } catch (e) {
3610
3604
  return {
3611
3605
  type: "error",
@@ -3619,7 +3613,7 @@ function createRouter(init) {
3619
3613
  // trigger a re-run of memoized `router.routes` dependencies.
3620
3614
  // HMR will already update the identity and reflow when it lands
3621
3615
  // `inFlightDataRoutes` in `completeNavigation`
3622
- if (isNonHMR) {
3616
+ if (isNonHMR && !signal.aborted) {
3623
3617
  dataRoutes = [...dataRoutes];
3624
3618
  }
3625
3619
  }
@@ -4198,9 +4192,21 @@ function normalizeTo(location, matches, basename, prependBasename, to, v7_relati
4198
4192
  path.hash = location.hash;
4199
4193
  }
4200
4194
 
4201
- // Add an ?index param for matched index routes if we don't already have one
4202
- if ((to == null || to === "" || to === ".") && activeRouteMatch && activeRouteMatch.route.index && !hasNakedIndexQuery(path.search)) {
4203
- path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
4195
+ // Account for `?index` params when routing to the current location
4196
+ if ((to == null || to === "" || to === ".") && activeRouteMatch) {
4197
+ let nakedIndex = hasNakedIndexQuery(path.search);
4198
+ if (activeRouteMatch.route.index && !nakedIndex) {
4199
+ // Add one when we're targeting an index route
4200
+ path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
4201
+ } else if (!activeRouteMatch.route.index && nakedIndex) {
4202
+ // Remove existing ones when we're not
4203
+ let params = new URLSearchParams(path.search);
4204
+ let indexValues = params.getAll("index");
4205
+ params.delete("index");
4206
+ indexValues.filter(v => v).forEach(v => params.append("index", v));
4207
+ let qs = params.toString();
4208
+ path.search = qs ? "?" + qs : "";
4209
+ }
4204
4210
  }
4205
4211
 
4206
4212
  // If we're operating within a basename, prepend it to the pathname. If
@@ -4249,8 +4255,8 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
4249
4255
  }
4250
4256
  let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?
4251
4257
  // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data
4252
- Array.from(opts.body.entries()).reduce((acc, _ref5) => {
4253
- let [name, value] = _ref5;
4258
+ Array.from(opts.body.entries()).reduce((acc, _ref3) => {
4259
+ let [name, value] = _ref3;
4254
4260
  return "" + acc + name + "=" + value + "\n";
4255
4261
  }, "") : String(opts.body);
4256
4262
  return {
@@ -4340,26 +4346,37 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
4340
4346
  };
4341
4347
  }
4342
4348
 
4343
- // Filter out all routes below any caught error as they aren't going to
4349
+ // Filter out all routes at/below any caught error as they aren't going to
4344
4350
  // render so we don't need to load them
4345
- function getLoaderMatchesUntilBoundary(matches, boundaryId) {
4346
- let boundaryMatches = matches;
4347
- if (boundaryId) {
4348
- let index = matches.findIndex(m => m.route.id === boundaryId);
4349
- if (index >= 0) {
4350
- boundaryMatches = matches.slice(0, index);
4351
- }
4351
+ function getLoaderMatchesUntilBoundary(matches, boundaryId, includeBoundary) {
4352
+ if (includeBoundary === void 0) {
4353
+ includeBoundary = false;
4352
4354
  }
4353
- return boundaryMatches;
4355
+ let index = matches.findIndex(m => m.route.id === boundaryId);
4356
+ if (index >= 0) {
4357
+ return matches.slice(0, includeBoundary ? index + 1 : index);
4358
+ }
4359
+ return matches;
4354
4360
  }
4355
- function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
4361
+ function getMatchesToLoad(history, state, matches, submission, location, initialHydration, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
4356
4362
  let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
4357
4363
  let currentUrl = history.createURL(state.location);
4358
4364
  let nextUrl = history.createURL(location);
4359
4365
 
4360
4366
  // Pick navigation matches that are net-new or qualify for revalidation
4361
- let boundaryId = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[0] : undefined;
4362
- let boundaryMatches = boundaryId ? getLoaderMatchesUntilBoundary(matches, boundaryId) : matches;
4367
+ let boundaryMatches = matches;
4368
+ if (initialHydration && state.errors) {
4369
+ // On initial hydration, only consider matches up to _and including_ the boundary.
4370
+ // This is inclusive to handle cases where a server loader ran successfully,
4371
+ // a child server loader bubbled up to this route, but this route has
4372
+ // `clientLoader.hydrate` so we want to still run the `clientLoader` so that
4373
+ // we have a complete version of `loaderData`
4374
+ boundaryMatches = getLoaderMatchesUntilBoundary(matches, Object.keys(state.errors)[0], true);
4375
+ } else if (pendingActionResult && isErrorResult(pendingActionResult[1])) {
4376
+ // If an action threw an error, we call loaders up to, but not including the
4377
+ // boundary
4378
+ boundaryMatches = getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]);
4379
+ }
4363
4380
 
4364
4381
  // Don't revalidate loaders by default after action 4xx/5xx responses
4365
4382
  // when the flag is enabled. They can still opt-into revalidation via
@@ -4377,13 +4394,8 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
4377
4394
  if (route.loader == null) {
4378
4395
  return false;
4379
4396
  }
4380
- if (isInitialLoad) {
4381
- if (typeof route.loader !== "function" || route.loader.hydrate) {
4382
- return true;
4383
- }
4384
- return state.loaderData[route.id] === undefined && (
4385
- // Don't re-run if the loader ran and threw an error
4386
- !state.errors || state.errors[route.id] === undefined);
4397
+ if (initialHydration) {
4398
+ return shouldLoadRouteOnHydration(route, state.loaderData, state.errors);
4387
4399
  }
4388
4400
 
4389
4401
  // Always call the loader on new route instances and pending defer cancellations
@@ -4417,11 +4429,11 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
4417
4429
  let revalidatingFetchers = [];
4418
4430
  fetchLoadMatches.forEach((f, key) => {
4419
4431
  // Don't revalidate:
4420
- // - on initial load (shouldn't be any fetchers then anyway)
4432
+ // - on initial hydration (shouldn't be any fetchers then anyway)
4421
4433
  // - if fetcher won't be present in the subsequent render
4422
4434
  // - no longer matches the URL (v7_fetcherPersist=false)
4423
4435
  // - was unmounted but persisted due to v7_fetcherPersist=true
4424
- if (isInitialLoad || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
4436
+ if (initialHydration || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
4425
4437
  return;
4426
4438
  }
4427
4439
  let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
@@ -4487,6 +4499,32 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
4487
4499
  });
4488
4500
  return [navigationMatches, revalidatingFetchers];
4489
4501
  }
4502
+ function shouldLoadRouteOnHydration(route, loaderData, errors) {
4503
+ // We dunno if we have a loader - gotta find out!
4504
+ if (route.lazy) {
4505
+ return true;
4506
+ }
4507
+
4508
+ // No loader, nothing to initialize
4509
+ if (!route.loader) {
4510
+ return false;
4511
+ }
4512
+ let hasData = loaderData != null && loaderData[route.id] !== undefined;
4513
+ let hasError = errors != null && errors[route.id] !== undefined;
4514
+
4515
+ // Don't run if we error'd during SSR
4516
+ if (!hasData && hasError) {
4517
+ return false;
4518
+ }
4519
+
4520
+ // Explicitly opting-in to running on hydration
4521
+ if (typeof route.loader === "function" && route.loader.hydrate === true) {
4522
+ return true;
4523
+ }
4524
+
4525
+ // Otherwise, run if we're not yet initialized with anything
4526
+ return !hasData && !hasError;
4527
+ }
4490
4528
  function isNewLoader(currentLoaderData, currentMatch, match) {
4491
4529
  let isNew =
4492
4530
  // [a] -> [a, b]
@@ -4520,34 +4558,6 @@ function shouldRevalidateLoader(loaderMatch, arg) {
4520
4558
  }
4521
4559
  return arg.defaultShouldRevalidate;
4522
4560
  }
4523
-
4524
- /**
4525
- * Idempotent utility to execute patchRoutesOnNavigation() to lazily load route
4526
- * definitions and update the routes/routeManifest
4527
- */
4528
- async function loadLazyRouteChildren(patchRoutesOnNavigationImpl, path, matches, routes, manifest, mapRouteProperties, pendingRouteChildren, signal) {
4529
- let key = [path, ...matches.map(m => m.route.id)].join("-");
4530
- try {
4531
- let pending = pendingRouteChildren.get(key);
4532
- if (!pending) {
4533
- pending = patchRoutesOnNavigationImpl({
4534
- path,
4535
- matches,
4536
- patch: (routeId, children) => {
4537
- if (!signal.aborted) {
4538
- patchRoutesImpl(routeId, children, routes, manifest, mapRouteProperties);
4539
- }
4540
- }
4541
- });
4542
- pendingRouteChildren.set(key, pending);
4543
- }
4544
- if (pending && isPromise(pending)) {
4545
- await pending;
4546
- }
4547
- } finally {
4548
- pendingRouteChildren.delete(key);
4549
- }
4550
- }
4551
4561
  function patchRoutesImpl(routeId, children, routesToUse, manifest, mapRouteProperties) {
4552
4562
  var _childrenToPatch;
4553
4563
  let childrenToPatch;
@@ -4565,10 +4575,34 @@ function patchRoutesImpl(routeId, children, routesToUse, manifest, mapRoutePrope
4565
4575
  // Don't patch in routes we already know about so that `patch` is idempotent
4566
4576
  // to simplify user-land code. This is useful because we re-call the
4567
4577
  // `patchRoutesOnNavigation` function for matched routes with params.
4568
- let uniqueChildren = children.filter(a => !childrenToPatch.some(b => a.index === b.index && a.path === b.path && a.caseSensitive === b.caseSensitive));
4578
+ let uniqueChildren = children.filter(newRoute => !childrenToPatch.some(existingRoute => isSameRoute(newRoute, existingRoute)));
4569
4579
  let newRoutes = convertRoutesToDataRoutes(uniqueChildren, mapRouteProperties, [routeId || "_", "patch", String(((_childrenToPatch = childrenToPatch) == null ? void 0 : _childrenToPatch.length) || "0")], manifest);
4570
4580
  childrenToPatch.push(...newRoutes);
4571
4581
  }
4582
+ function isSameRoute(newRoute, existingRoute) {
4583
+ // Most optimal check is by id
4584
+ if ("id" in newRoute && "id" in existingRoute && newRoute.id === existingRoute.id) {
4585
+ return true;
4586
+ }
4587
+
4588
+ // Second is by pathing differences
4589
+ if (!(newRoute.index === existingRoute.index && newRoute.path === existingRoute.path && newRoute.caseSensitive === existingRoute.caseSensitive)) {
4590
+ return false;
4591
+ }
4592
+
4593
+ // Pathless layout routes are trickier since we need to check children.
4594
+ // If they have no children then they're the same as far as we can tell
4595
+ if ((!newRoute.children || newRoute.children.length === 0) && (!existingRoute.children || existingRoute.children.length === 0)) {
4596
+ return true;
4597
+ }
4598
+
4599
+ // Otherwise, we look to see if every child in the new route is already
4600
+ // represented in the existing route's children
4601
+ return newRoute.children.every((aChild, i) => {
4602
+ var _existingRoute$childr;
4603
+ return (_existingRoute$childr = existingRoute.children) == null ? void 0 : _existingRoute$childr.some(bChild => isSameRoute(aChild, bChild));
4604
+ });
4605
+ }
4572
4606
 
4573
4607
  /**
4574
4608
  * Execute route.lazy() methods to lazily load route modules (loader, action,
@@ -4624,10 +4658,10 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
4624
4658
  }
4625
4659
 
4626
4660
  // Default implementation of `dataStrategy` which fetches all loaders in parallel
4627
- async function defaultDataStrategy(_ref6) {
4661
+ async function defaultDataStrategy(_ref4) {
4628
4662
  let {
4629
4663
  matches
4630
- } = _ref6;
4664
+ } = _ref4;
4631
4665
  let matchesToLoad = matches.filter(m => m.shouldLoad);
4632
4666
  let results = await Promise.all(matchesToLoad.map(m => m.resolve()));
4633
4667
  return results.reduce((acc, result, i) => Object.assign(acc, {
@@ -5041,7 +5075,7 @@ function processRouteLoaderData(matches, results, pendingActionResult, activeDef
5041
5075
  loaderHeaders
5042
5076
  };
5043
5077
  }
5044
- function processLoaderData(state, matches, matchesToLoad, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
5078
+ function processLoaderData(state, matches, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
5045
5079
  let {
5046
5080
  loaderData,
5047
5081
  errors
@@ -5220,9 +5254,6 @@ function isHashChangeOnly(a, b) {
5220
5254
  // /page#hash -> /page
5221
5255
  return false;
5222
5256
  }
5223
- function isPromise(val) {
5224
- return typeof val === "object" && val != null && "then" in val;
5225
- }
5226
5257
  function isDataStrategyResult(result) {
5227
5258
  return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
5228
5259
  }