@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.
package/dist/router.js CHANGED
@@ -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
  *
@@ -1362,8 +1362,8 @@ function createRouter(init) {
1362
1362
  let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
1363
1363
  let inFlightDataRoutes;
1364
1364
  let basename = init.basename || "/";
1365
- let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
1366
- let patchRoutesOnNavigationImpl = init.unstable_patchRoutesOnNavigation;
1365
+ let dataStrategyImpl = init.dataStrategy || defaultDataStrategy;
1366
+ let patchRoutesOnNavigationImpl = init.patchRoutesOnNavigation;
1367
1367
  // Config driven behavior flags
1368
1368
  let future = _extends({
1369
1369
  v7_fetcherPersist: false,
@@ -1377,10 +1377,6 @@ function createRouter(init) {
1377
1377
  let unlistenHistory = null;
1378
1378
  // Externally-provided functions to call on all state changes
1379
1379
  let subscribers = new Set();
1380
- // FIFO queue of previously discovered routes to prevent re-calling on
1381
- // subsequent navigations to the same path
1382
- let discoveredRoutesMaxSize = 1000;
1383
- let discoveredRoutes = new Set();
1384
1380
  // Externally-provided object to hold scroll restoration locations during routing
1385
1381
  let savedScrollPositions = null;
1386
1382
  // Externally-provided function to get scroll restoration keys
@@ -1449,24 +1445,12 @@ function createRouter(init) {
1449
1445
  // were marked for explicit hydration
1450
1446
  let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
1451
1447
  let errors = init.hydrationData ? init.hydrationData.errors : null;
1452
- let isRouteInitialized = m => {
1453
- // No loader, nothing to initialize
1454
- if (!m.route.loader) {
1455
- return true;
1456
- }
1457
- // Explicitly opting-in to running on hydration
1458
- if (typeof m.route.loader === "function" && m.route.loader.hydrate === true) {
1459
- return false;
1460
- }
1461
- // Otherwise, initialized if hydrated with data or an error
1462
- return loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined;
1463
- };
1464
1448
  // If errors exist, don't consider routes below the boundary
1465
1449
  if (errors) {
1466
1450
  let idx = initialMatches.findIndex(m => errors[m.route.id] !== undefined);
1467
- initialized = initialMatches.slice(0, idx + 1).every(isRouteInitialized);
1451
+ initialized = initialMatches.slice(0, idx + 1).every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
1468
1452
  } else {
1469
- initialized = initialMatches.every(isRouteInitialized);
1453
+ initialized = initialMatches.every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
1470
1454
  }
1471
1455
  } else {
1472
1456
  // Without partial hydration - we're initialized if we were provided any
@@ -1545,9 +1529,6 @@ function createRouter(init) {
1545
1529
  // Store blocker functions in a separate Map outside of router state since
1546
1530
  // we don't need to update UI state if they change
1547
1531
  let blockerFunctions = new Map();
1548
- // Map of pending patchRoutesOnNavigation() promises (keyed by path/matches) so
1549
- // that we only kick them off once for a given combo
1550
- let pendingPatchRoutes = new Map();
1551
1532
  // Flag to ignore the next history update, so we can revert the URL change on
1552
1533
  // a POP navigation that was blocked by the user without touching router state
1553
1534
  let unblockBlockerHistoryUpdate = undefined;
@@ -1677,8 +1658,8 @@ function createRouter(init) {
1677
1658
  // we don't get ourselves into a loop calling the new subscriber immediately
1678
1659
  [...subscribers].forEach(subscriber => subscriber(state, {
1679
1660
  deletedFetchers: deletedFetchersKeys,
1680
- unstable_viewTransitionOpts: opts.viewTransitionOpts,
1681
- unstable_flushSync: opts.flushSync === true
1661
+ viewTransitionOpts: opts.viewTransitionOpts,
1662
+ flushSync: opts.flushSync === true
1682
1663
  }));
1683
1664
  // Remove idle fetchers from state since we only care about in-flight fetchers.
1684
1665
  if (future.v7_fetcherPersist) {
@@ -1827,7 +1808,7 @@ function createRouter(init) {
1827
1808
  historyAction = Action.Replace;
1828
1809
  }
1829
1810
  let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
1830
- let flushSync = (opts && opts.unstable_flushSync) === true;
1811
+ let flushSync = (opts && opts.flushSync) === true;
1831
1812
  let blockerKey = shouldBlockNavigation({
1832
1813
  currentLocation,
1833
1814
  nextLocation,
@@ -1865,7 +1846,7 @@ function createRouter(init) {
1865
1846
  pendingError: error,
1866
1847
  preventScrollReset,
1867
1848
  replace: opts && opts.replace,
1868
- enableViewTransition: opts && opts.unstable_viewTransition,
1849
+ enableViewTransition: opts && opts.viewTransition,
1869
1850
  flushSync
1870
1851
  });
1871
1852
  }
@@ -1945,7 +1926,7 @@ function createRouter(init) {
1945
1926
  // Short circuit if it's only a hash change and not a revalidation or
1946
1927
  // mutation submission.
1947
1928
  //
1948
- // Ignore on initial page loads because since the initial load will always
1929
+ // Ignore on initial page loads because since the initial hydration will always
1949
1930
  // be "same hash". For example, on /page#hash and submit a <Form method="post">
1950
1931
  // which will default to a navigation to /page
1951
1932
  if (state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
@@ -2249,9 +2230,7 @@ function createRouter(init) {
2249
2230
  });
2250
2231
  }
2251
2232
  revalidatingFetchers.forEach(rf => {
2252
- if (fetchControllers.has(rf.key)) {
2253
- abortFetcher(rf.key);
2254
- }
2233
+ abortFetcher(rf.key);
2255
2234
  if (rf.controller) {
2256
2235
  // Fetchers use an independent AbortController so that aborting a fetcher
2257
2236
  // (via deleteFetcher) does not abort the triggering navigation that
@@ -2307,7 +2286,7 @@ function createRouter(init) {
2307
2286
  let {
2308
2287
  loaderData,
2309
2288
  errors
2310
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
2289
+ } = processLoaderData(state, matches, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
2311
2290
  // Wire up subscribers to update loaderData as promises settle
2312
2291
  activeDeferreds.forEach((deferredData, routeId) => {
2313
2292
  deferredData.subscribe(aborted => {
@@ -2319,17 +2298,9 @@ function createRouter(init) {
2319
2298
  }
2320
2299
  });
2321
2300
  });
2322
- // During partial hydration, preserve SSR errors for routes that don't re-run
2301
+ // Preserve SSR errors during partial hydration
2323
2302
  if (future.v7_partialHydration && initialHydration && state.errors) {
2324
- Object.entries(state.errors).filter(_ref2 => {
2325
- let [id] = _ref2;
2326
- return !matchesToLoad.some(m => m.route.id === id);
2327
- }).forEach(_ref3 => {
2328
- let [routeId, error] = _ref3;
2329
- errors = Object.assign(errors || {}, {
2330
- [routeId]: error
2331
- });
2332
- });
2303
+ errors = _extends({}, state.errors, errors);
2333
2304
  }
2334
2305
  let updatedFetchers = markFetchRedirectsDone();
2335
2306
  let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
@@ -2371,8 +2342,8 @@ function createRouter(init) {
2371
2342
  if (isServer) {
2372
2343
  throw new Error("router.fetch() was called during the server render, but it shouldn't be. " + "You are likely calling a useFetcher() method in the body of your component. " + "Try moving it to a useEffect or a callback.");
2373
2344
  }
2374
- if (fetchControllers.has(key)) abortFetcher(key);
2375
- let flushSync = (opts && opts.unstable_flushSync) === true;
2345
+ abortFetcher(key);
2346
+ let flushSync = (opts && opts.flushSync) === true;
2376
2347
  let routesToUse = inFlightDataRoutes || dataRoutes;
2377
2348
  let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, future.v7_relativeSplatPath, routeId, opts == null ? void 0 : opts.relative);
2378
2349
  let matches = matchRoutes(routesToUse, normalizedPath, basename);
@@ -2400,9 +2371,9 @@ function createRouter(init) {
2400
2371
  return;
2401
2372
  }
2402
2373
  let match = getTargetMatch(matches, path);
2403
- pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2374
+ let preventScrollReset = (opts && opts.preventScrollReset) === true;
2404
2375
  if (submission && isMutationMethod(submission.formMethod)) {
2405
- handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2376
+ handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
2406
2377
  return;
2407
2378
  }
2408
2379
  // Store off the match so we can call it's shouldRevalidate on subsequent
@@ -2411,11 +2382,11 @@ function createRouter(init) {
2411
2382
  routeId,
2412
2383
  path
2413
2384
  });
2414
- handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2385
+ handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
2415
2386
  }
2416
2387
  // Call the action for the matched fetcher.submit(), and then handle redirects,
2417
2388
  // errors, and revalidation
2418
- async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, submission) {
2389
+ async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, preventScrollReset, submission) {
2419
2390
  interruptActiveLoads();
2420
2391
  fetchLoadMatches.delete(key);
2421
2392
  function detectAndHandle405Error(m) {
@@ -2505,7 +2476,8 @@ function createRouter(init) {
2505
2476
  fetchRedirectIds.add(key);
2506
2477
  updateFetcherState(key, getLoadingFetcher(submission));
2507
2478
  return startRedirectNavigation(fetchRequest, actionResult, false, {
2508
- fetcherSubmission: submission
2479
+ fetcherSubmission: submission,
2480
+ preventScrollReset
2509
2481
  });
2510
2482
  }
2511
2483
  }
@@ -2540,9 +2512,7 @@ function createRouter(init) {
2540
2512
  let existingFetcher = state.fetchers.get(staleKey);
2541
2513
  let revalidatingFetcher = getLoadingFetcher(undefined, existingFetcher ? existingFetcher.data : undefined);
2542
2514
  state.fetchers.set(staleKey, revalidatingFetcher);
2543
- if (fetchControllers.has(staleKey)) {
2544
- abortFetcher(staleKey);
2545
- }
2515
+ abortFetcher(staleKey);
2546
2516
  if (rf.controller) {
2547
2517
  fetchControllers.set(staleKey, rf.controller);
2548
2518
  }
@@ -2565,7 +2535,9 @@ function createRouter(init) {
2565
2535
  revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
2566
2536
  let redirect = findRedirect(loaderResults);
2567
2537
  if (redirect) {
2568
- return startRedirectNavigation(revalidationRequest, redirect.result, false);
2538
+ return startRedirectNavigation(revalidationRequest, redirect.result, false, {
2539
+ preventScrollReset
2540
+ });
2569
2541
  }
2570
2542
  redirect = findRedirect(fetcherResults);
2571
2543
  if (redirect) {
@@ -2573,13 +2545,15 @@ function createRouter(init) {
2573
2545
  // fetchRedirectIds so it doesn't get revalidated on the next set of
2574
2546
  // loader executions
2575
2547
  fetchRedirectIds.add(redirect.key);
2576
- return startRedirectNavigation(revalidationRequest, redirect.result, false);
2548
+ return startRedirectNavigation(revalidationRequest, redirect.result, false, {
2549
+ preventScrollReset
2550
+ });
2577
2551
  }
2578
2552
  // Process and commit output from loaders
2579
2553
  let {
2580
2554
  loaderData,
2581
2555
  errors
2582
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
2556
+ } = processLoaderData(state, matches, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
2583
2557
  // Since we let revalidations complete even if the submitting fetcher was
2584
2558
  // deleted, only put it back to idle if it hasn't been deleted
2585
2559
  if (state.fetchers.has(key)) {
@@ -2612,7 +2586,7 @@ function createRouter(init) {
2612
2586
  }
2613
2587
  }
2614
2588
  // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
2615
- async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, submission) {
2589
+ async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, preventScrollReset, submission) {
2616
2590
  let existingFetcher = state.fetchers.get(key);
2617
2591
  updateFetcherState(key, getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined), {
2618
2592
  flushSync
@@ -2678,7 +2652,9 @@ function createRouter(init) {
2678
2652
  return;
2679
2653
  } else {
2680
2654
  fetchRedirectIds.add(key);
2681
- await startRedirectNavigation(fetchRequest, result, false);
2655
+ await startRedirectNavigation(fetchRequest, result, false, {
2656
+ preventScrollReset
2657
+ });
2682
2658
  return;
2683
2659
  }
2684
2660
  }
@@ -2714,6 +2690,7 @@ function createRouter(init) {
2714
2690
  let {
2715
2691
  submission,
2716
2692
  fetcherSubmission,
2693
+ preventScrollReset,
2717
2694
  replace
2718
2695
  } = _temp2 === void 0 ? {} : _temp2;
2719
2696
  if (redirect.response.headers.has("X-Remix-Revalidate")) {
@@ -2771,7 +2748,7 @@ function createRouter(init) {
2771
2748
  formAction: location
2772
2749
  }),
2773
2750
  // Preserve these flags across redirects
2774
- preventScrollReset: pendingPreventScrollReset,
2751
+ preventScrollReset: preventScrollReset || pendingPreventScrollReset,
2775
2752
  enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
2776
2753
  });
2777
2754
  } else {
@@ -2783,7 +2760,7 @@ function createRouter(init) {
2783
2760
  // Send fetcher submissions through for shouldRevalidate
2784
2761
  fetcherSubmission,
2785
2762
  // Preserve these flags across redirects
2786
- preventScrollReset: pendingPreventScrollReset,
2763
+ preventScrollReset: preventScrollReset || pendingPreventScrollReset,
2787
2764
  enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
2788
2765
  });
2789
2766
  }
@@ -2860,8 +2837,8 @@ function createRouter(init) {
2860
2837
  fetchLoadMatches.forEach((_, key) => {
2861
2838
  if (fetchControllers.has(key)) {
2862
2839
  cancelledFetcherLoads.add(key);
2863
- abortFetcher(key);
2864
2840
  }
2841
+ abortFetcher(key);
2865
2842
  });
2866
2843
  }
2867
2844
  function updateFetcherState(key, fetcher, opts) {
@@ -2934,9 +2911,10 @@ function createRouter(init) {
2934
2911
  }
2935
2912
  function abortFetcher(key) {
2936
2913
  let controller = fetchControllers.get(key);
2937
- invariant(controller, "Expected fetch controller: " + key);
2938
- controller.abort();
2939
- fetchControllers.delete(key);
2914
+ if (controller) {
2915
+ controller.abort();
2916
+ fetchControllers.delete(key);
2917
+ }
2940
2918
  }
2941
2919
  function markFetchersDone(keys) {
2942
2920
  for (let key of keys) {
@@ -2999,12 +2977,12 @@ function createRouter(init) {
2999
2977
  blockers
3000
2978
  });
3001
2979
  }
3002
- function shouldBlockNavigation(_ref4) {
2980
+ function shouldBlockNavigation(_ref2) {
3003
2981
  let {
3004
2982
  currentLocation,
3005
2983
  nextLocation,
3006
2984
  historyAction
3007
- } = _ref4;
2985
+ } = _ref2;
3008
2986
  if (blockerFunctions.size === 0) {
3009
2987
  return;
3010
2988
  }
@@ -3121,15 +3099,6 @@ function createRouter(init) {
3121
3099
  }
3122
3100
  function checkFogOfWar(matches, routesToUse, pathname) {
3123
3101
  if (patchRoutesOnNavigationImpl) {
3124
- // Don't bother re-calling patchRouteOnMiss for a path we've already
3125
- // processed. the last execution would have patched the route tree
3126
- // accordingly so `matches` here are already accurate.
3127
- if (discoveredRoutes.has(pathname)) {
3128
- return {
3129
- active: false,
3130
- matches
3131
- };
3132
- }
3133
3102
  if (!matches) {
3134
3103
  let fogMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
3135
3104
  return {
@@ -3155,12 +3124,26 @@ function createRouter(init) {
3155
3124
  };
3156
3125
  }
3157
3126
  async function discoverRoutes(matches, pathname, signal) {
3127
+ if (!patchRoutesOnNavigationImpl) {
3128
+ return {
3129
+ type: "success",
3130
+ matches
3131
+ };
3132
+ }
3158
3133
  let partialMatches = matches;
3159
3134
  while (true) {
3160
3135
  let isNonHMR = inFlightDataRoutes == null;
3161
3136
  let routesToUse = inFlightDataRoutes || dataRoutes;
3137
+ let localManifest = manifest;
3162
3138
  try {
3163
- await loadLazyRouteChildren(patchRoutesOnNavigationImpl, pathname, partialMatches, routesToUse, manifest, mapRouteProperties, pendingPatchRoutes, signal);
3139
+ await patchRoutesOnNavigationImpl({
3140
+ path: pathname,
3141
+ matches: partialMatches,
3142
+ patch: (routeId, children) => {
3143
+ if (signal.aborted) return;
3144
+ patchRoutesImpl(routeId, children, routesToUse, localManifest, mapRouteProperties);
3145
+ }
3146
+ });
3164
3147
  } catch (e) {
3165
3148
  return {
3166
3149
  type: "error",
@@ -3174,7 +3157,7 @@ function createRouter(init) {
3174
3157
  // trigger a re-run of memoized `router.routes` dependencies.
3175
3158
  // HMR will already update the identity and reflow when it lands
3176
3159
  // `inFlightDataRoutes` in `completeNavigation`
3177
- if (isNonHMR) {
3160
+ if (isNonHMR && !signal.aborted) {
3178
3161
  dataRoutes = [...dataRoutes];
3179
3162
  }
3180
3163
  }
@@ -3185,7 +3168,6 @@ function createRouter(init) {
3185
3168
  }
3186
3169
  let newMatches = matchRoutes(routesToUse, pathname, basename);
3187
3170
  if (newMatches) {
3188
- addToFifoQueue(pathname, discoveredRoutes);
3189
3171
  return {
3190
3172
  type: "success",
3191
3173
  matches: newMatches
@@ -3194,7 +3176,6 @@ function createRouter(init) {
3194
3176
  let newPartialMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
3195
3177
  // Avoid loops if the second pass results in the same partial matches
3196
3178
  if (!newPartialMatches || partialMatches.length === newPartialMatches.length && partialMatches.every((m, i) => m.route.id === newPartialMatches[i].route.id)) {
3197
- addToFifoQueue(pathname, discoveredRoutes);
3198
3179
  return {
3199
3180
  type: "success",
3200
3181
  matches: null
@@ -3203,13 +3184,6 @@ function createRouter(init) {
3203
3184
  partialMatches = newPartialMatches;
3204
3185
  }
3205
3186
  }
3206
- function addToFifoQueue(path, queue) {
3207
- if (queue.size >= discoveredRoutesMaxSize) {
3208
- let first = queue.values().next().value;
3209
- queue.delete(first);
3210
- }
3211
- queue.add(path);
3212
- }
3213
3187
  function _internalSetRoutes(newRoutes) {
3214
3188
  manifest = {};
3215
3189
  inFlightDataRoutes = convertRoutesToDataRoutes(newRoutes, mapRouteProperties, undefined, manifest);
@@ -3325,7 +3299,7 @@ function createStaticHandler(routes, opts) {
3325
3299
  let {
3326
3300
  requestContext,
3327
3301
  skipLoaderErrorBubbling,
3328
- unstable_dataStrategy
3302
+ dataStrategy
3329
3303
  } = _temp3 === void 0 ? {} : _temp3;
3330
3304
  let url = new URL(request.url);
3331
3305
  let method = request.method;
@@ -3377,7 +3351,7 @@ function createStaticHandler(routes, opts) {
3377
3351
  activeDeferreds: null
3378
3352
  };
3379
3353
  }
3380
- let result = await queryImpl(request, location, matches, requestContext, unstable_dataStrategy || null, skipLoaderErrorBubbling === true, null);
3354
+ let result = await queryImpl(request, location, matches, requestContext, dataStrategy || null, skipLoaderErrorBubbling === true, null);
3381
3355
  if (isResponse(result)) {
3382
3356
  return result;
3383
3357
  }
@@ -3419,7 +3393,7 @@ function createStaticHandler(routes, opts) {
3419
3393
  let {
3420
3394
  routeId,
3421
3395
  requestContext,
3422
- unstable_dataStrategy
3396
+ dataStrategy
3423
3397
  } = _temp4 === void 0 ? {} : _temp4;
3424
3398
  let url = new URL(request.url);
3425
3399
  let method = request.method;
@@ -3447,7 +3421,7 @@ function createStaticHandler(routes, opts) {
3447
3421
  pathname: location.pathname
3448
3422
  });
3449
3423
  }
3450
- let result = await queryImpl(request, location, matches, requestContext, unstable_dataStrategy || null, false, match);
3424
+ let result = await queryImpl(request, location, matches, requestContext, dataStrategy || null, false, match);
3451
3425
  if (isResponse(result)) {
3452
3426
  return result;
3453
3427
  }
@@ -3473,14 +3447,14 @@ function createStaticHandler(routes, opts) {
3473
3447
  }
3474
3448
  return undefined;
3475
3449
  }
3476
- async function queryImpl(request, location, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch) {
3450
+ async function queryImpl(request, location, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch) {
3477
3451
  invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
3478
3452
  try {
3479
3453
  if (isMutationMethod(request.method.toLowerCase())) {
3480
- let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch != null);
3454
+ let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch != null);
3481
3455
  return result;
3482
3456
  }
3483
- let result = await loadRouteData(request, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch);
3457
+ let result = await loadRouteData(request, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch);
3484
3458
  return isResponse(result) ? result : _extends({}, result, {
3485
3459
  actionData: null,
3486
3460
  actionHeaders: {}
@@ -3503,7 +3477,7 @@ function createStaticHandler(routes, opts) {
3503
3477
  throw e;
3504
3478
  }
3505
3479
  }
3506
- async function submit(request, matches, actionMatch, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, isRouteRequest) {
3480
+ async function submit(request, matches, actionMatch, requestContext, dataStrategy, skipLoaderErrorBubbling, isRouteRequest) {
3507
3481
  let result;
3508
3482
  if (!actionMatch.route.action && !actionMatch.route.lazy) {
3509
3483
  let error = getInternalRouterError(405, {
@@ -3519,7 +3493,7 @@ function createStaticHandler(routes, opts) {
3519
3493
  error
3520
3494
  };
3521
3495
  } else {
3522
- let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, unstable_dataStrategy);
3496
+ let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, dataStrategy);
3523
3497
  result = results[actionMatch.route.id];
3524
3498
  if (request.signal.aborted) {
3525
3499
  throwStaticHandlerAbortedError(request, isRouteRequest, future);
@@ -3580,7 +3554,7 @@ function createStaticHandler(routes, opts) {
3580
3554
  // Store off the pending error - we use it to determine which loaders
3581
3555
  // to call and will commit it when we complete the navigation
3582
3556
  let boundaryMatch = skipLoaderErrorBubbling ? actionMatch : findNearestBoundary(matches, actionMatch.route.id);
3583
- let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
3557
+ let context = await loadRouteData(loaderRequest, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
3584
3558
  // action status codes take precedence over loader status codes
3585
3559
  return _extends({}, context, {
3586
3560
  statusCode: isRouteErrorResponse(result.error) ? result.error.status : result.statusCode != null ? result.statusCode : 500,
@@ -3590,7 +3564,7 @@ function createStaticHandler(routes, opts) {
3590
3564
  } : {})
3591
3565
  });
3592
3566
  }
3593
- let context = await loadRouteData(loaderRequest, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, null);
3567
+ let context = await loadRouteData(loaderRequest, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, null);
3594
3568
  return _extends({}, context, {
3595
3569
  actionData: {
3596
3570
  [actionMatch.route.id]: result.data
@@ -3603,7 +3577,7 @@ function createStaticHandler(routes, opts) {
3603
3577
  } : {}
3604
3578
  });
3605
3579
  }
3606
- async function loadRouteData(request, matches, requestContext, unstable_dataStrategy, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
3580
+ async function loadRouteData(request, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
3607
3581
  let isRouteRequest = routeMatch != null;
3608
3582
  // Short circuit if we have no loaders to run (queryRoute())
3609
3583
  if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
@@ -3631,7 +3605,7 @@ function createStaticHandler(routes, opts) {
3631
3605
  activeDeferreds: null
3632
3606
  };
3633
3607
  }
3634
- let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy);
3608
+ let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext, dataStrategy);
3635
3609
  if (request.signal.aborted) {
3636
3610
  throwStaticHandlerAbortedError(request, isRouteRequest, future);
3637
3611
  }
@@ -3652,8 +3626,8 @@ function createStaticHandler(routes, opts) {
3652
3626
  }
3653
3627
  // Utility wrapper for calling dataStrategy server-side without having to
3654
3628
  // pass around the manifest, mapRouteProperties, etc.
3655
- async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy) {
3656
- let results = await callDataStrategyImpl(unstable_dataStrategy || defaultDataStrategy, type, null, request, matchesToLoad, matches, null, manifest, mapRouteProperties, requestContext);
3629
+ async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, dataStrategy) {
3630
+ let results = await callDataStrategyImpl(dataStrategy || defaultDataStrategy, type, null, request, matchesToLoad, matches, null, manifest, mapRouteProperties, requestContext);
3657
3631
  let dataResults = {};
3658
3632
  await Promise.all(matches.map(async match => {
3659
3633
  if (!(match.route.id in results)) {
@@ -3734,9 +3708,21 @@ function normalizeTo(location, matches, basename, prependBasename, to, v7_relati
3734
3708
  path.search = location.search;
3735
3709
  path.hash = location.hash;
3736
3710
  }
3737
- // Add an ?index param for matched index routes if we don't already have one
3738
- if ((to == null || to === "" || to === ".") && activeRouteMatch && activeRouteMatch.route.index && !hasNakedIndexQuery(path.search)) {
3739
- path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
3711
+ // Account for `?index` params when routing to the current location
3712
+ if ((to == null || to === "" || to === ".") && activeRouteMatch) {
3713
+ let nakedIndex = hasNakedIndexQuery(path.search);
3714
+ if (activeRouteMatch.route.index && !nakedIndex) {
3715
+ // Add one when we're targeting an index route
3716
+ path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
3717
+ } else if (!activeRouteMatch.route.index && nakedIndex) {
3718
+ // Remove existing ones when we're not
3719
+ let params = new URLSearchParams(path.search);
3720
+ let indexValues = params.getAll("index");
3721
+ params.delete("index");
3722
+ indexValues.filter(v => v).forEach(v => params.append("index", v));
3723
+ let qs = params.toString();
3724
+ path.search = qs ? "?" + qs : "";
3725
+ }
3740
3726
  }
3741
3727
  // If we're operating within a basename, prepend it to the pathname. If
3742
3728
  // this is a root navigation, then just use the raw basename which allows
@@ -3782,8 +3768,8 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
3782
3768
  }
3783
3769
  let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?
3784
3770
  // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data
3785
- Array.from(opts.body.entries()).reduce((acc, _ref5) => {
3786
- let [name, value] = _ref5;
3771
+ Array.from(opts.body.entries()).reduce((acc, _ref3) => {
3772
+ let [name, value] = _ref3;
3787
3773
  return "" + acc + name + "=" + value + "\n";
3788
3774
  }, "") : String(opts.body);
3789
3775
  return {
@@ -3871,25 +3857,36 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
3871
3857
  submission
3872
3858
  };
3873
3859
  }
3874
- // Filter out all routes below any caught error as they aren't going to
3860
+ // Filter out all routes at/below any caught error as they aren't going to
3875
3861
  // render so we don't need to load them
3876
- function getLoaderMatchesUntilBoundary(matches, boundaryId) {
3877
- let boundaryMatches = matches;
3878
- if (boundaryId) {
3879
- let index = matches.findIndex(m => m.route.id === boundaryId);
3880
- if (index >= 0) {
3881
- boundaryMatches = matches.slice(0, index);
3882
- }
3862
+ function getLoaderMatchesUntilBoundary(matches, boundaryId, includeBoundary) {
3863
+ if (includeBoundary === void 0) {
3864
+ includeBoundary = false;
3883
3865
  }
3884
- return boundaryMatches;
3866
+ let index = matches.findIndex(m => m.route.id === boundaryId);
3867
+ if (index >= 0) {
3868
+ return matches.slice(0, includeBoundary ? index + 1 : index);
3869
+ }
3870
+ return matches;
3885
3871
  }
3886
- function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
3872
+ function getMatchesToLoad(history, state, matches, submission, location, initialHydration, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
3887
3873
  let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
3888
3874
  let currentUrl = history.createURL(state.location);
3889
3875
  let nextUrl = history.createURL(location);
3890
3876
  // Pick navigation matches that are net-new or qualify for revalidation
3891
- let boundaryId = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[0] : undefined;
3892
- let boundaryMatches = boundaryId ? getLoaderMatchesUntilBoundary(matches, boundaryId) : matches;
3877
+ let boundaryMatches = matches;
3878
+ if (initialHydration && state.errors) {
3879
+ // On initial hydration, only consider matches up to _and including_ the boundary.
3880
+ // This is inclusive to handle cases where a server loader ran successfully,
3881
+ // a child server loader bubbled up to this route, but this route has
3882
+ // `clientLoader.hydrate` so we want to still run the `clientLoader` so that
3883
+ // we have a complete version of `loaderData`
3884
+ boundaryMatches = getLoaderMatchesUntilBoundary(matches, Object.keys(state.errors)[0], true);
3885
+ } else if (pendingActionResult && isErrorResult(pendingActionResult[1])) {
3886
+ // If an action threw an error, we call loaders up to, but not including the
3887
+ // boundary
3888
+ boundaryMatches = getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]);
3889
+ }
3893
3890
  // Don't revalidate loaders by default after action 4xx/5xx responses
3894
3891
  // when the flag is enabled. They can still opt-into revalidation via
3895
3892
  // `shouldRevalidate` via `actionResult`
@@ -3906,13 +3903,8 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
3906
3903
  if (route.loader == null) {
3907
3904
  return false;
3908
3905
  }
3909
- if (isInitialLoad) {
3910
- if (typeof route.loader !== "function" || route.loader.hydrate) {
3911
- return true;
3912
- }
3913
- return state.loaderData[route.id] === undefined && (
3914
- // Don't re-run if the loader ran and threw an error
3915
- !state.errors || state.errors[route.id] === undefined);
3906
+ if (initialHydration) {
3907
+ return shouldLoadRouteOnHydration(route, state.loaderData, state.errors);
3916
3908
  }
3917
3909
  // Always call the loader on new route instances and pending defer cancellations
3918
3910
  if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
@@ -3943,11 +3935,11 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
3943
3935
  let revalidatingFetchers = [];
3944
3936
  fetchLoadMatches.forEach((f, key) => {
3945
3937
  // Don't revalidate:
3946
- // - on initial load (shouldn't be any fetchers then anyway)
3938
+ // - on initial hydration (shouldn't be any fetchers then anyway)
3947
3939
  // - if fetcher won't be present in the subsequent render
3948
3940
  // - no longer matches the URL (v7_fetcherPersist=false)
3949
3941
  // - was unmounted but persisted due to v7_fetcherPersist=true
3950
- if (isInitialLoad || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
3942
+ if (initialHydration || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
3951
3943
  return;
3952
3944
  }
3953
3945
  let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
@@ -4011,6 +4003,28 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
4011
4003
  });
4012
4004
  return [navigationMatches, revalidatingFetchers];
4013
4005
  }
4006
+ function shouldLoadRouteOnHydration(route, loaderData, errors) {
4007
+ // We dunno if we have a loader - gotta find out!
4008
+ if (route.lazy) {
4009
+ return true;
4010
+ }
4011
+ // No loader, nothing to initialize
4012
+ if (!route.loader) {
4013
+ return false;
4014
+ }
4015
+ let hasData = loaderData != null && loaderData[route.id] !== undefined;
4016
+ let hasError = errors != null && errors[route.id] !== undefined;
4017
+ // Don't run if we error'd during SSR
4018
+ if (!hasData && hasError) {
4019
+ return false;
4020
+ }
4021
+ // Explicitly opting-in to running on hydration
4022
+ if (typeof route.loader === "function" && route.loader.hydrate === true) {
4023
+ return true;
4024
+ }
4025
+ // Otherwise, run if we're not yet initialized with anything
4026
+ return !hasData && !hasError;
4027
+ }
4014
4028
  function isNewLoader(currentLoaderData, currentMatch, match) {
4015
4029
  let isNew =
4016
4030
  // [a] -> [a, b]
@@ -4042,48 +4056,46 @@ function shouldRevalidateLoader(loaderMatch, arg) {
4042
4056
  }
4043
4057
  return arg.defaultShouldRevalidate;
4044
4058
  }
4045
- /**
4046
- * Idempotent utility to execute patchRoutesOnNavigation() to lazily load route
4047
- * definitions and update the routes/routeManifest
4048
- */
4049
- async function loadLazyRouteChildren(patchRoutesOnNavigationImpl, path, matches, routes, manifest, mapRouteProperties, pendingRouteChildren, signal) {
4050
- let key = [path, ...matches.map(m => m.route.id)].join("-");
4051
- try {
4052
- let pending = pendingRouteChildren.get(key);
4053
- if (!pending) {
4054
- pending = patchRoutesOnNavigationImpl({
4055
- path,
4056
- matches,
4057
- patch: (routeId, children) => {
4058
- if (!signal.aborted) {
4059
- patchRoutesImpl(routeId, children, routes, manifest, mapRouteProperties);
4060
- }
4061
- }
4062
- });
4063
- pendingRouteChildren.set(key, pending);
4064
- }
4065
- if (pending && isPromise(pending)) {
4066
- await pending;
4067
- }
4068
- } finally {
4069
- pendingRouteChildren.delete(key);
4070
- }
4071
- }
4072
4059
  function patchRoutesImpl(routeId, children, routesToUse, manifest, mapRouteProperties) {
4060
+ var _childrenToPatch;
4061
+ let childrenToPatch;
4073
4062
  if (routeId) {
4074
- var _route$children;
4075
4063
  let route = manifest[routeId];
4076
4064
  invariant(route, "No route found to patch children into: routeId = " + routeId);
4077
- let dataChildren = convertRoutesToDataRoutes(children, mapRouteProperties, [routeId, "patch", String(((_route$children = route.children) == null ? void 0 : _route$children.length) || "0")], manifest);
4078
- if (route.children) {
4079
- route.children.push(...dataChildren);
4080
- } else {
4081
- route.children = dataChildren;
4065
+ if (!route.children) {
4066
+ route.children = [];
4082
4067
  }
4068
+ childrenToPatch = route.children;
4083
4069
  } else {
4084
- let dataChildren = convertRoutesToDataRoutes(children, mapRouteProperties, ["patch", String(routesToUse.length || "0")], manifest);
4085
- routesToUse.push(...dataChildren);
4070
+ childrenToPatch = routesToUse;
4071
+ }
4072
+ // Don't patch in routes we already know about so that `patch` is idempotent
4073
+ // to simplify user-land code. This is useful because we re-call the
4074
+ // `patchRoutesOnNavigation` function for matched routes with params.
4075
+ let uniqueChildren = children.filter(newRoute => !childrenToPatch.some(existingRoute => isSameRoute(newRoute, existingRoute)));
4076
+ let newRoutes = convertRoutesToDataRoutes(uniqueChildren, mapRouteProperties, [routeId || "_", "patch", String(((_childrenToPatch = childrenToPatch) == null ? void 0 : _childrenToPatch.length) || "0")], manifest);
4077
+ childrenToPatch.push(...newRoutes);
4078
+ }
4079
+ function isSameRoute(newRoute, existingRoute) {
4080
+ // Most optimal check is by id
4081
+ if ("id" in newRoute && "id" in existingRoute && newRoute.id === existingRoute.id) {
4082
+ return true;
4083
+ }
4084
+ // Second is by pathing differences
4085
+ if (!(newRoute.index === existingRoute.index && newRoute.path === existingRoute.path && newRoute.caseSensitive === existingRoute.caseSensitive)) {
4086
+ return false;
4086
4087
  }
4088
+ // Pathless layout routes are trickier since we need to check children.
4089
+ // If they have no children then they're the same as far as we can tell
4090
+ if ((!newRoute.children || newRoute.children.length === 0) && (!existingRoute.children || existingRoute.children.length === 0)) {
4091
+ return true;
4092
+ }
4093
+ // Otherwise, we look to see if every child in the new route is already
4094
+ // represented in the existing route's children
4095
+ return newRoute.children.every((aChild, i) => {
4096
+ var _existingRoute$childr;
4097
+ return (_existingRoute$childr = existingRoute.children) == null ? void 0 : _existingRoute$childr.some(bChild => isSameRoute(aChild, bChild));
4098
+ });
4087
4099
  }
4088
4100
  /**
4089
4101
  * Execute route.lazy() methods to lazily load route modules (loader, action,
@@ -4134,10 +4146,10 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
4134
4146
  }));
4135
4147
  }
4136
4148
  // Default implementation of `dataStrategy` which fetches all loaders in parallel
4137
- async function defaultDataStrategy(_ref6) {
4149
+ async function defaultDataStrategy(_ref4) {
4138
4150
  let {
4139
4151
  matches
4140
- } = _ref6;
4152
+ } = _ref4;
4141
4153
  let matchesToLoad = matches.filter(m => m.shouldLoad);
4142
4154
  let results = await Promise.all(matchesToLoad.map(m => m.resolve()));
4143
4155
  return results.reduce((acc, result, i) => Object.assign(acc, {
@@ -4346,7 +4358,7 @@ async function convertDataStrategyResultToDataResult(dataStrategyResult) {
4346
4358
  statusCode: (_result$init = result.init) == null ? void 0 : _result$init.status
4347
4359
  };
4348
4360
  }
4349
- // Convert thrown unstable_data() to ErrorResponse instances
4361
+ // Convert thrown data() to ErrorResponse instances
4350
4362
  result = new ErrorResponseImpl(((_result$init2 = result.init) == null ? void 0 : _result$init2.status) || 500, undefined, result.data);
4351
4363
  }
4352
4364
  return {
@@ -4540,7 +4552,7 @@ function processRouteLoaderData(matches, results, pendingActionResult, activeDef
4540
4552
  loaderHeaders
4541
4553
  };
4542
4554
  }
4543
- function processLoaderData(state, matches, matchesToLoad, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
4555
+ function processLoaderData(state, matches, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
4544
4556
  let {
4545
4557
  loaderData,
4546
4558
  errors
@@ -4653,7 +4665,7 @@ function getInternalRouterError(status, _temp5) {
4653
4665
  if (status === 400) {
4654
4666
  statusText = "Bad Request";
4655
4667
  if (type === "route-discovery") {
4656
- errorMessage = "Unable to match URL \"" + pathname + "\" - the `unstable_patchRoutesOnNavigation()` " + ("function threw the following error:\n" + message);
4668
+ errorMessage = "Unable to match URL \"" + pathname + "\" - the `patchRoutesOnNavigation()` " + ("function threw the following error:\n" + message);
4657
4669
  } else if (method && pathname && routeId) {
4658
4670
  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.";
4659
4671
  } else if (type === "defer-action") {
@@ -4714,9 +4726,6 @@ function isHashChangeOnly(a, b) {
4714
4726
  // /page#hash -> /page
4715
4727
  return false;
4716
4728
  }
4717
- function isPromise(val) {
4718
- return typeof val === "object" && val != null && "then" in val;
4719
- }
4720
4729
  function isDataStrategyResult(result) {
4721
4730
  return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
4722
4731
  }
@@ -5014,5 +5023,5 @@ function persistAppliedTransitions(_window, transitions) {
5014
5023
  }
5015
5024
  //#endregion
5016
5025
 
5017
- export { AbortedDeferredError, Action, IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION, UNSAFE_DEFERRED_SYMBOL, DeferredData as UNSAFE_DeferredData, ErrorResponseImpl as UNSAFE_ErrorResponseImpl, convertRouteMatchToUiMatch as UNSAFE_convertRouteMatchToUiMatch, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, decodePath as UNSAFE_decodePath, getResolveToMatches as UNSAFE_getResolveToMatches, invariant as UNSAFE_invariant, warning as UNSAFE_warning, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, createRouter, createStaticHandler, defer, generatePath, getStaticContextFromError, getToPathname, isDataWithResponseInit, isDeferredData, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, parsePath, redirect, redirectDocument, replace, resolvePath, resolveTo, stripBasename, data as unstable_data };
5026
+ export { AbortedDeferredError, Action, IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION, UNSAFE_DEFERRED_SYMBOL, DeferredData as UNSAFE_DeferredData, ErrorResponseImpl as UNSAFE_ErrorResponseImpl, convertRouteMatchToUiMatch as UNSAFE_convertRouteMatchToUiMatch, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, decodePath as UNSAFE_decodePath, getResolveToMatches as UNSAFE_getResolveToMatches, invariant as UNSAFE_invariant, warning as UNSAFE_warning, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, createRouter, createStaticHandler, data, defer, generatePath, getStaticContextFromError, getToPathname, isDataWithResponseInit, isDeferredData, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, parsePath, redirect, redirectDocument, replace, resolvePath, resolveTo, stripBasename };
5018
5027
  //# sourceMappingURL=router.js.map