@remix-run/router 1.19.2 → 1.20.0-pre.0

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 v1.19.2
2
+ * @remix-run/router v1.20.0-pre.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1711,8 +1711,8 @@ function createRouter(init) {
1711
1711
  let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
1712
1712
  let inFlightDataRoutes;
1713
1713
  let basename = init.basename || "/";
1714
- let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
1715
- let patchRoutesOnNavigationImpl = init.unstable_patchRoutesOnNavigation;
1714
+ let dataStrategyImpl = init.dataStrategy || defaultDataStrategy;
1715
+ let patchRoutesOnNavigationImpl = init.patchRoutesOnNavigation;
1716
1716
 
1717
1717
  // Config driven behavior flags
1718
1718
  let future = _extends({
@@ -1727,10 +1727,6 @@ function createRouter(init) {
1727
1727
  let unlistenHistory = null;
1728
1728
  // Externally-provided functions to call on all state changes
1729
1729
  let subscribers = new Set();
1730
- // FIFO queue of previously discovered routes to prevent re-calling on
1731
- // subsequent navigations to the same path
1732
- let discoveredRoutesMaxSize = 1000;
1733
- let discoveredRoutes = new Set();
1734
1730
  // Externally-provided object to hold scroll restoration locations during routing
1735
1731
  let savedScrollPositions = null;
1736
1732
  // Externally-provided function to get scroll restoration keys
@@ -1801,25 +1797,12 @@ function createRouter(init) {
1801
1797
  // were marked for explicit hydration
1802
1798
  let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
1803
1799
  let errors = init.hydrationData ? init.hydrationData.errors : null;
1804
- let isRouteInitialized = m => {
1805
- // No loader, nothing to initialize
1806
- if (!m.route.loader) {
1807
- return true;
1808
- }
1809
- // Explicitly opting-in to running on hydration
1810
- if (typeof m.route.loader === "function" && m.route.loader.hydrate === true) {
1811
- return false;
1812
- }
1813
- // Otherwise, initialized if hydrated with data or an error
1814
- return loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined;
1815
- };
1816
-
1817
1800
  // If errors exist, don't consider routes below the boundary
1818
1801
  if (errors) {
1819
1802
  let idx = initialMatches.findIndex(m => errors[m.route.id] !== undefined);
1820
- initialized = initialMatches.slice(0, idx + 1).every(isRouteInitialized);
1803
+ initialized = initialMatches.slice(0, idx + 1).every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
1821
1804
  } else {
1822
- initialized = initialMatches.every(isRouteInitialized);
1805
+ initialized = initialMatches.every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
1823
1806
  }
1824
1807
  } else {
1825
1808
  // Without partial hydration - we're initialized if we were provided any
@@ -1919,10 +1902,6 @@ function createRouter(init) {
1919
1902
  // we don't need to update UI state if they change
1920
1903
  let blockerFunctions = new Map();
1921
1904
 
1922
- // Map of pending patchRoutesOnNavigation() promises (keyed by path/matches) so
1923
- // that we only kick them off once for a given combo
1924
- let pendingPatchRoutes = new Map();
1925
-
1926
1905
  // Flag to ignore the next history update, so we can revert the URL change on
1927
1906
  // a POP navigation that was blocked by the user without touching router state
1928
1907
  let unblockBlockerHistoryUpdate = undefined;
@@ -2060,8 +2039,8 @@ function createRouter(init) {
2060
2039
  // we don't get ourselves into a loop calling the new subscriber immediately
2061
2040
  [...subscribers].forEach(subscriber => subscriber(state, {
2062
2041
  deletedFetchers: deletedFetchersKeys,
2063
- unstable_viewTransitionOpts: opts.viewTransitionOpts,
2064
- unstable_flushSync: opts.flushSync === true
2042
+ viewTransitionOpts: opts.viewTransitionOpts,
2043
+ flushSync: opts.flushSync === true
2065
2044
  }));
2066
2045
 
2067
2046
  // Remove idle fetchers from state since we only care about in-flight fetchers.
@@ -2221,7 +2200,7 @@ function createRouter(init) {
2221
2200
  historyAction = Action.Replace;
2222
2201
  }
2223
2202
  let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
2224
- let flushSync = (opts && opts.unstable_flushSync) === true;
2203
+ let flushSync = (opts && opts.flushSync) === true;
2225
2204
  let blockerKey = shouldBlockNavigation({
2226
2205
  currentLocation,
2227
2206
  nextLocation,
@@ -2259,7 +2238,7 @@ function createRouter(init) {
2259
2238
  pendingError: error,
2260
2239
  preventScrollReset,
2261
2240
  replace: opts && opts.replace,
2262
- enableViewTransition: opts && opts.unstable_viewTransition,
2241
+ enableViewTransition: opts && opts.viewTransition,
2263
2242
  flushSync
2264
2243
  });
2265
2244
  }
@@ -2347,7 +2326,7 @@ function createRouter(init) {
2347
2326
  // Short circuit if it's only a hash change and not a revalidation or
2348
2327
  // mutation submission.
2349
2328
  //
2350
- // 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
2351
2330
  // be "same hash". For example, on /page#hash and submit a <Form method="post">
2352
2331
  // which will default to a navigation to /page
2353
2332
  if (state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
@@ -2666,9 +2645,7 @@ function createRouter(init) {
2666
2645
  });
2667
2646
  }
2668
2647
  revalidatingFetchers.forEach(rf => {
2669
- if (fetchControllers.has(rf.key)) {
2670
- abortFetcher(rf.key);
2671
- }
2648
+ abortFetcher(rf.key);
2672
2649
  if (rf.controller) {
2673
2650
  // Fetchers use an independent AbortController so that aborting a fetcher
2674
2651
  // (via deleteFetcher) does not abort the triggering navigation that
@@ -2728,7 +2705,7 @@ function createRouter(init) {
2728
2705
  let {
2729
2706
  loaderData,
2730
2707
  errors
2731
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
2708
+ } = processLoaderData(state, matches, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
2732
2709
 
2733
2710
  // Wire up subscribers to update loaderData as promises settle
2734
2711
  activeDeferreds.forEach((deferredData, routeId) => {
@@ -2742,17 +2719,9 @@ function createRouter(init) {
2742
2719
  });
2743
2720
  });
2744
2721
 
2745
- // During partial hydration, preserve SSR errors for routes that don't re-run
2722
+ // Preserve SSR errors during partial hydration
2746
2723
  if (future.v7_partialHydration && initialHydration && state.errors) {
2747
- Object.entries(state.errors).filter(_ref2 => {
2748
- let [id] = _ref2;
2749
- return !matchesToLoad.some(m => m.route.id === id);
2750
- }).forEach(_ref3 => {
2751
- let [routeId, error] = _ref3;
2752
- errors = Object.assign(errors || {}, {
2753
- [routeId]: error
2754
- });
2755
- });
2724
+ errors = _extends({}, state.errors, errors);
2756
2725
  }
2757
2726
  let updatedFetchers = markFetchRedirectsDone();
2758
2727
  let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
@@ -2795,8 +2764,8 @@ function createRouter(init) {
2795
2764
  if (isServer) {
2796
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.");
2797
2766
  }
2798
- if (fetchControllers.has(key)) abortFetcher(key);
2799
- let flushSync = (opts && opts.unstable_flushSync) === true;
2767
+ abortFetcher(key);
2768
+ let flushSync = (opts && opts.flushSync) === true;
2800
2769
  let routesToUse = inFlightDataRoutes || dataRoutes;
2801
2770
  let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, future.v7_relativeSplatPath, routeId, opts == null ? void 0 : opts.relative);
2802
2771
  let matches = matchRoutes(routesToUse, normalizedPath, basename);
@@ -2824,9 +2793,9 @@ function createRouter(init) {
2824
2793
  return;
2825
2794
  }
2826
2795
  let match = getTargetMatch(matches, path);
2827
- pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2796
+ let preventScrollReset = (opts && opts.preventScrollReset) === true;
2828
2797
  if (submission && isMutationMethod(submission.formMethod)) {
2829
- handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2798
+ handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
2830
2799
  return;
2831
2800
  }
2832
2801
 
@@ -2836,12 +2805,12 @@ function createRouter(init) {
2836
2805
  routeId,
2837
2806
  path
2838
2807
  });
2839
- handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2808
+ handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
2840
2809
  }
2841
2810
 
2842
2811
  // Call the action for the matched fetcher.submit(), and then handle redirects,
2843
2812
  // errors, and revalidation
2844
- async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, submission) {
2813
+ async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, preventScrollReset, submission) {
2845
2814
  interruptActiveLoads();
2846
2815
  fetchLoadMatches.delete(key);
2847
2816
  function detectAndHandle405Error(m) {
@@ -2934,7 +2903,8 @@ function createRouter(init) {
2934
2903
  fetchRedirectIds.add(key);
2935
2904
  updateFetcherState(key, getLoadingFetcher(submission));
2936
2905
  return startRedirectNavigation(fetchRequest, actionResult, false, {
2937
- fetcherSubmission: submission
2906
+ fetcherSubmission: submission,
2907
+ preventScrollReset
2938
2908
  });
2939
2909
  }
2940
2910
  }
@@ -2972,9 +2942,7 @@ function createRouter(init) {
2972
2942
  let existingFetcher = state.fetchers.get(staleKey);
2973
2943
  let revalidatingFetcher = getLoadingFetcher(undefined, existingFetcher ? existingFetcher.data : undefined);
2974
2944
  state.fetchers.set(staleKey, revalidatingFetcher);
2975
- if (fetchControllers.has(staleKey)) {
2976
- abortFetcher(staleKey);
2977
- }
2945
+ abortFetcher(staleKey);
2978
2946
  if (rf.controller) {
2979
2947
  fetchControllers.set(staleKey, rf.controller);
2980
2948
  }
@@ -2997,7 +2965,9 @@ function createRouter(init) {
2997
2965
  revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
2998
2966
  let redirect = findRedirect(loaderResults);
2999
2967
  if (redirect) {
3000
- return startRedirectNavigation(revalidationRequest, redirect.result, false);
2968
+ return startRedirectNavigation(revalidationRequest, redirect.result, false, {
2969
+ preventScrollReset
2970
+ });
3001
2971
  }
3002
2972
  redirect = findRedirect(fetcherResults);
3003
2973
  if (redirect) {
@@ -3005,14 +2975,16 @@ function createRouter(init) {
3005
2975
  // fetchRedirectIds so it doesn't get revalidated on the next set of
3006
2976
  // loader executions
3007
2977
  fetchRedirectIds.add(redirect.key);
3008
- return startRedirectNavigation(revalidationRequest, redirect.result, false);
2978
+ return startRedirectNavigation(revalidationRequest, redirect.result, false, {
2979
+ preventScrollReset
2980
+ });
3009
2981
  }
3010
2982
 
3011
2983
  // Process and commit output from loaders
3012
2984
  let {
3013
2985
  loaderData,
3014
2986
  errors
3015
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
2987
+ } = processLoaderData(state, matches, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
3016
2988
 
3017
2989
  // Since we let revalidations complete even if the submitting fetcher was
3018
2990
  // deleted, only put it back to idle if it hasn't been deleted
@@ -3048,7 +3020,7 @@ function createRouter(init) {
3048
3020
  }
3049
3021
 
3050
3022
  // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
3051
- async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, submission) {
3023
+ async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, preventScrollReset, submission) {
3052
3024
  let existingFetcher = state.fetchers.get(key);
3053
3025
  updateFetcherState(key, getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined), {
3054
3026
  flushSync
@@ -3119,7 +3091,9 @@ function createRouter(init) {
3119
3091
  return;
3120
3092
  } else {
3121
3093
  fetchRedirectIds.add(key);
3122
- await startRedirectNavigation(fetchRequest, result, false);
3094
+ await startRedirectNavigation(fetchRequest, result, false, {
3095
+ preventScrollReset
3096
+ });
3123
3097
  return;
3124
3098
  }
3125
3099
  }
@@ -3158,6 +3132,7 @@ function createRouter(init) {
3158
3132
  let {
3159
3133
  submission,
3160
3134
  fetcherSubmission,
3135
+ preventScrollReset,
3161
3136
  replace
3162
3137
  } = _temp2 === void 0 ? {} : _temp2;
3163
3138
  if (redirect.response.headers.has("X-Remix-Revalidate")) {
@@ -3218,7 +3193,7 @@ function createRouter(init) {
3218
3193
  formAction: location
3219
3194
  }),
3220
3195
  // Preserve these flags across redirects
3221
- preventScrollReset: pendingPreventScrollReset,
3196
+ preventScrollReset: preventScrollReset || pendingPreventScrollReset,
3222
3197
  enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
3223
3198
  });
3224
3199
  } else {
@@ -3230,7 +3205,7 @@ function createRouter(init) {
3230
3205
  // Send fetcher submissions through for shouldRevalidate
3231
3206
  fetcherSubmission,
3232
3207
  // Preserve these flags across redirects
3233
- preventScrollReset: pendingPreventScrollReset,
3208
+ preventScrollReset: preventScrollReset || pendingPreventScrollReset,
3234
3209
  enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
3235
3210
  });
3236
3211
  }
@@ -3311,8 +3286,8 @@ function createRouter(init) {
3311
3286
  fetchLoadMatches.forEach((_, key) => {
3312
3287
  if (fetchControllers.has(key)) {
3313
3288
  cancelledFetcherLoads.add(key);
3314
- abortFetcher(key);
3315
3289
  }
3290
+ abortFetcher(key);
3316
3291
  });
3317
3292
  }
3318
3293
  function updateFetcherState(key, fetcher, opts) {
@@ -3385,9 +3360,10 @@ function createRouter(init) {
3385
3360
  }
3386
3361
  function abortFetcher(key) {
3387
3362
  let controller = fetchControllers.get(key);
3388
- invariant(controller, "Expected fetch controller: " + key);
3389
- controller.abort();
3390
- fetchControllers.delete(key);
3363
+ if (controller) {
3364
+ controller.abort();
3365
+ fetchControllers.delete(key);
3366
+ }
3391
3367
  }
3392
3368
  function markFetchersDone(keys) {
3393
3369
  for (let key of keys) {
@@ -3452,12 +3428,12 @@ function createRouter(init) {
3452
3428
  blockers
3453
3429
  });
3454
3430
  }
3455
- function shouldBlockNavigation(_ref4) {
3431
+ function shouldBlockNavigation(_ref2) {
3456
3432
  let {
3457
3433
  currentLocation,
3458
3434
  nextLocation,
3459
3435
  historyAction
3460
- } = _ref4;
3436
+ } = _ref2;
3461
3437
  if (blockerFunctions.size === 0) {
3462
3438
  return;
3463
3439
  }
@@ -3579,15 +3555,6 @@ function createRouter(init) {
3579
3555
  }
3580
3556
  function checkFogOfWar(matches, routesToUse, pathname) {
3581
3557
  if (patchRoutesOnNavigationImpl) {
3582
- // Don't bother re-calling patchRouteOnMiss for a path we've already
3583
- // processed. the last execution would have patched the route tree
3584
- // accordingly so `matches` here are already accurate.
3585
- if (discoveredRoutes.has(pathname)) {
3586
- return {
3587
- active: false,
3588
- matches
3589
- };
3590
- }
3591
3558
  if (!matches) {
3592
3559
  let fogMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
3593
3560
  return {
@@ -3613,12 +3580,26 @@ function createRouter(init) {
3613
3580
  };
3614
3581
  }
3615
3582
  async function discoverRoutes(matches, pathname, signal) {
3583
+ if (!patchRoutesOnNavigationImpl) {
3584
+ return {
3585
+ type: "success",
3586
+ matches
3587
+ };
3588
+ }
3616
3589
  let partialMatches = matches;
3617
3590
  while (true) {
3618
3591
  let isNonHMR = inFlightDataRoutes == null;
3619
3592
  let routesToUse = inFlightDataRoutes || dataRoutes;
3593
+ let localManifest = manifest;
3620
3594
  try {
3621
- 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
+ });
3622
3603
  } catch (e) {
3623
3604
  return {
3624
3605
  type: "error",
@@ -3632,7 +3613,7 @@ function createRouter(init) {
3632
3613
  // trigger a re-run of memoized `router.routes` dependencies.
3633
3614
  // HMR will already update the identity and reflow when it lands
3634
3615
  // `inFlightDataRoutes` in `completeNavigation`
3635
- if (isNonHMR) {
3616
+ if (isNonHMR && !signal.aborted) {
3636
3617
  dataRoutes = [...dataRoutes];
3637
3618
  }
3638
3619
  }
@@ -3643,7 +3624,6 @@ function createRouter(init) {
3643
3624
  }
3644
3625
  let newMatches = matchRoutes(routesToUse, pathname, basename);
3645
3626
  if (newMatches) {
3646
- addToFifoQueue(pathname, discoveredRoutes);
3647
3627
  return {
3648
3628
  type: "success",
3649
3629
  matches: newMatches
@@ -3653,7 +3633,6 @@ function createRouter(init) {
3653
3633
 
3654
3634
  // Avoid loops if the second pass results in the same partial matches
3655
3635
  if (!newPartialMatches || partialMatches.length === newPartialMatches.length && partialMatches.every((m, i) => m.route.id === newPartialMatches[i].route.id)) {
3656
- addToFifoQueue(pathname, discoveredRoutes);
3657
3636
  return {
3658
3637
  type: "success",
3659
3638
  matches: null
@@ -3662,13 +3641,6 @@ function createRouter(init) {
3662
3641
  partialMatches = newPartialMatches;
3663
3642
  }
3664
3643
  }
3665
- function addToFifoQueue(path, queue) {
3666
- if (queue.size >= discoveredRoutesMaxSize) {
3667
- let first = queue.values().next().value;
3668
- queue.delete(first);
3669
- }
3670
- queue.add(path);
3671
- }
3672
3644
  function _internalSetRoutes(newRoutes) {
3673
3645
  manifest = {};
3674
3646
  inFlightDataRoutes = convertRoutesToDataRoutes(newRoutes, mapRouteProperties, undefined, manifest);
@@ -3793,7 +3765,7 @@ function createStaticHandler(routes, opts) {
3793
3765
  let {
3794
3766
  requestContext,
3795
3767
  skipLoaderErrorBubbling,
3796
- unstable_dataStrategy
3768
+ dataStrategy
3797
3769
  } = _temp3 === void 0 ? {} : _temp3;
3798
3770
  let url = new URL(request.url);
3799
3771
  let method = request.method;
@@ -3846,7 +3818,7 @@ function createStaticHandler(routes, opts) {
3846
3818
  activeDeferreds: null
3847
3819
  };
3848
3820
  }
3849
- let result = await queryImpl(request, location, matches, requestContext, unstable_dataStrategy || null, skipLoaderErrorBubbling === true, null);
3821
+ let result = await queryImpl(request, location, matches, requestContext, dataStrategy || null, skipLoaderErrorBubbling === true, null);
3850
3822
  if (isResponse(result)) {
3851
3823
  return result;
3852
3824
  }
@@ -3890,7 +3862,7 @@ function createStaticHandler(routes, opts) {
3890
3862
  let {
3891
3863
  routeId,
3892
3864
  requestContext,
3893
- unstable_dataStrategy
3865
+ dataStrategy
3894
3866
  } = _temp4 === void 0 ? {} : _temp4;
3895
3867
  let url = new URL(request.url);
3896
3868
  let method = request.method;
@@ -3919,7 +3891,7 @@ function createStaticHandler(routes, opts) {
3919
3891
  pathname: location.pathname
3920
3892
  });
3921
3893
  }
3922
- let result = await queryImpl(request, location, matches, requestContext, unstable_dataStrategy || null, false, match);
3894
+ let result = await queryImpl(request, location, matches, requestContext, dataStrategy || null, false, match);
3923
3895
  if (isResponse(result)) {
3924
3896
  return result;
3925
3897
  }
@@ -3946,14 +3918,14 @@ function createStaticHandler(routes, opts) {
3946
3918
  }
3947
3919
  return undefined;
3948
3920
  }
3949
- async function queryImpl(request, location, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch) {
3921
+ async function queryImpl(request, location, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch) {
3950
3922
  invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
3951
3923
  try {
3952
3924
  if (isMutationMethod(request.method.toLowerCase())) {
3953
- let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch != null);
3925
+ let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch != null);
3954
3926
  return result;
3955
3927
  }
3956
- let result = await loadRouteData(request, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch);
3928
+ let result = await loadRouteData(request, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch);
3957
3929
  return isResponse(result) ? result : _extends({}, result, {
3958
3930
  actionData: null,
3959
3931
  actionHeaders: {}
@@ -3976,7 +3948,7 @@ function createStaticHandler(routes, opts) {
3976
3948
  throw e;
3977
3949
  }
3978
3950
  }
3979
- async function submit(request, matches, actionMatch, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, isRouteRequest) {
3951
+ async function submit(request, matches, actionMatch, requestContext, dataStrategy, skipLoaderErrorBubbling, isRouteRequest) {
3980
3952
  let result;
3981
3953
  if (!actionMatch.route.action && !actionMatch.route.lazy) {
3982
3954
  let error = getInternalRouterError(405, {
@@ -3992,7 +3964,7 @@ function createStaticHandler(routes, opts) {
3992
3964
  error
3993
3965
  };
3994
3966
  } else {
3995
- let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, unstable_dataStrategy);
3967
+ let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, dataStrategy);
3996
3968
  result = results[actionMatch.route.id];
3997
3969
  if (request.signal.aborted) {
3998
3970
  throwStaticHandlerAbortedError(request, isRouteRequest, future);
@@ -4054,7 +4026,7 @@ function createStaticHandler(routes, opts) {
4054
4026
  // Store off the pending error - we use it to determine which loaders
4055
4027
  // to call and will commit it when we complete the navigation
4056
4028
  let boundaryMatch = skipLoaderErrorBubbling ? actionMatch : findNearestBoundary(matches, actionMatch.route.id);
4057
- let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
4029
+ let context = await loadRouteData(loaderRequest, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
4058
4030
 
4059
4031
  // action status codes take precedence over loader status codes
4060
4032
  return _extends({}, context, {
@@ -4065,7 +4037,7 @@ function createStaticHandler(routes, opts) {
4065
4037
  } : {})
4066
4038
  });
4067
4039
  }
4068
- let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, null);
4040
+ let context = await loadRouteData(loaderRequest, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, null);
4069
4041
  return _extends({}, context, {
4070
4042
  actionData: {
4071
4043
  [actionMatch.route.id]: result.data
@@ -4078,7 +4050,7 @@ function createStaticHandler(routes, opts) {
4078
4050
  } : {}
4079
4051
  });
4080
4052
  }
4081
- async function loadRouteData(request, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
4053
+ async function loadRouteData(request, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
4082
4054
  let isRouteRequest = routeMatch != null;
4083
4055
 
4084
4056
  // Short circuit if we have no loaders to run (queryRoute())
@@ -4108,7 +4080,7 @@ function createStaticHandler(routes, opts) {
4108
4080
  activeDeferreds: null
4109
4081
  };
4110
4082
  }
4111
- let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy);
4083
+ let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext, dataStrategy);
4112
4084
  if (request.signal.aborted) {
4113
4085
  throwStaticHandlerAbortedError(request, isRouteRequest, future);
4114
4086
  }
@@ -4132,8 +4104,8 @@ function createStaticHandler(routes, opts) {
4132
4104
 
4133
4105
  // Utility wrapper for calling dataStrategy server-side without having to
4134
4106
  // pass around the manifest, mapRouteProperties, etc.
4135
- async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy) {
4136
- let results = await callDataStrategyImpl(unstable_dataStrategy || defaultDataStrategy, type, null, request, matchesToLoad, matches, null, manifest, mapRouteProperties, requestContext);
4107
+ async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, dataStrategy) {
4108
+ let results = await callDataStrategyImpl(dataStrategy || defaultDataStrategy, type, null, request, matchesToLoad, matches, null, manifest, mapRouteProperties, requestContext);
4137
4109
  let dataResults = {};
4138
4110
  await Promise.all(matches.map(async match => {
4139
4111
  if (!(match.route.id in results)) {
@@ -4220,9 +4192,21 @@ function normalizeTo(location, matches, basename, prependBasename, to, v7_relati
4220
4192
  path.hash = location.hash;
4221
4193
  }
4222
4194
 
4223
- // Add an ?index param for matched index routes if we don't already have one
4224
- if ((to == null || to === "" || to === ".") && activeRouteMatch && activeRouteMatch.route.index && !hasNakedIndexQuery(path.search)) {
4225
- 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
+ }
4226
4210
  }
4227
4211
 
4228
4212
  // If we're operating within a basename, prepend it to the pathname. If
@@ -4271,8 +4255,8 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
4271
4255
  }
4272
4256
  let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?
4273
4257
  // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data
4274
- Array.from(opts.body.entries()).reduce((acc, _ref5) => {
4275
- let [name, value] = _ref5;
4258
+ Array.from(opts.body.entries()).reduce((acc, _ref3) => {
4259
+ let [name, value] = _ref3;
4276
4260
  return "" + acc + name + "=" + value + "\n";
4277
4261
  }, "") : String(opts.body);
4278
4262
  return {
@@ -4362,26 +4346,37 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
4362
4346
  };
4363
4347
  }
4364
4348
 
4365
- // 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
4366
4350
  // render so we don't need to load them
4367
- function getLoaderMatchesUntilBoundary(matches, boundaryId) {
4368
- let boundaryMatches = matches;
4369
- if (boundaryId) {
4370
- let index = matches.findIndex(m => m.route.id === boundaryId);
4371
- if (index >= 0) {
4372
- boundaryMatches = matches.slice(0, index);
4373
- }
4351
+ function getLoaderMatchesUntilBoundary(matches, boundaryId, includeBoundary) {
4352
+ if (includeBoundary === void 0) {
4353
+ includeBoundary = false;
4354
+ }
4355
+ let index = matches.findIndex(m => m.route.id === boundaryId);
4356
+ if (index >= 0) {
4357
+ return matches.slice(0, includeBoundary ? index + 1 : index);
4374
4358
  }
4375
- return boundaryMatches;
4359
+ return matches;
4376
4360
  }
4377
- 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) {
4378
4362
  let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
4379
4363
  let currentUrl = history.createURL(state.location);
4380
4364
  let nextUrl = history.createURL(location);
4381
4365
 
4382
4366
  // Pick navigation matches that are net-new or qualify for revalidation
4383
- let boundaryId = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[0] : undefined;
4384
- 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
+ }
4385
4380
 
4386
4381
  // Don't revalidate loaders by default after action 4xx/5xx responses
4387
4382
  // when the flag is enabled. They can still opt-into revalidation via
@@ -4399,13 +4394,8 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
4399
4394
  if (route.loader == null) {
4400
4395
  return false;
4401
4396
  }
4402
- if (isInitialLoad) {
4403
- if (typeof route.loader !== "function" || route.loader.hydrate) {
4404
- return true;
4405
- }
4406
- return state.loaderData[route.id] === undefined && (
4407
- // Don't re-run if the loader ran and threw an error
4408
- !state.errors || state.errors[route.id] === undefined);
4397
+ if (initialHydration) {
4398
+ return shouldLoadRouteOnHydration(route, state.loaderData, state.errors);
4409
4399
  }
4410
4400
 
4411
4401
  // Always call the loader on new route instances and pending defer cancellations
@@ -4439,11 +4429,11 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
4439
4429
  let revalidatingFetchers = [];
4440
4430
  fetchLoadMatches.forEach((f, key) => {
4441
4431
  // Don't revalidate:
4442
- // - on initial load (shouldn't be any fetchers then anyway)
4432
+ // - on initial hydration (shouldn't be any fetchers then anyway)
4443
4433
  // - if fetcher won't be present in the subsequent render
4444
4434
  // - no longer matches the URL (v7_fetcherPersist=false)
4445
4435
  // - was unmounted but persisted due to v7_fetcherPersist=true
4446
- 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)) {
4447
4437
  return;
4448
4438
  }
4449
4439
  let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
@@ -4509,6 +4499,32 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
4509
4499
  });
4510
4500
  return [navigationMatches, revalidatingFetchers];
4511
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
+ }
4512
4528
  function isNewLoader(currentLoaderData, currentMatch, match) {
4513
4529
  let isNew =
4514
4530
  // [a] -> [a, b]
@@ -4542,49 +4558,50 @@ function shouldRevalidateLoader(loaderMatch, arg) {
4542
4558
  }
4543
4559
  return arg.defaultShouldRevalidate;
4544
4560
  }
4545
-
4546
- /**
4547
- * Idempotent utility to execute patchRoutesOnNavigation() to lazily load route
4548
- * definitions and update the routes/routeManifest
4549
- */
4550
- async function loadLazyRouteChildren(patchRoutesOnNavigationImpl, path, matches, routes, manifest, mapRouteProperties, pendingRouteChildren, signal) {
4551
- let key = [path, ...matches.map(m => m.route.id)].join("-");
4552
- try {
4553
- let pending = pendingRouteChildren.get(key);
4554
- if (!pending) {
4555
- pending = patchRoutesOnNavigationImpl({
4556
- path,
4557
- matches,
4558
- patch: (routeId, children) => {
4559
- if (!signal.aborted) {
4560
- patchRoutesImpl(routeId, children, routes, manifest, mapRouteProperties);
4561
- }
4562
- }
4563
- });
4564
- pendingRouteChildren.set(key, pending);
4565
- }
4566
- if (pending && isPromise(pending)) {
4567
- await pending;
4568
- }
4569
- } finally {
4570
- pendingRouteChildren.delete(key);
4571
- }
4572
- }
4573
4561
  function patchRoutesImpl(routeId, children, routesToUse, manifest, mapRouteProperties) {
4562
+ var _childrenToPatch;
4563
+ let childrenToPatch;
4574
4564
  if (routeId) {
4575
- var _route$children;
4576
4565
  let route = manifest[routeId];
4577
4566
  invariant(route, "No route found to patch children into: routeId = " + routeId);
4578
- let dataChildren = convertRoutesToDataRoutes(children, mapRouteProperties, [routeId, "patch", String(((_route$children = route.children) == null ? void 0 : _route$children.length) || "0")], manifest);
4579
- if (route.children) {
4580
- route.children.push(...dataChildren);
4581
- } else {
4582
- route.children = dataChildren;
4567
+ if (!route.children) {
4568
+ route.children = [];
4583
4569
  }
4570
+ childrenToPatch = route.children;
4584
4571
  } else {
4585
- let dataChildren = convertRoutesToDataRoutes(children, mapRouteProperties, ["patch", String(routesToUse.length || "0")], manifest);
4586
- routesToUse.push(...dataChildren);
4572
+ childrenToPatch = routesToUse;
4573
+ }
4574
+
4575
+ // Don't patch in routes we already know about so that `patch` is idempotent
4576
+ // to simplify user-land code. This is useful because we re-call the
4577
+ // `patchRoutesOnNavigation` function for matched routes with params.
4578
+ let uniqueChildren = children.filter(newRoute => !childrenToPatch.some(existingRoute => isSameRoute(newRoute, existingRoute)));
4579
+ let newRoutes = convertRoutesToDataRoutes(uniqueChildren, mapRouteProperties, [routeId || "_", "patch", String(((_childrenToPatch = childrenToPatch) == null ? void 0 : _childrenToPatch.length) || "0")], manifest);
4580
+ childrenToPatch.push(...newRoutes);
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;
4587
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
+ });
4588
4605
  }
4589
4606
 
4590
4607
  /**
@@ -4641,10 +4658,10 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
4641
4658
  }
4642
4659
 
4643
4660
  // Default implementation of `dataStrategy` which fetches all loaders in parallel
4644
- async function defaultDataStrategy(_ref6) {
4661
+ async function defaultDataStrategy(_ref4) {
4645
4662
  let {
4646
4663
  matches
4647
- } = _ref6;
4664
+ } = _ref4;
4648
4665
  let matchesToLoad = matches.filter(m => m.shouldLoad);
4649
4666
  let results = await Promise.all(matchesToLoad.map(m => m.resolve()));
4650
4667
  return results.reduce((acc, result, i) => Object.assign(acc, {
@@ -4858,7 +4875,7 @@ async function convertDataStrategyResultToDataResult(dataStrategyResult) {
4858
4875
  };
4859
4876
  }
4860
4877
 
4861
- // Convert thrown unstable_data() to ErrorResponse instances
4878
+ // Convert thrown data() to ErrorResponse instances
4862
4879
  result = new ErrorResponseImpl(((_result$init2 = result.init) == null ? void 0 : _result$init2.status) || 500, undefined, result.data);
4863
4880
  }
4864
4881
  return {
@@ -5058,7 +5075,7 @@ function processRouteLoaderData(matches, results, pendingActionResult, activeDef
5058
5075
  loaderHeaders
5059
5076
  };
5060
5077
  }
5061
- function processLoaderData(state, matches, matchesToLoad, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
5078
+ function processLoaderData(state, matches, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
5062
5079
  let {
5063
5080
  loaderData,
5064
5081
  errors
@@ -5174,7 +5191,7 @@ function getInternalRouterError(status, _temp5) {
5174
5191
  if (status === 400) {
5175
5192
  statusText = "Bad Request";
5176
5193
  if (type === "route-discovery") {
5177
- errorMessage = "Unable to match URL \"" + pathname + "\" - the `unstable_patchRoutesOnNavigation()` " + ("function threw the following error:\n" + message);
5194
+ errorMessage = "Unable to match URL \"" + pathname + "\" - the `patchRoutesOnNavigation()` " + ("function threw the following error:\n" + message);
5178
5195
  } else if (method && pathname && routeId) {
5179
5196
  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.";
5180
5197
  } else if (type === "defer-action") {
@@ -5237,9 +5254,6 @@ function isHashChangeOnly(a, b) {
5237
5254
  // /page#hash -> /page
5238
5255
  return false;
5239
5256
  }
5240
- function isPromise(val) {
5241
- return typeof val === "object" && val != null && "then" in val;
5242
- }
5243
5257
  function isDataStrategyResult(result) {
5244
5258
  return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
5245
5259
  }
@@ -5557,6 +5571,7 @@ exports.createMemoryHistory = createMemoryHistory;
5557
5571
  exports.createPath = createPath;
5558
5572
  exports.createRouter = createRouter;
5559
5573
  exports.createStaticHandler = createStaticHandler;
5574
+ exports.data = data;
5560
5575
  exports.defer = defer;
5561
5576
  exports.generatePath = generatePath;
5562
5577
  exports.getStaticContextFromError = getStaticContextFromError;
@@ -5576,5 +5591,4 @@ exports.replace = replace;
5576
5591
  exports.resolvePath = resolvePath;
5577
5592
  exports.resolveTo = resolveTo;
5578
5593
  exports.stripBasename = stripBasename;
5579
- exports.unstable_data = data;
5580
5594
  //# sourceMappingURL=router.cjs.js.map