@remix-run/router 0.0.0-experimental-bcda00aaf → 0.0.0-experimental-1116190f7

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-1116190f7
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1741,6 +1741,7 @@ function createRouter(init) {
1741
1741
  // SSR did the initial scroll restoration.
1742
1742
  let initialScrollRestored = init.hydrationData != null;
1743
1743
  let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);
1744
+ let initialMatchesIsFOW = false;
1744
1745
  let initialErrors = null;
1745
1746
  if (initialMatches == null && !patchRoutesOnNavigationImpl) {
1746
1747
  // If we do not match a user-provided-route, fall back to the root
@@ -1781,6 +1782,7 @@ function createRouter(init) {
1781
1782
  if (future.v7_partialHydration) {
1782
1783
  let fogOfWar = checkFogOfWar(null, dataRoutes, init.history.location.pathname);
1783
1784
  if (fogOfWar.active && fogOfWar.matches) {
1785
+ initialMatchesIsFOW = true;
1784
1786
  initialMatches = fogOfWar.matches;
1785
1787
  }
1786
1788
  }
@@ -1797,25 +1799,12 @@ function createRouter(init) {
1797
1799
  // were marked for explicit hydration
1798
1800
  let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
1799
1801
  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
1802
  // If errors exist, don't consider routes below the boundary
1814
1803
  if (errors) {
1815
1804
  let idx = initialMatches.findIndex(m => errors[m.route.id] !== undefined);
1816
- initialized = initialMatches.slice(0, idx + 1).every(isRouteInitialized);
1805
+ initialized = initialMatches.slice(0, idx + 1).every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
1817
1806
  } else {
1818
- initialized = initialMatches.every(isRouteInitialized);
1807
+ initialized = initialMatches.every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
1819
1808
  }
1820
1809
  } else {
1821
1810
  // Without partial hydration - we're initialized if we were provided any
@@ -1915,10 +1904,6 @@ function createRouter(init) {
1915
1904
  // we don't need to update UI state if they change
1916
1905
  let blockerFunctions = new Map();
1917
1906
 
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
1907
  // Flag to ignore the next history update, so we can revert the URL change on
1923
1908
  // a POP navigation that was blocked by the user without touching router state
1924
1909
  let unblockBlockerHistoryUpdate = undefined;
@@ -2314,7 +2299,9 @@ function createRouter(init) {
2314
2299
  pendingViewTransitionEnabled = (opts && opts.enableViewTransition) === true;
2315
2300
  let routesToUse = inFlightDataRoutes || dataRoutes;
2316
2301
  let loadingNavigation = opts && opts.overrideNavigation;
2317
- let matches = isUninterruptedRevalidation && inFlightDataRoutes == null ? state.matches : matchRoutes(routesToUse, location, basename);
2302
+ let matches = opts != null && opts.initialHydration && state.matches && state.matches.length > 0 && !initialMatchesIsFOW ?
2303
+ // `matchRoutes()` has already been called if we're in here via `router.initialize()`
2304
+ state.matches : matchRoutes(routesToUse, location, basename);
2318
2305
  let flushSync = (opts && opts.flushSync) === true;
2319
2306
  let fogOfWar = checkFogOfWar(matches, routesToUse, location.pathname);
2320
2307
  if (fogOfWar.active && fogOfWar.matches) {
@@ -2343,7 +2330,7 @@ function createRouter(init) {
2343
2330
  // Short circuit if it's only a hash change and not a revalidation or
2344
2331
  // mutation submission.
2345
2332
  //
2346
- // Ignore on initial page loads because since the initial load will always
2333
+ // Ignore on initial page loads because since the initial hydration will always
2347
2334
  // be "same hash". For example, on /page#hash and submit a <Form method="post">
2348
2335
  // which will default to a navigation to /page
2349
2336
  if (state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
@@ -2450,15 +2437,12 @@ function createRouter(init) {
2450
2437
  shortCircuited: true
2451
2438
  };
2452
2439
  } else if (discoverResult.type === "error") {
2453
- let {
2454
- boundaryId,
2455
- error
2456
- } = handleDiscoverRouteError(location.pathname, discoverResult);
2440
+ let boundaryId = findNearestBoundary(discoverResult.partialMatches).route.id;
2457
2441
  return {
2458
2442
  matches: discoverResult.partialMatches,
2459
2443
  pendingActionResult: [boundaryId, {
2460
2444
  type: ResultType.error,
2461
- error
2445
+ error: discoverResult.error
2462
2446
  }]
2463
2447
  };
2464
2448
  } else if (!discoverResult.matches) {
@@ -2588,15 +2572,12 @@ function createRouter(init) {
2588
2572
  shortCircuited: true
2589
2573
  };
2590
2574
  } else if (discoverResult.type === "error") {
2591
- let {
2592
- boundaryId,
2593
- error
2594
- } = handleDiscoverRouteError(location.pathname, discoverResult);
2575
+ let boundaryId = findNearestBoundary(discoverResult.partialMatches).route.id;
2595
2576
  return {
2596
2577
  matches: discoverResult.partialMatches,
2597
2578
  loaderData: {},
2598
2579
  errors: {
2599
- [boundaryId]: error
2580
+ [boundaryId]: discoverResult.error
2600
2581
  }
2601
2582
  };
2602
2583
  } else if (!discoverResult.matches) {
@@ -2662,9 +2643,7 @@ function createRouter(init) {
2662
2643
  });
2663
2644
  }
2664
2645
  revalidatingFetchers.forEach(rf => {
2665
- if (fetchControllers.has(rf.key)) {
2666
- abortFetcher(rf.key);
2667
- }
2646
+ abortFetcher(rf.key);
2668
2647
  if (rf.controller) {
2669
2648
  // Fetchers use an independent AbortController so that aborting a fetcher
2670
2649
  // (via deleteFetcher) does not abort the triggering navigation that
@@ -2724,7 +2703,7 @@ function createRouter(init) {
2724
2703
  let {
2725
2704
  loaderData,
2726
2705
  errors
2727
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
2706
+ } = processLoaderData(state, matches, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
2728
2707
 
2729
2708
  // Wire up subscribers to update loaderData as promises settle
2730
2709
  activeDeferreds.forEach((deferredData, routeId) => {
@@ -2738,17 +2717,9 @@ function createRouter(init) {
2738
2717
  });
2739
2718
  });
2740
2719
 
2741
- // During partial hydration, preserve SSR errors for routes that don't re-run
2720
+ // Preserve SSR errors during partial hydration
2742
2721
  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
- });
2722
+ errors = _extends({}, state.errors, errors);
2752
2723
  }
2753
2724
  let updatedFetchers = markFetchRedirectsDone();
2754
2725
  let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
@@ -2791,7 +2762,7 @@ function createRouter(init) {
2791
2762
  if (isServer) {
2792
2763
  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
2764
  }
2794
- if (fetchControllers.has(key)) abortFetcher(key);
2765
+ abortFetcher(key);
2795
2766
  let flushSync = (opts && opts.flushSync) === true;
2796
2767
  let routesToUse = inFlightDataRoutes || dataRoutes;
2797
2768
  let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, future.v7_relativeSplatPath, routeId, opts == null ? void 0 : opts.relative);
@@ -2820,9 +2791,9 @@ function createRouter(init) {
2820
2791
  return;
2821
2792
  }
2822
2793
  let match = getTargetMatch(matches, path);
2823
- pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2794
+ let preventScrollReset = (opts && opts.preventScrollReset) === true;
2824
2795
  if (submission && isMutationMethod(submission.formMethod)) {
2825
- handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2796
+ handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
2826
2797
  return;
2827
2798
  }
2828
2799
 
@@ -2832,12 +2803,12 @@ function createRouter(init) {
2832
2803
  routeId,
2833
2804
  path
2834
2805
  });
2835
- handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2806
+ handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
2836
2807
  }
2837
2808
 
2838
2809
  // Call the action for the matched fetcher.submit(), and then handle redirects,
2839
2810
  // errors, and revalidation
2840
- async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, submission) {
2811
+ async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, preventScrollReset, submission) {
2841
2812
  interruptActiveLoads();
2842
2813
  fetchLoadMatches.delete(key);
2843
2814
  function detectAndHandle405Error(m) {
@@ -2870,10 +2841,7 @@ function createRouter(init) {
2870
2841
  if (discoverResult.type === "aborted") {
2871
2842
  return;
2872
2843
  } else if (discoverResult.type === "error") {
2873
- let {
2874
- error
2875
- } = handleDiscoverRouteError(path, discoverResult);
2876
- setFetcherError(key, routeId, error, {
2844
+ setFetcherError(key, routeId, discoverResult.error, {
2877
2845
  flushSync
2878
2846
  });
2879
2847
  return;
@@ -2930,7 +2898,8 @@ function createRouter(init) {
2930
2898
  fetchRedirectIds.add(key);
2931
2899
  updateFetcherState(key, getLoadingFetcher(submission));
2932
2900
  return startRedirectNavigation(fetchRequest, actionResult, false, {
2933
- fetcherSubmission: submission
2901
+ fetcherSubmission: submission,
2902
+ preventScrollReset
2934
2903
  });
2935
2904
  }
2936
2905
  }
@@ -2968,9 +2937,7 @@ function createRouter(init) {
2968
2937
  let existingFetcher = state.fetchers.get(staleKey);
2969
2938
  let revalidatingFetcher = getLoadingFetcher(undefined, existingFetcher ? existingFetcher.data : undefined);
2970
2939
  state.fetchers.set(staleKey, revalidatingFetcher);
2971
- if (fetchControllers.has(staleKey)) {
2972
- abortFetcher(staleKey);
2973
- }
2940
+ abortFetcher(staleKey);
2974
2941
  if (rf.controller) {
2975
2942
  fetchControllers.set(staleKey, rf.controller);
2976
2943
  }
@@ -2993,7 +2960,9 @@ function createRouter(init) {
2993
2960
  revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
2994
2961
  let redirect = findRedirect(loaderResults);
2995
2962
  if (redirect) {
2996
- return startRedirectNavigation(revalidationRequest, redirect.result, false);
2963
+ return startRedirectNavigation(revalidationRequest, redirect.result, false, {
2964
+ preventScrollReset
2965
+ });
2997
2966
  }
2998
2967
  redirect = findRedirect(fetcherResults);
2999
2968
  if (redirect) {
@@ -3001,14 +2970,16 @@ function createRouter(init) {
3001
2970
  // fetchRedirectIds so it doesn't get revalidated on the next set of
3002
2971
  // loader executions
3003
2972
  fetchRedirectIds.add(redirect.key);
3004
- return startRedirectNavigation(revalidationRequest, redirect.result, false);
2973
+ return startRedirectNavigation(revalidationRequest, redirect.result, false, {
2974
+ preventScrollReset
2975
+ });
3005
2976
  }
3006
2977
 
3007
2978
  // Process and commit output from loaders
3008
2979
  let {
3009
2980
  loaderData,
3010
2981
  errors
3011
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
2982
+ } = processLoaderData(state, matches, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
3012
2983
 
3013
2984
  // Since we let revalidations complete even if the submitting fetcher was
3014
2985
  // deleted, only put it back to idle if it hasn't been deleted
@@ -3044,7 +3015,7 @@ function createRouter(init) {
3044
3015
  }
3045
3016
 
3046
3017
  // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
3047
- async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, submission) {
3018
+ async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, preventScrollReset, submission) {
3048
3019
  let existingFetcher = state.fetchers.get(key);
3049
3020
  updateFetcherState(key, getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined), {
3050
3021
  flushSync
@@ -3056,10 +3027,7 @@ function createRouter(init) {
3056
3027
  if (discoverResult.type === "aborted") {
3057
3028
  return;
3058
3029
  } else if (discoverResult.type === "error") {
3059
- let {
3060
- error
3061
- } = handleDiscoverRouteError(path, discoverResult);
3062
- setFetcherError(key, routeId, error, {
3030
+ setFetcherError(key, routeId, discoverResult.error, {
3063
3031
  flushSync
3064
3032
  });
3065
3033
  return;
@@ -3115,7 +3083,9 @@ function createRouter(init) {
3115
3083
  return;
3116
3084
  } else {
3117
3085
  fetchRedirectIds.add(key);
3118
- await startRedirectNavigation(fetchRequest, result, false);
3086
+ await startRedirectNavigation(fetchRequest, result, false, {
3087
+ preventScrollReset
3088
+ });
3119
3089
  return;
3120
3090
  }
3121
3091
  }
@@ -3154,6 +3124,7 @@ function createRouter(init) {
3154
3124
  let {
3155
3125
  submission,
3156
3126
  fetcherSubmission,
3127
+ preventScrollReset,
3157
3128
  replace
3158
3129
  } = _temp2 === void 0 ? {} : _temp2;
3159
3130
  if (redirect.response.headers.has("X-Remix-Revalidate")) {
@@ -3214,7 +3185,7 @@ function createRouter(init) {
3214
3185
  formAction: location
3215
3186
  }),
3216
3187
  // Preserve these flags across redirects
3217
- preventScrollReset: pendingPreventScrollReset,
3188
+ preventScrollReset: preventScrollReset || pendingPreventScrollReset,
3218
3189
  enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
3219
3190
  });
3220
3191
  } else {
@@ -3226,7 +3197,7 @@ function createRouter(init) {
3226
3197
  // Send fetcher submissions through for shouldRevalidate
3227
3198
  fetcherSubmission,
3228
3199
  // Preserve these flags across redirects
3229
- preventScrollReset: pendingPreventScrollReset,
3200
+ preventScrollReset: preventScrollReset || pendingPreventScrollReset,
3230
3201
  enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
3231
3202
  });
3232
3203
  }
@@ -3307,8 +3278,8 @@ function createRouter(init) {
3307
3278
  fetchLoadMatches.forEach((_, key) => {
3308
3279
  if (fetchControllers.has(key)) {
3309
3280
  cancelledFetcherLoads.add(key);
3310
- abortFetcher(key);
3311
3281
  }
3282
+ abortFetcher(key);
3312
3283
  });
3313
3284
  }
3314
3285
  function updateFetcherState(key, fetcher, opts) {
@@ -3381,9 +3352,10 @@ function createRouter(init) {
3381
3352
  }
3382
3353
  function abortFetcher(key) {
3383
3354
  let controller = fetchControllers.get(key);
3384
- invariant(controller, "Expected fetch controller: " + key);
3385
- controller.abort();
3386
- fetchControllers.delete(key);
3355
+ if (controller) {
3356
+ controller.abort();
3357
+ fetchControllers.delete(key);
3358
+ }
3387
3359
  }
3388
3360
  function markFetchersDone(keys) {
3389
3361
  for (let key of keys) {
@@ -3448,12 +3420,12 @@ function createRouter(init) {
3448
3420
  blockers
3449
3421
  });
3450
3422
  }
3451
- function shouldBlockNavigation(_ref4) {
3423
+ function shouldBlockNavigation(_ref2) {
3452
3424
  let {
3453
3425
  currentLocation,
3454
3426
  nextLocation,
3455
3427
  historyAction
3456
- } = _ref4;
3428
+ } = _ref2;
3457
3429
  if (blockerFunctions.size === 0) {
3458
3430
  return;
3459
3431
  }
@@ -3500,16 +3472,6 @@ function createRouter(init) {
3500
3472
  error
3501
3473
  };
3502
3474
  }
3503
- function handleDiscoverRouteError(pathname, discoverResult) {
3504
- return {
3505
- boundaryId: findNearestBoundary(discoverResult.partialMatches).route.id,
3506
- error: getInternalRouterError(400, {
3507
- type: "route-discovery",
3508
- pathname,
3509
- message: discoverResult.error != null && "message" in discoverResult.error ? discoverResult.error : String(discoverResult.error)
3510
- })
3511
- };
3512
- }
3513
3475
  function cancelActiveDeferreds(predicate) {
3514
3476
  let cancelledRouteIds = [];
3515
3477
  activeDeferreds.forEach((dfd, routeId) => {
@@ -3600,12 +3562,26 @@ function createRouter(init) {
3600
3562
  };
3601
3563
  }
3602
3564
  async function discoverRoutes(matches, pathname, signal) {
3565
+ if (!patchRoutesOnNavigationImpl) {
3566
+ return {
3567
+ type: "success",
3568
+ matches
3569
+ };
3570
+ }
3603
3571
  let partialMatches = matches;
3604
3572
  while (true) {
3605
3573
  let isNonHMR = inFlightDataRoutes == null;
3606
3574
  let routesToUse = inFlightDataRoutes || dataRoutes;
3575
+ let localManifest = manifest;
3607
3576
  try {
3608
- await loadLazyRouteChildren(patchRoutesOnNavigationImpl, pathname, partialMatches, routesToUse, manifest, mapRouteProperties, pendingPatchRoutes, signal);
3577
+ await patchRoutesOnNavigationImpl({
3578
+ path: pathname,
3579
+ matches: partialMatches,
3580
+ patch: (routeId, children) => {
3581
+ if (signal.aborted) return;
3582
+ patchRoutesImpl(routeId, children, routesToUse, localManifest, mapRouteProperties);
3583
+ }
3584
+ });
3609
3585
  } catch (e) {
3610
3586
  return {
3611
3587
  type: "error",
@@ -3619,7 +3595,7 @@ function createRouter(init) {
3619
3595
  // trigger a re-run of memoized `router.routes` dependencies.
3620
3596
  // HMR will already update the identity and reflow when it lands
3621
3597
  // `inFlightDataRoutes` in `completeNavigation`
3622
- if (isNonHMR) {
3598
+ if (isNonHMR && !signal.aborted) {
3623
3599
  dataRoutes = [...dataRoutes];
3624
3600
  }
3625
3601
  }
@@ -4198,9 +4174,21 @@ function normalizeTo(location, matches, basename, prependBasename, to, v7_relati
4198
4174
  path.hash = location.hash;
4199
4175
  }
4200
4176
 
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";
4177
+ // Account for `?index` params when routing to the current location
4178
+ if ((to == null || to === "" || to === ".") && activeRouteMatch) {
4179
+ let nakedIndex = hasNakedIndexQuery(path.search);
4180
+ if (activeRouteMatch.route.index && !nakedIndex) {
4181
+ // Add one when we're targeting an index route
4182
+ path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
4183
+ } else if (!activeRouteMatch.route.index && nakedIndex) {
4184
+ // Remove existing ones when we're not
4185
+ let params = new URLSearchParams(path.search);
4186
+ let indexValues = params.getAll("index");
4187
+ params.delete("index");
4188
+ indexValues.filter(v => v).forEach(v => params.append("index", v));
4189
+ let qs = params.toString();
4190
+ path.search = qs ? "?" + qs : "";
4191
+ }
4204
4192
  }
4205
4193
 
4206
4194
  // If we're operating within a basename, prepend it to the pathname. If
@@ -4249,8 +4237,8 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
4249
4237
  }
4250
4238
  let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?
4251
4239
  // 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;
4240
+ Array.from(opts.body.entries()).reduce((acc, _ref3) => {
4241
+ let [name, value] = _ref3;
4254
4242
  return "" + acc + name + "=" + value + "\n";
4255
4243
  }, "") : String(opts.body);
4256
4244
  return {
@@ -4340,26 +4328,37 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
4340
4328
  };
4341
4329
  }
4342
4330
 
4343
- // Filter out all routes below any caught error as they aren't going to
4331
+ // Filter out all routes at/below any caught error as they aren't going to
4344
4332
  // 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
- }
4333
+ function getLoaderMatchesUntilBoundary(matches, boundaryId, includeBoundary) {
4334
+ if (includeBoundary === void 0) {
4335
+ includeBoundary = false;
4336
+ }
4337
+ let index = matches.findIndex(m => m.route.id === boundaryId);
4338
+ if (index >= 0) {
4339
+ return matches.slice(0, includeBoundary ? index + 1 : index);
4352
4340
  }
4353
- return boundaryMatches;
4341
+ return matches;
4354
4342
  }
4355
- function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
4343
+ function getMatchesToLoad(history, state, matches, submission, location, initialHydration, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
4356
4344
  let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
4357
4345
  let currentUrl = history.createURL(state.location);
4358
4346
  let nextUrl = history.createURL(location);
4359
4347
 
4360
4348
  // 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;
4349
+ let boundaryMatches = matches;
4350
+ if (initialHydration && state.errors) {
4351
+ // On initial hydration, only consider matches up to _and including_ the boundary.
4352
+ // This is inclusive to handle cases where a server loader ran successfully,
4353
+ // a child server loader bubbled up to this route, but this route has
4354
+ // `clientLoader.hydrate` so we want to still run the `clientLoader` so that
4355
+ // we have a complete version of `loaderData`
4356
+ boundaryMatches = getLoaderMatchesUntilBoundary(matches, Object.keys(state.errors)[0], true);
4357
+ } else if (pendingActionResult && isErrorResult(pendingActionResult[1])) {
4358
+ // If an action threw an error, we call loaders up to, but not including the
4359
+ // boundary
4360
+ boundaryMatches = getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]);
4361
+ }
4363
4362
 
4364
4363
  // Don't revalidate loaders by default after action 4xx/5xx responses
4365
4364
  // when the flag is enabled. They can still opt-into revalidation via
@@ -4377,13 +4376,8 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
4377
4376
  if (route.loader == null) {
4378
4377
  return false;
4379
4378
  }
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);
4379
+ if (initialHydration) {
4380
+ return shouldLoadRouteOnHydration(route, state.loaderData, state.errors);
4387
4381
  }
4388
4382
 
4389
4383
  // Always call the loader on new route instances and pending defer cancellations
@@ -4417,11 +4411,11 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
4417
4411
  let revalidatingFetchers = [];
4418
4412
  fetchLoadMatches.forEach((f, key) => {
4419
4413
  // Don't revalidate:
4420
- // - on initial load (shouldn't be any fetchers then anyway)
4414
+ // - on initial hydration (shouldn't be any fetchers then anyway)
4421
4415
  // - if fetcher won't be present in the subsequent render
4422
4416
  // - no longer matches the URL (v7_fetcherPersist=false)
4423
4417
  // - was unmounted but persisted due to v7_fetcherPersist=true
4424
- if (isInitialLoad || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
4418
+ if (initialHydration || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
4425
4419
  return;
4426
4420
  }
4427
4421
  let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
@@ -4487,6 +4481,32 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
4487
4481
  });
4488
4482
  return [navigationMatches, revalidatingFetchers];
4489
4483
  }
4484
+ function shouldLoadRouteOnHydration(route, loaderData, errors) {
4485
+ // We dunno if we have a loader - gotta find out!
4486
+ if (route.lazy) {
4487
+ return true;
4488
+ }
4489
+
4490
+ // No loader, nothing to initialize
4491
+ if (!route.loader) {
4492
+ return false;
4493
+ }
4494
+ let hasData = loaderData != null && loaderData[route.id] !== undefined;
4495
+ let hasError = errors != null && errors[route.id] !== undefined;
4496
+
4497
+ // Don't run if we error'd during SSR
4498
+ if (!hasData && hasError) {
4499
+ return false;
4500
+ }
4501
+
4502
+ // Explicitly opting-in to running on hydration
4503
+ if (typeof route.loader === "function" && route.loader.hydrate === true) {
4504
+ return true;
4505
+ }
4506
+
4507
+ // Otherwise, run if we're not yet initialized with anything
4508
+ return !hasData && !hasError;
4509
+ }
4490
4510
  function isNewLoader(currentLoaderData, currentMatch, match) {
4491
4511
  let isNew =
4492
4512
  // [a] -> [a, b]
@@ -4520,34 +4540,6 @@ function shouldRevalidateLoader(loaderMatch, arg) {
4520
4540
  }
4521
4541
  return arg.defaultShouldRevalidate;
4522
4542
  }
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
4543
  function patchRoutesImpl(routeId, children, routesToUse, manifest, mapRouteProperties) {
4552
4544
  var _childrenToPatch;
4553
4545
  let childrenToPatch;
@@ -4565,10 +4557,34 @@ function patchRoutesImpl(routeId, children, routesToUse, manifest, mapRoutePrope
4565
4557
  // Don't patch in routes we already know about so that `patch` is idempotent
4566
4558
  // to simplify user-land code. This is useful because we re-call the
4567
4559
  // `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));
4560
+ let uniqueChildren = children.filter(newRoute => !childrenToPatch.some(existingRoute => isSameRoute(newRoute, existingRoute)));
4569
4561
  let newRoutes = convertRoutesToDataRoutes(uniqueChildren, mapRouteProperties, [routeId || "_", "patch", String(((_childrenToPatch = childrenToPatch) == null ? void 0 : _childrenToPatch.length) || "0")], manifest);
4570
4562
  childrenToPatch.push(...newRoutes);
4571
4563
  }
4564
+ function isSameRoute(newRoute, existingRoute) {
4565
+ // Most optimal check is by id
4566
+ if ("id" in newRoute && "id" in existingRoute && newRoute.id === existingRoute.id) {
4567
+ return true;
4568
+ }
4569
+
4570
+ // Second is by pathing differences
4571
+ if (!(newRoute.index === existingRoute.index && newRoute.path === existingRoute.path && newRoute.caseSensitive === existingRoute.caseSensitive)) {
4572
+ return false;
4573
+ }
4574
+
4575
+ // Pathless layout routes are trickier since we need to check children.
4576
+ // If they have no children then they're the same as far as we can tell
4577
+ if ((!newRoute.children || newRoute.children.length === 0) && (!existingRoute.children || existingRoute.children.length === 0)) {
4578
+ return true;
4579
+ }
4580
+
4581
+ // Otherwise, we look to see if every child in the new route is already
4582
+ // represented in the existing route's children
4583
+ return newRoute.children.every((aChild, i) => {
4584
+ var _existingRoute$childr;
4585
+ return (_existingRoute$childr = existingRoute.children) == null ? void 0 : _existingRoute$childr.some(bChild => isSameRoute(aChild, bChild));
4586
+ });
4587
+ }
4572
4588
 
4573
4589
  /**
4574
4590
  * Execute route.lazy() methods to lazily load route modules (loader, action,
@@ -4624,10 +4640,10 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
4624
4640
  }
4625
4641
 
4626
4642
  // Default implementation of `dataStrategy` which fetches all loaders in parallel
4627
- async function defaultDataStrategy(_ref6) {
4643
+ async function defaultDataStrategy(_ref4) {
4628
4644
  let {
4629
4645
  matches
4630
- } = _ref6;
4646
+ } = _ref4;
4631
4647
  let matchesToLoad = matches.filter(m => m.shouldLoad);
4632
4648
  let results = await Promise.all(matchesToLoad.map(m => m.resolve()));
4633
4649
  return results.reduce((acc, result, i) => Object.assign(acc, {
@@ -5041,7 +5057,7 @@ function processRouteLoaderData(matches, results, pendingActionResult, activeDef
5041
5057
  loaderHeaders
5042
5058
  };
5043
5059
  }
5044
- function processLoaderData(state, matches, matchesToLoad, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
5060
+ function processLoaderData(state, matches, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
5045
5061
  let {
5046
5062
  loaderData,
5047
5063
  errors
@@ -5156,9 +5172,7 @@ function getInternalRouterError(status, _temp5) {
5156
5172
  let errorMessage = "Unknown @remix-run/router error";
5157
5173
  if (status === 400) {
5158
5174
  statusText = "Bad Request";
5159
- if (type === "route-discovery") {
5160
- errorMessage = "Unable to match URL \"" + pathname + "\" - the `patchRoutesOnNavigation()` " + ("function threw the following error:\n" + message);
5161
- } else if (method && pathname && routeId) {
5175
+ if (method && pathname && routeId) {
5162
5176
  errorMessage = "You made a " + method + " request to \"" + pathname + "\" but " + ("did not provide a `loader` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
5163
5177
  } else if (type === "defer-action") {
5164
5178
  errorMessage = "defer() is not supported in actions";
@@ -5220,9 +5234,6 @@ function isHashChangeOnly(a, b) {
5220
5234
  // /page#hash -> /page
5221
5235
  return false;
5222
5236
  }
5223
- function isPromise(val) {
5224
- return typeof val === "object" && val != null && "then" in val;
5225
- }
5226
5237
  function isDataStrategyResult(result) {
5227
5238
  return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
5228
5239
  }