@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.js CHANGED
@@ -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
  *
@@ -1445,24 +1445,12 @@ function createRouter(init) {
1445
1445
  // were marked for explicit hydration
1446
1446
  let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
1447
1447
  let errors = init.hydrationData ? init.hydrationData.errors : null;
1448
- let isRouteInitialized = m => {
1449
- // No loader, nothing to initialize
1450
- if (!m.route.loader) {
1451
- return true;
1452
- }
1453
- // Explicitly opting-in to running on hydration
1454
- if (typeof m.route.loader === "function" && m.route.loader.hydrate === true) {
1455
- return false;
1456
- }
1457
- // Otherwise, initialized if hydrated with data or an error
1458
- return loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined;
1459
- };
1460
1448
  // If errors exist, don't consider routes below the boundary
1461
1449
  if (errors) {
1462
1450
  let idx = initialMatches.findIndex(m => errors[m.route.id] !== undefined);
1463
- initialized = initialMatches.slice(0, idx + 1).every(isRouteInitialized);
1451
+ initialized = initialMatches.slice(0, idx + 1).every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
1464
1452
  } else {
1465
- initialized = initialMatches.every(isRouteInitialized);
1453
+ initialized = initialMatches.every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
1466
1454
  }
1467
1455
  } else {
1468
1456
  // Without partial hydration - we're initialized if we were provided any
@@ -1541,9 +1529,6 @@ function createRouter(init) {
1541
1529
  // Store blocker functions in a separate Map outside of router state since
1542
1530
  // we don't need to update UI state if they change
1543
1531
  let blockerFunctions = new Map();
1544
- // Map of pending patchRoutesOnNavigation() promises (keyed by path/matches) so
1545
- // that we only kick them off once for a given combo
1546
- let pendingPatchRoutes = new Map();
1547
1532
  // Flag to ignore the next history update, so we can revert the URL change on
1548
1533
  // a POP navigation that was blocked by the user without touching router state
1549
1534
  let unblockBlockerHistoryUpdate = undefined;
@@ -1914,7 +1899,7 @@ function createRouter(init) {
1914
1899
  pendingViewTransitionEnabled = (opts && opts.enableViewTransition) === true;
1915
1900
  let routesToUse = inFlightDataRoutes || dataRoutes;
1916
1901
  let loadingNavigation = opts && opts.overrideNavigation;
1917
- let matches = isUninterruptedRevalidation && inFlightDataRoutes == null ? state.matches : matchRoutes(routesToUse, location, basename);
1902
+ let matches = matchRoutes(routesToUse, location, basename);
1918
1903
  let flushSync = (opts && opts.flushSync) === true;
1919
1904
  let fogOfWar = checkFogOfWar(matches, routesToUse, location.pathname);
1920
1905
  if (fogOfWar.active && fogOfWar.matches) {
@@ -1941,7 +1926,7 @@ function createRouter(init) {
1941
1926
  // Short circuit if it's only a hash change and not a revalidation or
1942
1927
  // mutation submission.
1943
1928
  //
1944
- // Ignore on initial page loads because since the initial load will always
1929
+ // Ignore on initial page loads because since the initial hydration will always
1945
1930
  // be "same hash". For example, on /page#hash and submit a <Form method="post">
1946
1931
  // which will default to a navigation to /page
1947
1932
  if (state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
@@ -2245,9 +2230,7 @@ function createRouter(init) {
2245
2230
  });
2246
2231
  }
2247
2232
  revalidatingFetchers.forEach(rf => {
2248
- if (fetchControllers.has(rf.key)) {
2249
- abortFetcher(rf.key);
2250
- }
2233
+ abortFetcher(rf.key);
2251
2234
  if (rf.controller) {
2252
2235
  // Fetchers use an independent AbortController so that aborting a fetcher
2253
2236
  // (via deleteFetcher) does not abort the triggering navigation that
@@ -2303,7 +2286,7 @@ function createRouter(init) {
2303
2286
  let {
2304
2287
  loaderData,
2305
2288
  errors
2306
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
2289
+ } = processLoaderData(state, matches, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
2307
2290
  // Wire up subscribers to update loaderData as promises settle
2308
2291
  activeDeferreds.forEach((deferredData, routeId) => {
2309
2292
  deferredData.subscribe(aborted => {
@@ -2315,17 +2298,9 @@ function createRouter(init) {
2315
2298
  }
2316
2299
  });
2317
2300
  });
2318
- // During partial hydration, preserve SSR errors for routes that don't re-run
2301
+ // Preserve SSR errors during partial hydration
2319
2302
  if (future.v7_partialHydration && initialHydration && state.errors) {
2320
- Object.entries(state.errors).filter(_ref2 => {
2321
- let [id] = _ref2;
2322
- return !matchesToLoad.some(m => m.route.id === id);
2323
- }).forEach(_ref3 => {
2324
- let [routeId, error] = _ref3;
2325
- errors = Object.assign(errors || {}, {
2326
- [routeId]: error
2327
- });
2328
- });
2303
+ errors = _extends({}, state.errors, errors);
2329
2304
  }
2330
2305
  let updatedFetchers = markFetchRedirectsDone();
2331
2306
  let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
@@ -2367,7 +2342,7 @@ function createRouter(init) {
2367
2342
  if (isServer) {
2368
2343
  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.");
2369
2344
  }
2370
- if (fetchControllers.has(key)) abortFetcher(key);
2345
+ abortFetcher(key);
2371
2346
  let flushSync = (opts && opts.flushSync) === true;
2372
2347
  let routesToUse = inFlightDataRoutes || dataRoutes;
2373
2348
  let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, future.v7_relativeSplatPath, routeId, opts == null ? void 0 : opts.relative);
@@ -2396,9 +2371,9 @@ function createRouter(init) {
2396
2371
  return;
2397
2372
  }
2398
2373
  let match = getTargetMatch(matches, path);
2399
- pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2374
+ let preventScrollReset = (opts && opts.preventScrollReset) === true;
2400
2375
  if (submission && isMutationMethod(submission.formMethod)) {
2401
- handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2376
+ handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
2402
2377
  return;
2403
2378
  }
2404
2379
  // Store off the match so we can call it's shouldRevalidate on subsequent
@@ -2407,11 +2382,11 @@ function createRouter(init) {
2407
2382
  routeId,
2408
2383
  path
2409
2384
  });
2410
- handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2385
+ handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
2411
2386
  }
2412
2387
  // Call the action for the matched fetcher.submit(), and then handle redirects,
2413
2388
  // errors, and revalidation
2414
- async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, submission) {
2389
+ async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, preventScrollReset, submission) {
2415
2390
  interruptActiveLoads();
2416
2391
  fetchLoadMatches.delete(key);
2417
2392
  function detectAndHandle405Error(m) {
@@ -2501,7 +2476,8 @@ function createRouter(init) {
2501
2476
  fetchRedirectIds.add(key);
2502
2477
  updateFetcherState(key, getLoadingFetcher(submission));
2503
2478
  return startRedirectNavigation(fetchRequest, actionResult, false, {
2504
- fetcherSubmission: submission
2479
+ fetcherSubmission: submission,
2480
+ preventScrollReset
2505
2481
  });
2506
2482
  }
2507
2483
  }
@@ -2536,9 +2512,7 @@ function createRouter(init) {
2536
2512
  let existingFetcher = state.fetchers.get(staleKey);
2537
2513
  let revalidatingFetcher = getLoadingFetcher(undefined, existingFetcher ? existingFetcher.data : undefined);
2538
2514
  state.fetchers.set(staleKey, revalidatingFetcher);
2539
- if (fetchControllers.has(staleKey)) {
2540
- abortFetcher(staleKey);
2541
- }
2515
+ abortFetcher(staleKey);
2542
2516
  if (rf.controller) {
2543
2517
  fetchControllers.set(staleKey, rf.controller);
2544
2518
  }
@@ -2561,7 +2535,9 @@ function createRouter(init) {
2561
2535
  revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
2562
2536
  let redirect = findRedirect(loaderResults);
2563
2537
  if (redirect) {
2564
- return startRedirectNavigation(revalidationRequest, redirect.result, false);
2538
+ return startRedirectNavigation(revalidationRequest, redirect.result, false, {
2539
+ preventScrollReset
2540
+ });
2565
2541
  }
2566
2542
  redirect = findRedirect(fetcherResults);
2567
2543
  if (redirect) {
@@ -2569,13 +2545,15 @@ function createRouter(init) {
2569
2545
  // fetchRedirectIds so it doesn't get revalidated on the next set of
2570
2546
  // loader executions
2571
2547
  fetchRedirectIds.add(redirect.key);
2572
- return startRedirectNavigation(revalidationRequest, redirect.result, false);
2548
+ return startRedirectNavigation(revalidationRequest, redirect.result, false, {
2549
+ preventScrollReset
2550
+ });
2573
2551
  }
2574
2552
  // Process and commit output from loaders
2575
2553
  let {
2576
2554
  loaderData,
2577
2555
  errors
2578
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
2556
+ } = processLoaderData(state, matches, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
2579
2557
  // Since we let revalidations complete even if the submitting fetcher was
2580
2558
  // deleted, only put it back to idle if it hasn't been deleted
2581
2559
  if (state.fetchers.has(key)) {
@@ -2608,7 +2586,7 @@ function createRouter(init) {
2608
2586
  }
2609
2587
  }
2610
2588
  // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
2611
- async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, submission) {
2589
+ async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, preventScrollReset, submission) {
2612
2590
  let existingFetcher = state.fetchers.get(key);
2613
2591
  updateFetcherState(key, getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined), {
2614
2592
  flushSync
@@ -2674,7 +2652,9 @@ function createRouter(init) {
2674
2652
  return;
2675
2653
  } else {
2676
2654
  fetchRedirectIds.add(key);
2677
- await startRedirectNavigation(fetchRequest, result, false);
2655
+ await startRedirectNavigation(fetchRequest, result, false, {
2656
+ preventScrollReset
2657
+ });
2678
2658
  return;
2679
2659
  }
2680
2660
  }
@@ -2710,6 +2690,7 @@ function createRouter(init) {
2710
2690
  let {
2711
2691
  submission,
2712
2692
  fetcherSubmission,
2693
+ preventScrollReset,
2713
2694
  replace
2714
2695
  } = _temp2 === void 0 ? {} : _temp2;
2715
2696
  if (redirect.response.headers.has("X-Remix-Revalidate")) {
@@ -2767,7 +2748,7 @@ function createRouter(init) {
2767
2748
  formAction: location
2768
2749
  }),
2769
2750
  // Preserve these flags across redirects
2770
- preventScrollReset: pendingPreventScrollReset,
2751
+ preventScrollReset: preventScrollReset || pendingPreventScrollReset,
2771
2752
  enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
2772
2753
  });
2773
2754
  } else {
@@ -2779,7 +2760,7 @@ function createRouter(init) {
2779
2760
  // Send fetcher submissions through for shouldRevalidate
2780
2761
  fetcherSubmission,
2781
2762
  // Preserve these flags across redirects
2782
- preventScrollReset: pendingPreventScrollReset,
2763
+ preventScrollReset: preventScrollReset || pendingPreventScrollReset,
2783
2764
  enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
2784
2765
  });
2785
2766
  }
@@ -2856,8 +2837,8 @@ function createRouter(init) {
2856
2837
  fetchLoadMatches.forEach((_, key) => {
2857
2838
  if (fetchControllers.has(key)) {
2858
2839
  cancelledFetcherLoads.add(key);
2859
- abortFetcher(key);
2860
2840
  }
2841
+ abortFetcher(key);
2861
2842
  });
2862
2843
  }
2863
2844
  function updateFetcherState(key, fetcher, opts) {
@@ -2930,9 +2911,10 @@ function createRouter(init) {
2930
2911
  }
2931
2912
  function abortFetcher(key) {
2932
2913
  let controller = fetchControllers.get(key);
2933
- invariant(controller, "Expected fetch controller: " + key);
2934
- controller.abort();
2935
- fetchControllers.delete(key);
2914
+ if (controller) {
2915
+ controller.abort();
2916
+ fetchControllers.delete(key);
2917
+ }
2936
2918
  }
2937
2919
  function markFetchersDone(keys) {
2938
2920
  for (let key of keys) {
@@ -2995,12 +2977,12 @@ function createRouter(init) {
2995
2977
  blockers
2996
2978
  });
2997
2979
  }
2998
- function shouldBlockNavigation(_ref4) {
2980
+ function shouldBlockNavigation(_ref2) {
2999
2981
  let {
3000
2982
  currentLocation,
3001
2983
  nextLocation,
3002
2984
  historyAction
3003
- } = _ref4;
2985
+ } = _ref2;
3004
2986
  if (blockerFunctions.size === 0) {
3005
2987
  return;
3006
2988
  }
@@ -3142,12 +3124,26 @@ function createRouter(init) {
3142
3124
  };
3143
3125
  }
3144
3126
  async function discoverRoutes(matches, pathname, signal) {
3127
+ if (!patchRoutesOnNavigationImpl) {
3128
+ return {
3129
+ type: "success",
3130
+ matches
3131
+ };
3132
+ }
3145
3133
  let partialMatches = matches;
3146
3134
  while (true) {
3147
3135
  let isNonHMR = inFlightDataRoutes == null;
3148
3136
  let routesToUse = inFlightDataRoutes || dataRoutes;
3137
+ let localManifest = manifest;
3149
3138
  try {
3150
- await loadLazyRouteChildren(patchRoutesOnNavigationImpl, pathname, partialMatches, routesToUse, manifest, mapRouteProperties, pendingPatchRoutes, signal);
3139
+ await patchRoutesOnNavigationImpl({
3140
+ path: pathname,
3141
+ matches: partialMatches,
3142
+ patch: (routeId, children) => {
3143
+ if (signal.aborted) return;
3144
+ patchRoutesImpl(routeId, children, routesToUse, localManifest, mapRouteProperties);
3145
+ }
3146
+ });
3151
3147
  } catch (e) {
3152
3148
  return {
3153
3149
  type: "error",
@@ -3161,7 +3157,7 @@ function createRouter(init) {
3161
3157
  // trigger a re-run of memoized `router.routes` dependencies.
3162
3158
  // HMR will already update the identity and reflow when it lands
3163
3159
  // `inFlightDataRoutes` in `completeNavigation`
3164
- if (isNonHMR) {
3160
+ if (isNonHMR && !signal.aborted) {
3165
3161
  dataRoutes = [...dataRoutes];
3166
3162
  }
3167
3163
  }
@@ -3712,9 +3708,21 @@ function normalizeTo(location, matches, basename, prependBasename, to, v7_relati
3712
3708
  path.search = location.search;
3713
3709
  path.hash = location.hash;
3714
3710
  }
3715
- // Add an ?index param for matched index routes if we don't already have one
3716
- if ((to == null || to === "" || to === ".") && activeRouteMatch && activeRouteMatch.route.index && !hasNakedIndexQuery(path.search)) {
3717
- path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
3711
+ // Account for `?index` params when routing to the current location
3712
+ if ((to == null || to === "" || to === ".") && activeRouteMatch) {
3713
+ let nakedIndex = hasNakedIndexQuery(path.search);
3714
+ if (activeRouteMatch.route.index && !nakedIndex) {
3715
+ // Add one when we're targeting an index route
3716
+ path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
3717
+ } else if (!activeRouteMatch.route.index && nakedIndex) {
3718
+ // Remove existing ones when we're not
3719
+ let params = new URLSearchParams(path.search);
3720
+ let indexValues = params.getAll("index");
3721
+ params.delete("index");
3722
+ indexValues.filter(v => v).forEach(v => params.append("index", v));
3723
+ let qs = params.toString();
3724
+ path.search = qs ? "?" + qs : "";
3725
+ }
3718
3726
  }
3719
3727
  // If we're operating within a basename, prepend it to the pathname. If
3720
3728
  // this is a root navigation, then just use the raw basename which allows
@@ -3760,8 +3768,8 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
3760
3768
  }
3761
3769
  let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?
3762
3770
  // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data
3763
- Array.from(opts.body.entries()).reduce((acc, _ref5) => {
3764
- let [name, value] = _ref5;
3771
+ Array.from(opts.body.entries()).reduce((acc, _ref3) => {
3772
+ let [name, value] = _ref3;
3765
3773
  return "" + acc + name + "=" + value + "\n";
3766
3774
  }, "") : String(opts.body);
3767
3775
  return {
@@ -3849,25 +3857,36 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
3849
3857
  submission
3850
3858
  };
3851
3859
  }
3852
- // Filter out all routes below any caught error as they aren't going to
3860
+ // Filter out all routes at/below any caught error as they aren't going to
3853
3861
  // render so we don't need to load them
3854
- function getLoaderMatchesUntilBoundary(matches, boundaryId) {
3855
- let boundaryMatches = matches;
3856
- if (boundaryId) {
3857
- let index = matches.findIndex(m => m.route.id === boundaryId);
3858
- if (index >= 0) {
3859
- boundaryMatches = matches.slice(0, index);
3860
- }
3862
+ function getLoaderMatchesUntilBoundary(matches, boundaryId, includeBoundary) {
3863
+ if (includeBoundary === void 0) {
3864
+ includeBoundary = false;
3861
3865
  }
3862
- return boundaryMatches;
3866
+ let index = matches.findIndex(m => m.route.id === boundaryId);
3867
+ if (index >= 0) {
3868
+ return matches.slice(0, includeBoundary ? index + 1 : index);
3869
+ }
3870
+ return matches;
3863
3871
  }
3864
- function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
3872
+ function getMatchesToLoad(history, state, matches, submission, location, initialHydration, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
3865
3873
  let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
3866
3874
  let currentUrl = history.createURL(state.location);
3867
3875
  let nextUrl = history.createURL(location);
3868
3876
  // Pick navigation matches that are net-new or qualify for revalidation
3869
- let boundaryId = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[0] : undefined;
3870
- let boundaryMatches = boundaryId ? getLoaderMatchesUntilBoundary(matches, boundaryId) : matches;
3877
+ let boundaryMatches = matches;
3878
+ if (initialHydration && state.errors) {
3879
+ // On initial hydration, only consider matches up to _and including_ the boundary.
3880
+ // This is inclusive to handle cases where a server loader ran successfully,
3881
+ // a child server loader bubbled up to this route, but this route has
3882
+ // `clientLoader.hydrate` so we want to still run the `clientLoader` so that
3883
+ // we have a complete version of `loaderData`
3884
+ boundaryMatches = getLoaderMatchesUntilBoundary(matches, Object.keys(state.errors)[0], true);
3885
+ } else if (pendingActionResult && isErrorResult(pendingActionResult[1])) {
3886
+ // If an action threw an error, we call loaders up to, but not including the
3887
+ // boundary
3888
+ boundaryMatches = getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]);
3889
+ }
3871
3890
  // Don't revalidate loaders by default after action 4xx/5xx responses
3872
3891
  // when the flag is enabled. They can still opt-into revalidation via
3873
3892
  // `shouldRevalidate` via `actionResult`
@@ -3884,13 +3903,8 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
3884
3903
  if (route.loader == null) {
3885
3904
  return false;
3886
3905
  }
3887
- if (isInitialLoad) {
3888
- if (typeof route.loader !== "function" || route.loader.hydrate) {
3889
- return true;
3890
- }
3891
- return state.loaderData[route.id] === undefined && (
3892
- // Don't re-run if the loader ran and threw an error
3893
- !state.errors || state.errors[route.id] === undefined);
3906
+ if (initialHydration) {
3907
+ return shouldLoadRouteOnHydration(route, state.loaderData, state.errors);
3894
3908
  }
3895
3909
  // Always call the loader on new route instances and pending defer cancellations
3896
3910
  if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
@@ -3921,11 +3935,11 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
3921
3935
  let revalidatingFetchers = [];
3922
3936
  fetchLoadMatches.forEach((f, key) => {
3923
3937
  // Don't revalidate:
3924
- // - on initial load (shouldn't be any fetchers then anyway)
3938
+ // - on initial hydration (shouldn't be any fetchers then anyway)
3925
3939
  // - if fetcher won't be present in the subsequent render
3926
3940
  // - no longer matches the URL (v7_fetcherPersist=false)
3927
3941
  // - was unmounted but persisted due to v7_fetcherPersist=true
3928
- if (isInitialLoad || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
3942
+ if (initialHydration || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
3929
3943
  return;
3930
3944
  }
3931
3945
  let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
@@ -3989,6 +4003,28 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
3989
4003
  });
3990
4004
  return [navigationMatches, revalidatingFetchers];
3991
4005
  }
4006
+ function shouldLoadRouteOnHydration(route, loaderData, errors) {
4007
+ // We dunno if we have a loader - gotta find out!
4008
+ if (route.lazy) {
4009
+ return true;
4010
+ }
4011
+ // No loader, nothing to initialize
4012
+ if (!route.loader) {
4013
+ return false;
4014
+ }
4015
+ let hasData = loaderData != null && loaderData[route.id] !== undefined;
4016
+ let hasError = errors != null && errors[route.id] !== undefined;
4017
+ // Don't run if we error'd during SSR
4018
+ if (!hasData && hasError) {
4019
+ return false;
4020
+ }
4021
+ // Explicitly opting-in to running on hydration
4022
+ if (typeof route.loader === "function" && route.loader.hydrate === true) {
4023
+ return true;
4024
+ }
4025
+ // Otherwise, run if we're not yet initialized with anything
4026
+ return !hasData && !hasError;
4027
+ }
3992
4028
  function isNewLoader(currentLoaderData, currentMatch, match) {
3993
4029
  let isNew =
3994
4030
  // [a] -> [a, b]
@@ -4020,33 +4056,6 @@ function shouldRevalidateLoader(loaderMatch, arg) {
4020
4056
  }
4021
4057
  return arg.defaultShouldRevalidate;
4022
4058
  }
4023
- /**
4024
- * Idempotent utility to execute patchRoutesOnNavigation() to lazily load route
4025
- * definitions and update the routes/routeManifest
4026
- */
4027
- async function loadLazyRouteChildren(patchRoutesOnNavigationImpl, path, matches, routes, manifest, mapRouteProperties, pendingRouteChildren, signal) {
4028
- let key = [path, ...matches.map(m => m.route.id)].join("-");
4029
- try {
4030
- let pending = pendingRouteChildren.get(key);
4031
- if (!pending) {
4032
- pending = patchRoutesOnNavigationImpl({
4033
- path,
4034
- matches,
4035
- patch: (routeId, children) => {
4036
- if (!signal.aborted) {
4037
- patchRoutesImpl(routeId, children, routes, manifest, mapRouteProperties);
4038
- }
4039
- }
4040
- });
4041
- pendingRouteChildren.set(key, pending);
4042
- }
4043
- if (pending && isPromise(pending)) {
4044
- await pending;
4045
- }
4046
- } finally {
4047
- pendingRouteChildren.delete(key);
4048
- }
4049
- }
4050
4059
  function patchRoutesImpl(routeId, children, routesToUse, manifest, mapRouteProperties) {
4051
4060
  var _childrenToPatch;
4052
4061
  let childrenToPatch;
@@ -4063,10 +4072,31 @@ function patchRoutesImpl(routeId, children, routesToUse, manifest, mapRoutePrope
4063
4072
  // Don't patch in routes we already know about so that `patch` is idempotent
4064
4073
  // to simplify user-land code. This is useful because we re-call the
4065
4074
  // `patchRoutesOnNavigation` function for matched routes with params.
4066
- let uniqueChildren = children.filter(a => !childrenToPatch.some(b => a.index === b.index && a.path === b.path && a.caseSensitive === b.caseSensitive));
4075
+ let uniqueChildren = children.filter(newRoute => !childrenToPatch.some(existingRoute => isSameRoute(newRoute, existingRoute)));
4067
4076
  let newRoutes = convertRoutesToDataRoutes(uniqueChildren, mapRouteProperties, [routeId || "_", "patch", String(((_childrenToPatch = childrenToPatch) == null ? void 0 : _childrenToPatch.length) || "0")], manifest);
4068
4077
  childrenToPatch.push(...newRoutes);
4069
4078
  }
4079
+ function isSameRoute(newRoute, existingRoute) {
4080
+ // Most optimal check is by id
4081
+ if ("id" in newRoute && "id" in existingRoute && newRoute.id === existingRoute.id) {
4082
+ return true;
4083
+ }
4084
+ // Second is by pathing differences
4085
+ if (!(newRoute.index === existingRoute.index && newRoute.path === existingRoute.path && newRoute.caseSensitive === existingRoute.caseSensitive)) {
4086
+ return false;
4087
+ }
4088
+ // Pathless layout routes are trickier since we need to check children.
4089
+ // If they have no children then they're the same as far as we can tell
4090
+ if ((!newRoute.children || newRoute.children.length === 0) && (!existingRoute.children || existingRoute.children.length === 0)) {
4091
+ return true;
4092
+ }
4093
+ // Otherwise, we look to see if every child in the new route is already
4094
+ // represented in the existing route's children
4095
+ return newRoute.children.every((aChild, i) => {
4096
+ var _existingRoute$childr;
4097
+ return (_existingRoute$childr = existingRoute.children) == null ? void 0 : _existingRoute$childr.some(bChild => isSameRoute(aChild, bChild));
4098
+ });
4099
+ }
4070
4100
  /**
4071
4101
  * Execute route.lazy() methods to lazily load route modules (loader, action,
4072
4102
  * shouldRevalidate) and update the routeManifest in place which shares objects
@@ -4116,10 +4146,10 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
4116
4146
  }));
4117
4147
  }
4118
4148
  // Default implementation of `dataStrategy` which fetches all loaders in parallel
4119
- async function defaultDataStrategy(_ref6) {
4149
+ async function defaultDataStrategy(_ref4) {
4120
4150
  let {
4121
4151
  matches
4122
- } = _ref6;
4152
+ } = _ref4;
4123
4153
  let matchesToLoad = matches.filter(m => m.shouldLoad);
4124
4154
  let results = await Promise.all(matchesToLoad.map(m => m.resolve()));
4125
4155
  return results.reduce((acc, result, i) => Object.assign(acc, {
@@ -4522,7 +4552,7 @@ function processRouteLoaderData(matches, results, pendingActionResult, activeDef
4522
4552
  loaderHeaders
4523
4553
  };
4524
4554
  }
4525
- function processLoaderData(state, matches, matchesToLoad, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
4555
+ function processLoaderData(state, matches, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
4526
4556
  let {
4527
4557
  loaderData,
4528
4558
  errors
@@ -4696,9 +4726,6 @@ function isHashChangeOnly(a, b) {
4696
4726
  // /page#hash -> /page
4697
4727
  return false;
4698
4728
  }
4699
- function isPromise(val) {
4700
- return typeof val === "object" && val != null && "then" in val;
4701
- }
4702
4729
  function isDataStrategyResult(result) {
4703
4730
  return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
4704
4731
  }