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