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

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.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1713,8 +1713,8 @@
1713
1713
  let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
1714
1714
  let inFlightDataRoutes;
1715
1715
  let basename = init.basename || "/";
1716
- let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
1717
- let patchRoutesOnNavigationImpl = init.unstable_patchRoutesOnNavigation;
1716
+ let dataStrategyImpl = init.dataStrategy || defaultDataStrategy;
1717
+ let patchRoutesOnNavigationImpl = init.patchRoutesOnNavigation;
1718
1718
 
1719
1719
  // Config driven behavior flags
1720
1720
  let future = _extends({
@@ -1729,10 +1729,6 @@
1729
1729
  let unlistenHistory = null;
1730
1730
  // Externally-provided functions to call on all state changes
1731
1731
  let subscribers = new Set();
1732
- // FIFO queue of previously discovered routes to prevent re-calling on
1733
- // subsequent navigations to the same path
1734
- let discoveredRoutesMaxSize = 1000;
1735
- let discoveredRoutes = new Set();
1736
1732
  // Externally-provided object to hold scroll restoration locations during routing
1737
1733
  let savedScrollPositions = null;
1738
1734
  // Externally-provided function to get scroll restoration keys
@@ -1803,25 +1799,12 @@
1803
1799
  // were marked for explicit hydration
1804
1800
  let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
1805
1801
  let errors = init.hydrationData ? init.hydrationData.errors : null;
1806
- let isRouteInitialized = m => {
1807
- // No loader, nothing to initialize
1808
- if (!m.route.loader) {
1809
- return true;
1810
- }
1811
- // Explicitly opting-in to running on hydration
1812
- if (typeof m.route.loader === "function" && m.route.loader.hydrate === true) {
1813
- return false;
1814
- }
1815
- // Otherwise, initialized if hydrated with data or an error
1816
- return loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined;
1817
- };
1818
-
1819
1802
  // If errors exist, don't consider routes below the boundary
1820
1803
  if (errors) {
1821
1804
  let idx = initialMatches.findIndex(m => errors[m.route.id] !== undefined);
1822
- initialized = initialMatches.slice(0, idx + 1).every(isRouteInitialized);
1805
+ initialized = initialMatches.slice(0, idx + 1).every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
1823
1806
  } else {
1824
- initialized = initialMatches.every(isRouteInitialized);
1807
+ initialized = initialMatches.every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
1825
1808
  }
1826
1809
  } else {
1827
1810
  // Without partial hydration - we're initialized if we were provided any
@@ -1921,10 +1904,6 @@
1921
1904
  // we don't need to update UI state if they change
1922
1905
  let blockerFunctions = new Map();
1923
1906
 
1924
- // Map of pending patchRoutesOnNavigation() promises (keyed by path/matches) so
1925
- // that we only kick them off once for a given combo
1926
- let pendingPatchRoutes = new Map();
1927
-
1928
1907
  // Flag to ignore the next history update, so we can revert the URL change on
1929
1908
  // a POP navigation that was blocked by the user without touching router state
1930
1909
  let unblockBlockerHistoryUpdate = undefined;
@@ -2062,8 +2041,8 @@
2062
2041
  // we don't get ourselves into a loop calling the new subscriber immediately
2063
2042
  [...subscribers].forEach(subscriber => subscriber(state, {
2064
2043
  deletedFetchers: deletedFetchersKeys,
2065
- unstable_viewTransitionOpts: opts.viewTransitionOpts,
2066
- unstable_flushSync: opts.flushSync === true
2044
+ viewTransitionOpts: opts.viewTransitionOpts,
2045
+ flushSync: opts.flushSync === true
2067
2046
  }));
2068
2047
 
2069
2048
  // Remove idle fetchers from state since we only care about in-flight fetchers.
@@ -2223,7 +2202,7 @@
2223
2202
  historyAction = Action.Replace;
2224
2203
  }
2225
2204
  let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
2226
- let flushSync = (opts && opts.unstable_flushSync) === true;
2205
+ let flushSync = (opts && opts.flushSync) === true;
2227
2206
  let blockerKey = shouldBlockNavigation({
2228
2207
  currentLocation,
2229
2208
  nextLocation,
@@ -2261,7 +2240,7 @@
2261
2240
  pendingError: error,
2262
2241
  preventScrollReset,
2263
2242
  replace: opts && opts.replace,
2264
- enableViewTransition: opts && opts.unstable_viewTransition,
2243
+ enableViewTransition: opts && opts.viewTransition,
2265
2244
  flushSync
2266
2245
  });
2267
2246
  }
@@ -2349,7 +2328,7 @@
2349
2328
  // Short circuit if it's only a hash change and not a revalidation or
2350
2329
  // mutation submission.
2351
2330
  //
2352
- // 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
2353
2332
  // be "same hash". For example, on /page#hash and submit a <Form method="post">
2354
2333
  // which will default to a navigation to /page
2355
2334
  if (state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
@@ -2456,15 +2435,12 @@
2456
2435
  shortCircuited: true
2457
2436
  };
2458
2437
  } else if (discoverResult.type === "error") {
2459
- let {
2460
- boundaryId,
2461
- error
2462
- } = handleDiscoverRouteError(location.pathname, discoverResult);
2438
+ let boundaryId = findNearestBoundary(discoverResult.partialMatches).route.id;
2463
2439
  return {
2464
2440
  matches: discoverResult.partialMatches,
2465
2441
  pendingActionResult: [boundaryId, {
2466
2442
  type: ResultType.error,
2467
- error
2443
+ error: discoverResult.error
2468
2444
  }]
2469
2445
  };
2470
2446
  } else if (!discoverResult.matches) {
@@ -2594,15 +2570,12 @@
2594
2570
  shortCircuited: true
2595
2571
  };
2596
2572
  } else if (discoverResult.type === "error") {
2597
- let {
2598
- boundaryId,
2599
- error
2600
- } = handleDiscoverRouteError(location.pathname, discoverResult);
2573
+ let boundaryId = findNearestBoundary(discoverResult.partialMatches).route.id;
2601
2574
  return {
2602
2575
  matches: discoverResult.partialMatches,
2603
2576
  loaderData: {},
2604
2577
  errors: {
2605
- [boundaryId]: error
2578
+ [boundaryId]: discoverResult.error
2606
2579
  }
2607
2580
  };
2608
2581
  } else if (!discoverResult.matches) {
@@ -2668,9 +2641,7 @@
2668
2641
  });
2669
2642
  }
2670
2643
  revalidatingFetchers.forEach(rf => {
2671
- if (fetchControllers.has(rf.key)) {
2672
- abortFetcher(rf.key);
2673
- }
2644
+ abortFetcher(rf.key);
2674
2645
  if (rf.controller) {
2675
2646
  // Fetchers use an independent AbortController so that aborting a fetcher
2676
2647
  // (via deleteFetcher) does not abort the triggering navigation that
@@ -2730,7 +2701,7 @@
2730
2701
  let {
2731
2702
  loaderData,
2732
2703
  errors
2733
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
2704
+ } = processLoaderData(state, matches, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
2734
2705
 
2735
2706
  // Wire up subscribers to update loaderData as promises settle
2736
2707
  activeDeferreds.forEach((deferredData, routeId) => {
@@ -2744,17 +2715,9 @@
2744
2715
  });
2745
2716
  });
2746
2717
 
2747
- // During partial hydration, preserve SSR errors for routes that don't re-run
2718
+ // Preserve SSR errors during partial hydration
2748
2719
  if (future.v7_partialHydration && initialHydration && state.errors) {
2749
- Object.entries(state.errors).filter(_ref2 => {
2750
- let [id] = _ref2;
2751
- return !matchesToLoad.some(m => m.route.id === id);
2752
- }).forEach(_ref3 => {
2753
- let [routeId, error] = _ref3;
2754
- errors = Object.assign(errors || {}, {
2755
- [routeId]: error
2756
- });
2757
- });
2720
+ errors = _extends({}, state.errors, errors);
2758
2721
  }
2759
2722
  let updatedFetchers = markFetchRedirectsDone();
2760
2723
  let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
@@ -2797,8 +2760,8 @@
2797
2760
  if (isServer) {
2798
2761
  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.");
2799
2762
  }
2800
- if (fetchControllers.has(key)) abortFetcher(key);
2801
- let flushSync = (opts && opts.unstable_flushSync) === true;
2763
+ abortFetcher(key);
2764
+ let flushSync = (opts && opts.flushSync) === true;
2802
2765
  let routesToUse = inFlightDataRoutes || dataRoutes;
2803
2766
  let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, future.v7_relativeSplatPath, routeId, opts == null ? void 0 : opts.relative);
2804
2767
  let matches = matchRoutes(routesToUse, normalizedPath, basename);
@@ -2826,9 +2789,9 @@
2826
2789
  return;
2827
2790
  }
2828
2791
  let match = getTargetMatch(matches, path);
2829
- pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2792
+ let preventScrollReset = (opts && opts.preventScrollReset) === true;
2830
2793
  if (submission && isMutationMethod(submission.formMethod)) {
2831
- handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2794
+ handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
2832
2795
  return;
2833
2796
  }
2834
2797
 
@@ -2838,12 +2801,12 @@
2838
2801
  routeId,
2839
2802
  path
2840
2803
  });
2841
- handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2804
+ handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
2842
2805
  }
2843
2806
 
2844
2807
  // Call the action for the matched fetcher.submit(), and then handle redirects,
2845
2808
  // errors, and revalidation
2846
- async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, submission) {
2809
+ async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, preventScrollReset, submission) {
2847
2810
  interruptActiveLoads();
2848
2811
  fetchLoadMatches.delete(key);
2849
2812
  function detectAndHandle405Error(m) {
@@ -2876,10 +2839,7 @@
2876
2839
  if (discoverResult.type === "aborted") {
2877
2840
  return;
2878
2841
  } else if (discoverResult.type === "error") {
2879
- let {
2880
- error
2881
- } = handleDiscoverRouteError(path, discoverResult);
2882
- setFetcherError(key, routeId, error, {
2842
+ setFetcherError(key, routeId, discoverResult.error, {
2883
2843
  flushSync
2884
2844
  });
2885
2845
  return;
@@ -2936,7 +2896,8 @@
2936
2896
  fetchRedirectIds.add(key);
2937
2897
  updateFetcherState(key, getLoadingFetcher(submission));
2938
2898
  return startRedirectNavigation(fetchRequest, actionResult, false, {
2939
- fetcherSubmission: submission
2899
+ fetcherSubmission: submission,
2900
+ preventScrollReset
2940
2901
  });
2941
2902
  }
2942
2903
  }
@@ -2974,9 +2935,7 @@
2974
2935
  let existingFetcher = state.fetchers.get(staleKey);
2975
2936
  let revalidatingFetcher = getLoadingFetcher(undefined, existingFetcher ? existingFetcher.data : undefined);
2976
2937
  state.fetchers.set(staleKey, revalidatingFetcher);
2977
- if (fetchControllers.has(staleKey)) {
2978
- abortFetcher(staleKey);
2979
- }
2938
+ abortFetcher(staleKey);
2980
2939
  if (rf.controller) {
2981
2940
  fetchControllers.set(staleKey, rf.controller);
2982
2941
  }
@@ -2999,7 +2958,9 @@
2999
2958
  revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
3000
2959
  let redirect = findRedirect(loaderResults);
3001
2960
  if (redirect) {
3002
- return startRedirectNavigation(revalidationRequest, redirect.result, false);
2961
+ return startRedirectNavigation(revalidationRequest, redirect.result, false, {
2962
+ preventScrollReset
2963
+ });
3003
2964
  }
3004
2965
  redirect = findRedirect(fetcherResults);
3005
2966
  if (redirect) {
@@ -3007,14 +2968,16 @@
3007
2968
  // fetchRedirectIds so it doesn't get revalidated on the next set of
3008
2969
  // loader executions
3009
2970
  fetchRedirectIds.add(redirect.key);
3010
- return startRedirectNavigation(revalidationRequest, redirect.result, false);
2971
+ return startRedirectNavigation(revalidationRequest, redirect.result, false, {
2972
+ preventScrollReset
2973
+ });
3011
2974
  }
3012
2975
 
3013
2976
  // Process and commit output from loaders
3014
2977
  let {
3015
2978
  loaderData,
3016
2979
  errors
3017
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
2980
+ } = processLoaderData(state, matches, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
3018
2981
 
3019
2982
  // Since we let revalidations complete even if the submitting fetcher was
3020
2983
  // deleted, only put it back to idle if it hasn't been deleted
@@ -3050,7 +3013,7 @@
3050
3013
  }
3051
3014
 
3052
3015
  // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
3053
- async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, submission) {
3016
+ async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, preventScrollReset, submission) {
3054
3017
  let existingFetcher = state.fetchers.get(key);
3055
3018
  updateFetcherState(key, getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined), {
3056
3019
  flushSync
@@ -3062,10 +3025,7 @@
3062
3025
  if (discoverResult.type === "aborted") {
3063
3026
  return;
3064
3027
  } else if (discoverResult.type === "error") {
3065
- let {
3066
- error
3067
- } = handleDiscoverRouteError(path, discoverResult);
3068
- setFetcherError(key, routeId, error, {
3028
+ setFetcherError(key, routeId, discoverResult.error, {
3069
3029
  flushSync
3070
3030
  });
3071
3031
  return;
@@ -3121,7 +3081,9 @@
3121
3081
  return;
3122
3082
  } else {
3123
3083
  fetchRedirectIds.add(key);
3124
- await startRedirectNavigation(fetchRequest, result, false);
3084
+ await startRedirectNavigation(fetchRequest, result, false, {
3085
+ preventScrollReset
3086
+ });
3125
3087
  return;
3126
3088
  }
3127
3089
  }
@@ -3160,6 +3122,7 @@
3160
3122
  let {
3161
3123
  submission,
3162
3124
  fetcherSubmission,
3125
+ preventScrollReset,
3163
3126
  replace
3164
3127
  } = _temp2 === void 0 ? {} : _temp2;
3165
3128
  if (redirect.response.headers.has("X-Remix-Revalidate")) {
@@ -3220,7 +3183,7 @@
3220
3183
  formAction: location
3221
3184
  }),
3222
3185
  // Preserve these flags across redirects
3223
- preventScrollReset: pendingPreventScrollReset,
3186
+ preventScrollReset: preventScrollReset || pendingPreventScrollReset,
3224
3187
  enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
3225
3188
  });
3226
3189
  } else {
@@ -3232,7 +3195,7 @@
3232
3195
  // Send fetcher submissions through for shouldRevalidate
3233
3196
  fetcherSubmission,
3234
3197
  // Preserve these flags across redirects
3235
- preventScrollReset: pendingPreventScrollReset,
3198
+ preventScrollReset: preventScrollReset || pendingPreventScrollReset,
3236
3199
  enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
3237
3200
  });
3238
3201
  }
@@ -3313,8 +3276,8 @@
3313
3276
  fetchLoadMatches.forEach((_, key) => {
3314
3277
  if (fetchControllers.has(key)) {
3315
3278
  cancelledFetcherLoads.add(key);
3316
- abortFetcher(key);
3317
3279
  }
3280
+ abortFetcher(key);
3318
3281
  });
3319
3282
  }
3320
3283
  function updateFetcherState(key, fetcher, opts) {
@@ -3387,9 +3350,10 @@
3387
3350
  }
3388
3351
  function abortFetcher(key) {
3389
3352
  let controller = fetchControllers.get(key);
3390
- invariant(controller, "Expected fetch controller: " + key);
3391
- controller.abort();
3392
- fetchControllers.delete(key);
3353
+ if (controller) {
3354
+ controller.abort();
3355
+ fetchControllers.delete(key);
3356
+ }
3393
3357
  }
3394
3358
  function markFetchersDone(keys) {
3395
3359
  for (let key of keys) {
@@ -3454,12 +3418,12 @@
3454
3418
  blockers
3455
3419
  });
3456
3420
  }
3457
- function shouldBlockNavigation(_ref4) {
3421
+ function shouldBlockNavigation(_ref2) {
3458
3422
  let {
3459
3423
  currentLocation,
3460
3424
  nextLocation,
3461
3425
  historyAction
3462
- } = _ref4;
3426
+ } = _ref2;
3463
3427
  if (blockerFunctions.size === 0) {
3464
3428
  return;
3465
3429
  }
@@ -3506,16 +3470,6 @@
3506
3470
  error
3507
3471
  };
3508
3472
  }
3509
- function handleDiscoverRouteError(pathname, discoverResult) {
3510
- return {
3511
- boundaryId: findNearestBoundary(discoverResult.partialMatches).route.id,
3512
- error: getInternalRouterError(400, {
3513
- type: "route-discovery",
3514
- pathname,
3515
- message: discoverResult.error != null && "message" in discoverResult.error ? discoverResult.error : String(discoverResult.error)
3516
- })
3517
- };
3518
- }
3519
3473
  function cancelActiveDeferreds(predicate) {
3520
3474
  let cancelledRouteIds = [];
3521
3475
  activeDeferreds.forEach((dfd, routeId) => {
@@ -3581,15 +3535,6 @@
3581
3535
  }
3582
3536
  function checkFogOfWar(matches, routesToUse, pathname) {
3583
3537
  if (patchRoutesOnNavigationImpl) {
3584
- // Don't bother re-calling patchRouteOnMiss for a path we've already
3585
- // processed. the last execution would have patched the route tree
3586
- // accordingly so `matches` here are already accurate.
3587
- if (discoveredRoutes.has(pathname)) {
3588
- return {
3589
- active: false,
3590
- matches
3591
- };
3592
- }
3593
3538
  if (!matches) {
3594
3539
  let fogMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
3595
3540
  return {
@@ -3615,12 +3560,26 @@
3615
3560
  };
3616
3561
  }
3617
3562
  async function discoverRoutes(matches, pathname, signal) {
3563
+ if (!patchRoutesOnNavigationImpl) {
3564
+ return {
3565
+ type: "success",
3566
+ matches
3567
+ };
3568
+ }
3618
3569
  let partialMatches = matches;
3619
3570
  while (true) {
3620
3571
  let isNonHMR = inFlightDataRoutes == null;
3621
3572
  let routesToUse = inFlightDataRoutes || dataRoutes;
3573
+ let localManifest = manifest;
3622
3574
  try {
3623
- await loadLazyRouteChildren(patchRoutesOnNavigationImpl, pathname, partialMatches, routesToUse, manifest, mapRouteProperties, pendingPatchRoutes, signal);
3575
+ await patchRoutesOnNavigationImpl({
3576
+ path: pathname,
3577
+ matches: partialMatches,
3578
+ patch: (routeId, children) => {
3579
+ if (signal.aborted) return;
3580
+ patchRoutesImpl(routeId, children, routesToUse, localManifest, mapRouteProperties);
3581
+ }
3582
+ });
3624
3583
  } catch (e) {
3625
3584
  return {
3626
3585
  type: "error",
@@ -3634,7 +3593,7 @@
3634
3593
  // trigger a re-run of memoized `router.routes` dependencies.
3635
3594
  // HMR will already update the identity and reflow when it lands
3636
3595
  // `inFlightDataRoutes` in `completeNavigation`
3637
- if (isNonHMR) {
3596
+ if (isNonHMR && !signal.aborted) {
3638
3597
  dataRoutes = [...dataRoutes];
3639
3598
  }
3640
3599
  }
@@ -3645,7 +3604,6 @@
3645
3604
  }
3646
3605
  let newMatches = matchRoutes(routesToUse, pathname, basename);
3647
3606
  if (newMatches) {
3648
- addToFifoQueue(pathname, discoveredRoutes);
3649
3607
  return {
3650
3608
  type: "success",
3651
3609
  matches: newMatches
@@ -3655,7 +3613,6 @@
3655
3613
 
3656
3614
  // Avoid loops if the second pass results in the same partial matches
3657
3615
  if (!newPartialMatches || partialMatches.length === newPartialMatches.length && partialMatches.every((m, i) => m.route.id === newPartialMatches[i].route.id)) {
3658
- addToFifoQueue(pathname, discoveredRoutes);
3659
3616
  return {
3660
3617
  type: "success",
3661
3618
  matches: null
@@ -3664,13 +3621,6 @@
3664
3621
  partialMatches = newPartialMatches;
3665
3622
  }
3666
3623
  }
3667
- function addToFifoQueue(path, queue) {
3668
- if (queue.size >= discoveredRoutesMaxSize) {
3669
- let first = queue.values().next().value;
3670
- queue.delete(first);
3671
- }
3672
- queue.add(path);
3673
- }
3674
3624
  function _internalSetRoutes(newRoutes) {
3675
3625
  manifest = {};
3676
3626
  inFlightDataRoutes = convertRoutesToDataRoutes(newRoutes, mapRouteProperties, undefined, manifest);
@@ -3795,7 +3745,7 @@
3795
3745
  let {
3796
3746
  requestContext,
3797
3747
  skipLoaderErrorBubbling,
3798
- unstable_dataStrategy
3748
+ dataStrategy
3799
3749
  } = _temp3 === void 0 ? {} : _temp3;
3800
3750
  let url = new URL(request.url);
3801
3751
  let method = request.method;
@@ -3848,7 +3798,7 @@
3848
3798
  activeDeferreds: null
3849
3799
  };
3850
3800
  }
3851
- let result = await queryImpl(request, location, matches, requestContext, unstable_dataStrategy || null, skipLoaderErrorBubbling === true, null);
3801
+ let result = await queryImpl(request, location, matches, requestContext, dataStrategy || null, skipLoaderErrorBubbling === true, null);
3852
3802
  if (isResponse(result)) {
3853
3803
  return result;
3854
3804
  }
@@ -3892,7 +3842,7 @@
3892
3842
  let {
3893
3843
  routeId,
3894
3844
  requestContext,
3895
- unstable_dataStrategy
3845
+ dataStrategy
3896
3846
  } = _temp4 === void 0 ? {} : _temp4;
3897
3847
  let url = new URL(request.url);
3898
3848
  let method = request.method;
@@ -3921,7 +3871,7 @@
3921
3871
  pathname: location.pathname
3922
3872
  });
3923
3873
  }
3924
- let result = await queryImpl(request, location, matches, requestContext, unstable_dataStrategy || null, false, match);
3874
+ let result = await queryImpl(request, location, matches, requestContext, dataStrategy || null, false, match);
3925
3875
  if (isResponse(result)) {
3926
3876
  return result;
3927
3877
  }
@@ -3948,14 +3898,14 @@
3948
3898
  }
3949
3899
  return undefined;
3950
3900
  }
3951
- async function queryImpl(request, location, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch) {
3901
+ async function queryImpl(request, location, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch) {
3952
3902
  invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
3953
3903
  try {
3954
3904
  if (isMutationMethod(request.method.toLowerCase())) {
3955
- let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch != null);
3905
+ let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch != null);
3956
3906
  return result;
3957
3907
  }
3958
- let result = await loadRouteData(request, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch);
3908
+ let result = await loadRouteData(request, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch);
3959
3909
  return isResponse(result) ? result : _extends({}, result, {
3960
3910
  actionData: null,
3961
3911
  actionHeaders: {}
@@ -3978,7 +3928,7 @@
3978
3928
  throw e;
3979
3929
  }
3980
3930
  }
3981
- async function submit(request, matches, actionMatch, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, isRouteRequest) {
3931
+ async function submit(request, matches, actionMatch, requestContext, dataStrategy, skipLoaderErrorBubbling, isRouteRequest) {
3982
3932
  let result;
3983
3933
  if (!actionMatch.route.action && !actionMatch.route.lazy) {
3984
3934
  let error = getInternalRouterError(405, {
@@ -3994,7 +3944,7 @@
3994
3944
  error
3995
3945
  };
3996
3946
  } else {
3997
- let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, unstable_dataStrategy);
3947
+ let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, dataStrategy);
3998
3948
  result = results[actionMatch.route.id];
3999
3949
  if (request.signal.aborted) {
4000
3950
  throwStaticHandlerAbortedError(request, isRouteRequest, future);
@@ -4056,7 +4006,7 @@
4056
4006
  // Store off the pending error - we use it to determine which loaders
4057
4007
  // to call and will commit it when we complete the navigation
4058
4008
  let boundaryMatch = skipLoaderErrorBubbling ? actionMatch : findNearestBoundary(matches, actionMatch.route.id);
4059
- let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
4009
+ let context = await loadRouteData(loaderRequest, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
4060
4010
 
4061
4011
  // action status codes take precedence over loader status codes
4062
4012
  return _extends({}, context, {
@@ -4067,7 +4017,7 @@
4067
4017
  } : {})
4068
4018
  });
4069
4019
  }
4070
- let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, null);
4020
+ let context = await loadRouteData(loaderRequest, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, null);
4071
4021
  return _extends({}, context, {
4072
4022
  actionData: {
4073
4023
  [actionMatch.route.id]: result.data
@@ -4080,7 +4030,7 @@
4080
4030
  } : {}
4081
4031
  });
4082
4032
  }
4083
- async function loadRouteData(request, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
4033
+ async function loadRouteData(request, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
4084
4034
  let isRouteRequest = routeMatch != null;
4085
4035
 
4086
4036
  // Short circuit if we have no loaders to run (queryRoute())
@@ -4110,7 +4060,7 @@
4110
4060
  activeDeferreds: null
4111
4061
  };
4112
4062
  }
4113
- let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy);
4063
+ let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext, dataStrategy);
4114
4064
  if (request.signal.aborted) {
4115
4065
  throwStaticHandlerAbortedError(request, isRouteRequest, future);
4116
4066
  }
@@ -4134,8 +4084,8 @@
4134
4084
 
4135
4085
  // Utility wrapper for calling dataStrategy server-side without having to
4136
4086
  // pass around the manifest, mapRouteProperties, etc.
4137
- async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy) {
4138
- let results = await callDataStrategyImpl(unstable_dataStrategy || defaultDataStrategy, type, null, request, matchesToLoad, matches, null, manifest, mapRouteProperties, requestContext);
4087
+ async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, dataStrategy) {
4088
+ let results = await callDataStrategyImpl(dataStrategy || defaultDataStrategy, type, null, request, matchesToLoad, matches, null, manifest, mapRouteProperties, requestContext);
4139
4089
  let dataResults = {};
4140
4090
  await Promise.all(matches.map(async match => {
4141
4091
  if (!(match.route.id in results)) {
@@ -4222,9 +4172,21 @@
4222
4172
  path.hash = location.hash;
4223
4173
  }
4224
4174
 
4225
- // Add an ?index param for matched index routes if we don't already have one
4226
- if ((to == null || to === "" || to === ".") && activeRouteMatch && activeRouteMatch.route.index && !hasNakedIndexQuery(path.search)) {
4227
- path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
4175
+ // Account for `?index` params when routing to the current location
4176
+ if ((to == null || to === "" || to === ".") && activeRouteMatch) {
4177
+ let nakedIndex = hasNakedIndexQuery(path.search);
4178
+ if (activeRouteMatch.route.index && !nakedIndex) {
4179
+ // Add one when we're targeting an index route
4180
+ path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
4181
+ } else if (!activeRouteMatch.route.index && nakedIndex) {
4182
+ // Remove existing ones when we're not
4183
+ let params = new URLSearchParams(path.search);
4184
+ let indexValues = params.getAll("index");
4185
+ params.delete("index");
4186
+ indexValues.filter(v => v).forEach(v => params.append("index", v));
4187
+ let qs = params.toString();
4188
+ path.search = qs ? "?" + qs : "";
4189
+ }
4228
4190
  }
4229
4191
 
4230
4192
  // If we're operating within a basename, prepend it to the pathname. If
@@ -4273,8 +4235,8 @@
4273
4235
  }
4274
4236
  let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?
4275
4237
  // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data
4276
- Array.from(opts.body.entries()).reduce((acc, _ref5) => {
4277
- let [name, value] = _ref5;
4238
+ Array.from(opts.body.entries()).reduce((acc, _ref3) => {
4239
+ let [name, value] = _ref3;
4278
4240
  return "" + acc + name + "=" + value + "\n";
4279
4241
  }, "") : String(opts.body);
4280
4242
  return {
@@ -4364,26 +4326,37 @@
4364
4326
  };
4365
4327
  }
4366
4328
 
4367
- // Filter out all routes below any caught error as they aren't going to
4329
+ // Filter out all routes at/below any caught error as they aren't going to
4368
4330
  // render so we don't need to load them
4369
- function getLoaderMatchesUntilBoundary(matches, boundaryId) {
4370
- let boundaryMatches = matches;
4371
- if (boundaryId) {
4372
- let index = matches.findIndex(m => m.route.id === boundaryId);
4373
- if (index >= 0) {
4374
- boundaryMatches = matches.slice(0, index);
4375
- }
4331
+ function getLoaderMatchesUntilBoundary(matches, boundaryId, includeBoundary) {
4332
+ if (includeBoundary === void 0) {
4333
+ includeBoundary = false;
4334
+ }
4335
+ let index = matches.findIndex(m => m.route.id === boundaryId);
4336
+ if (index >= 0) {
4337
+ return matches.slice(0, includeBoundary ? index + 1 : index);
4376
4338
  }
4377
- return boundaryMatches;
4339
+ return matches;
4378
4340
  }
4379
- function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
4341
+ function getMatchesToLoad(history, state, matches, submission, location, initialHydration, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
4380
4342
  let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
4381
4343
  let currentUrl = history.createURL(state.location);
4382
4344
  let nextUrl = history.createURL(location);
4383
4345
 
4384
4346
  // Pick navigation matches that are net-new or qualify for revalidation
4385
- let boundaryId = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[0] : undefined;
4386
- let boundaryMatches = boundaryId ? getLoaderMatchesUntilBoundary(matches, boundaryId) : matches;
4347
+ let boundaryMatches = matches;
4348
+ if (initialHydration && state.errors) {
4349
+ // On initial hydration, only consider matches up to _and including_ the boundary.
4350
+ // This is inclusive to handle cases where a server loader ran successfully,
4351
+ // a child server loader bubbled up to this route, but this route has
4352
+ // `clientLoader.hydrate` so we want to still run the `clientLoader` so that
4353
+ // we have a complete version of `loaderData`
4354
+ boundaryMatches = getLoaderMatchesUntilBoundary(matches, Object.keys(state.errors)[0], true);
4355
+ } else if (pendingActionResult && isErrorResult(pendingActionResult[1])) {
4356
+ // If an action threw an error, we call loaders up to, but not including the
4357
+ // boundary
4358
+ boundaryMatches = getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]);
4359
+ }
4387
4360
 
4388
4361
  // Don't revalidate loaders by default after action 4xx/5xx responses
4389
4362
  // when the flag is enabled. They can still opt-into revalidation via
@@ -4401,13 +4374,8 @@
4401
4374
  if (route.loader == null) {
4402
4375
  return false;
4403
4376
  }
4404
- if (isInitialLoad) {
4405
- if (typeof route.loader !== "function" || route.loader.hydrate) {
4406
- return true;
4407
- }
4408
- return state.loaderData[route.id] === undefined && (
4409
- // Don't re-run if the loader ran and threw an error
4410
- !state.errors || state.errors[route.id] === undefined);
4377
+ if (initialHydration) {
4378
+ return shouldLoadRouteOnHydration(route, state.loaderData, state.errors);
4411
4379
  }
4412
4380
 
4413
4381
  // Always call the loader on new route instances and pending defer cancellations
@@ -4441,11 +4409,11 @@
4441
4409
  let revalidatingFetchers = [];
4442
4410
  fetchLoadMatches.forEach((f, key) => {
4443
4411
  // Don't revalidate:
4444
- // - on initial load (shouldn't be any fetchers then anyway)
4412
+ // - on initial hydration (shouldn't be any fetchers then anyway)
4445
4413
  // - if fetcher won't be present in the subsequent render
4446
4414
  // - no longer matches the URL (v7_fetcherPersist=false)
4447
4415
  // - was unmounted but persisted due to v7_fetcherPersist=true
4448
- if (isInitialLoad || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
4416
+ if (initialHydration || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
4449
4417
  return;
4450
4418
  }
4451
4419
  let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
@@ -4511,6 +4479,32 @@
4511
4479
  });
4512
4480
  return [navigationMatches, revalidatingFetchers];
4513
4481
  }
4482
+ function shouldLoadRouteOnHydration(route, loaderData, errors) {
4483
+ // We dunno if we have a loader - gotta find out!
4484
+ if (route.lazy) {
4485
+ return true;
4486
+ }
4487
+
4488
+ // No loader, nothing to initialize
4489
+ if (!route.loader) {
4490
+ return false;
4491
+ }
4492
+ let hasData = loaderData != null && loaderData[route.id] !== undefined;
4493
+ let hasError = errors != null && errors[route.id] !== undefined;
4494
+
4495
+ // Don't run if we error'd during SSR
4496
+ if (!hasData && hasError) {
4497
+ return false;
4498
+ }
4499
+
4500
+ // Explicitly opting-in to running on hydration
4501
+ if (typeof route.loader === "function" && route.loader.hydrate === true) {
4502
+ return true;
4503
+ }
4504
+
4505
+ // Otherwise, run if we're not yet initialized with anything
4506
+ return !hasData && !hasError;
4507
+ }
4514
4508
  function isNewLoader(currentLoaderData, currentMatch, match) {
4515
4509
  let isNew =
4516
4510
  // [a] -> [a, b]
@@ -4544,49 +4538,50 @@
4544
4538
  }
4545
4539
  return arg.defaultShouldRevalidate;
4546
4540
  }
4547
-
4548
- /**
4549
- * Idempotent utility to execute patchRoutesOnNavigation() to lazily load route
4550
- * definitions and update the routes/routeManifest
4551
- */
4552
- async function loadLazyRouteChildren(patchRoutesOnNavigationImpl, path, matches, routes, manifest, mapRouteProperties, pendingRouteChildren, signal) {
4553
- let key = [path, ...matches.map(m => m.route.id)].join("-");
4554
- try {
4555
- let pending = pendingRouteChildren.get(key);
4556
- if (!pending) {
4557
- pending = patchRoutesOnNavigationImpl({
4558
- path,
4559
- matches,
4560
- patch: (routeId, children) => {
4561
- if (!signal.aborted) {
4562
- patchRoutesImpl(routeId, children, routes, manifest, mapRouteProperties);
4563
- }
4564
- }
4565
- });
4566
- pendingRouteChildren.set(key, pending);
4567
- }
4568
- if (pending && isPromise(pending)) {
4569
- await pending;
4570
- }
4571
- } finally {
4572
- pendingRouteChildren.delete(key);
4573
- }
4574
- }
4575
4541
  function patchRoutesImpl(routeId, children, routesToUse, manifest, mapRouteProperties) {
4542
+ var _childrenToPatch;
4543
+ let childrenToPatch;
4576
4544
  if (routeId) {
4577
- var _route$children;
4578
4545
  let route = manifest[routeId];
4579
4546
  invariant(route, "No route found to patch children into: routeId = " + routeId);
4580
- let dataChildren = convertRoutesToDataRoutes(children, mapRouteProperties, [routeId, "patch", String(((_route$children = route.children) == null ? void 0 : _route$children.length) || "0")], manifest);
4581
- if (route.children) {
4582
- route.children.push(...dataChildren);
4583
- } else {
4584
- route.children = dataChildren;
4547
+ if (!route.children) {
4548
+ route.children = [];
4585
4549
  }
4550
+ childrenToPatch = route.children;
4586
4551
  } else {
4587
- let dataChildren = convertRoutesToDataRoutes(children, mapRouteProperties, ["patch", String(routesToUse.length || "0")], manifest);
4588
- routesToUse.push(...dataChildren);
4552
+ childrenToPatch = routesToUse;
4589
4553
  }
4554
+
4555
+ // Don't patch in routes we already know about so that `patch` is idempotent
4556
+ // to simplify user-land code. This is useful because we re-call the
4557
+ // `patchRoutesOnNavigation` function for matched routes with params.
4558
+ let uniqueChildren = children.filter(newRoute => !childrenToPatch.some(existingRoute => isSameRoute(newRoute, existingRoute)));
4559
+ let newRoutes = convertRoutesToDataRoutes(uniqueChildren, mapRouteProperties, [routeId || "_", "patch", String(((_childrenToPatch = childrenToPatch) == null ? void 0 : _childrenToPatch.length) || "0")], manifest);
4560
+ childrenToPatch.push(...newRoutes);
4561
+ }
4562
+ function isSameRoute(newRoute, existingRoute) {
4563
+ // Most optimal check is by id
4564
+ if ("id" in newRoute && "id" in existingRoute && newRoute.id === existingRoute.id) {
4565
+ return true;
4566
+ }
4567
+
4568
+ // Second is by pathing differences
4569
+ if (!(newRoute.index === existingRoute.index && newRoute.path === existingRoute.path && newRoute.caseSensitive === existingRoute.caseSensitive)) {
4570
+ return false;
4571
+ }
4572
+
4573
+ // Pathless layout routes are trickier since we need to check children.
4574
+ // If they have no children then they're the same as far as we can tell
4575
+ if ((!newRoute.children || newRoute.children.length === 0) && (!existingRoute.children || existingRoute.children.length === 0)) {
4576
+ return true;
4577
+ }
4578
+
4579
+ // Otherwise, we look to see if every child in the new route is already
4580
+ // represented in the existing route's children
4581
+ return newRoute.children.every((aChild, i) => {
4582
+ var _existingRoute$childr;
4583
+ return (_existingRoute$childr = existingRoute.children) == null ? void 0 : _existingRoute$childr.some(bChild => isSameRoute(aChild, bChild));
4584
+ });
4590
4585
  }
4591
4586
 
4592
4587
  /**
@@ -4643,10 +4638,10 @@
4643
4638
  }
4644
4639
 
4645
4640
  // Default implementation of `dataStrategy` which fetches all loaders in parallel
4646
- async function defaultDataStrategy(_ref6) {
4641
+ async function defaultDataStrategy(_ref4) {
4647
4642
  let {
4648
4643
  matches
4649
- } = _ref6;
4644
+ } = _ref4;
4650
4645
  let matchesToLoad = matches.filter(m => m.shouldLoad);
4651
4646
  let results = await Promise.all(matchesToLoad.map(m => m.resolve()));
4652
4647
  return results.reduce((acc, result, i) => Object.assign(acc, {
@@ -4860,7 +4855,7 @@
4860
4855
  };
4861
4856
  }
4862
4857
 
4863
- // Convert thrown unstable_data() to ErrorResponse instances
4858
+ // Convert thrown data() to ErrorResponse instances
4864
4859
  result = new ErrorResponseImpl(((_result$init2 = result.init) == null ? void 0 : _result$init2.status) || 500, undefined, result.data);
4865
4860
  }
4866
4861
  return {
@@ -5060,7 +5055,7 @@
5060
5055
  loaderHeaders
5061
5056
  };
5062
5057
  }
5063
- function processLoaderData(state, matches, matchesToLoad, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
5058
+ function processLoaderData(state, matches, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
5064
5059
  let {
5065
5060
  loaderData,
5066
5061
  errors
@@ -5175,9 +5170,7 @@
5175
5170
  let errorMessage = "Unknown @remix-run/router error";
5176
5171
  if (status === 400) {
5177
5172
  statusText = "Bad Request";
5178
- if (type === "route-discovery") {
5179
- errorMessage = "Unable to match URL \"" + pathname + "\" - the `unstable_patchRoutesOnNavigation()` " + ("function threw the following error:\n" + message);
5180
- } else if (method && pathname && routeId) {
5173
+ if (method && pathname && routeId) {
5181
5174
  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.";
5182
5175
  } else if (type === "defer-action") {
5183
5176
  errorMessage = "defer() is not supported in actions";
@@ -5239,9 +5232,6 @@
5239
5232
  // /page#hash -> /page
5240
5233
  return false;
5241
5234
  }
5242
- function isPromise(val) {
5243
- return typeof val === "object" && val != null && "then" in val;
5244
- }
5245
5235
  function isDataStrategyResult(result) {
5246
5236
  return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
5247
5237
  }
@@ -5559,6 +5549,7 @@
5559
5549
  exports.createPath = createPath;
5560
5550
  exports.createRouter = createRouter;
5561
5551
  exports.createStaticHandler = createStaticHandler;
5552
+ exports.data = data;
5562
5553
  exports.defer = defer;
5563
5554
  exports.generatePath = generatePath;
5564
5555
  exports.getStaticContextFromError = getStaticContextFromError;
@@ -5578,7 +5569,6 @@
5578
5569
  exports.resolvePath = resolvePath;
5579
5570
  exports.resolveTo = resolveTo;
5580
5571
  exports.stripBasename = stripBasename;
5581
- exports.unstable_data = data;
5582
5572
 
5583
5573
  Object.defineProperty(exports, '__esModule', { value: true });
5584
5574