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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/router.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v0.0.0-experimental-bcda00aaf
2
+ * @remix-run/router v0.0.0-experimental-1116190f7
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1391,6 +1391,7 @@ function createRouter(init) {
1391
1391
  // SSR did the initial scroll restoration.
1392
1392
  let initialScrollRestored = init.hydrationData != null;
1393
1393
  let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);
1394
+ let initialMatchesIsFOW = false;
1394
1395
  let initialErrors = null;
1395
1396
  if (initialMatches == null && !patchRoutesOnNavigationImpl) {
1396
1397
  // If we do not match a user-provided-route, fall back to the root
@@ -1429,6 +1430,7 @@ function createRouter(init) {
1429
1430
  if (future.v7_partialHydration) {
1430
1431
  let fogOfWar = checkFogOfWar(null, dataRoutes, init.history.location.pathname);
1431
1432
  if (fogOfWar.active && fogOfWar.matches) {
1433
+ initialMatchesIsFOW = true;
1432
1434
  initialMatches = fogOfWar.matches;
1433
1435
  }
1434
1436
  }
@@ -1445,24 +1447,12 @@ function createRouter(init) {
1445
1447
  // were marked for explicit hydration
1446
1448
  let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
1447
1449
  let errors = init.hydrationData ? init.hydrationData.errors : null;
1448
- let isRouteInitialized = m => {
1449
- // No loader, nothing to initialize
1450
- if (!m.route.loader) {
1451
- return true;
1452
- }
1453
- // Explicitly opting-in to running on hydration
1454
- if (typeof m.route.loader === "function" && m.route.loader.hydrate === true) {
1455
- return false;
1456
- }
1457
- // Otherwise, initialized if hydrated with data or an error
1458
- return loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined;
1459
- };
1460
1450
  // If errors exist, don't consider routes below the boundary
1461
1451
  if (errors) {
1462
1452
  let idx = initialMatches.findIndex(m => errors[m.route.id] !== undefined);
1463
- initialized = initialMatches.slice(0, idx + 1).every(isRouteInitialized);
1453
+ initialized = initialMatches.slice(0, idx + 1).every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
1464
1454
  } else {
1465
- initialized = initialMatches.every(isRouteInitialized);
1455
+ initialized = initialMatches.every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
1466
1456
  }
1467
1457
  } else {
1468
1458
  // Without partial hydration - we're initialized if we were provided any
@@ -1541,9 +1531,6 @@ function createRouter(init) {
1541
1531
  // Store blocker functions in a separate Map outside of router state since
1542
1532
  // we don't need to update UI state if they change
1543
1533
  let blockerFunctions = new Map();
1544
- // Map of pending patchRoutesOnNavigation() promises (keyed by path/matches) so
1545
- // that we only kick them off once for a given combo
1546
- let pendingPatchRoutes = new Map();
1547
1534
  // Flag to ignore the next history update, so we can revert the URL change on
1548
1535
  // a POP navigation that was blocked by the user without touching router state
1549
1536
  let unblockBlockerHistoryUpdate = undefined;
@@ -1914,7 +1901,9 @@ function createRouter(init) {
1914
1901
  pendingViewTransitionEnabled = (opts && opts.enableViewTransition) === true;
1915
1902
  let routesToUse = inFlightDataRoutes || dataRoutes;
1916
1903
  let loadingNavigation = opts && opts.overrideNavigation;
1917
- let matches = isUninterruptedRevalidation && inFlightDataRoutes == null ? state.matches : matchRoutes(routesToUse, location, basename);
1904
+ let matches = opts != null && opts.initialHydration && state.matches && state.matches.length > 0 && !initialMatchesIsFOW ?
1905
+ // `matchRoutes()` has already been called if we're in here via `router.initialize()`
1906
+ state.matches : matchRoutes(routesToUse, location, basename);
1918
1907
  let flushSync = (opts && opts.flushSync) === true;
1919
1908
  let fogOfWar = checkFogOfWar(matches, routesToUse, location.pathname);
1920
1909
  if (fogOfWar.active && fogOfWar.matches) {
@@ -1941,7 +1930,7 @@ function createRouter(init) {
1941
1930
  // Short circuit if it's only a hash change and not a revalidation or
1942
1931
  // mutation submission.
1943
1932
  //
1944
- // Ignore on initial page loads because since the initial load will always
1933
+ // Ignore on initial page loads because since the initial hydration will always
1945
1934
  // be "same hash". For example, on /page#hash and submit a <Form method="post">
1946
1935
  // which will default to a navigation to /page
1947
1936
  if (state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
@@ -2041,15 +2030,12 @@ function createRouter(init) {
2041
2030
  shortCircuited: true
2042
2031
  };
2043
2032
  } else if (discoverResult.type === "error") {
2044
- let {
2045
- boundaryId,
2046
- error
2047
- } = handleDiscoverRouteError(location.pathname, discoverResult);
2033
+ let boundaryId = findNearestBoundary(discoverResult.partialMatches).route.id;
2048
2034
  return {
2049
2035
  matches: discoverResult.partialMatches,
2050
2036
  pendingActionResult: [boundaryId, {
2051
2037
  type: ResultType.error,
2052
- error
2038
+ error: discoverResult.error
2053
2039
  }]
2054
2040
  };
2055
2041
  } else if (!discoverResult.matches) {
@@ -2173,15 +2159,12 @@ function createRouter(init) {
2173
2159
  shortCircuited: true
2174
2160
  };
2175
2161
  } else if (discoverResult.type === "error") {
2176
- let {
2177
- boundaryId,
2178
- error
2179
- } = handleDiscoverRouteError(location.pathname, discoverResult);
2162
+ let boundaryId = findNearestBoundary(discoverResult.partialMatches).route.id;
2180
2163
  return {
2181
2164
  matches: discoverResult.partialMatches,
2182
2165
  loaderData: {},
2183
2166
  errors: {
2184
- [boundaryId]: error
2167
+ [boundaryId]: discoverResult.error
2185
2168
  }
2186
2169
  };
2187
2170
  } else if (!discoverResult.matches) {
@@ -2245,9 +2228,7 @@ function createRouter(init) {
2245
2228
  });
2246
2229
  }
2247
2230
  revalidatingFetchers.forEach(rf => {
2248
- if (fetchControllers.has(rf.key)) {
2249
- abortFetcher(rf.key);
2250
- }
2231
+ abortFetcher(rf.key);
2251
2232
  if (rf.controller) {
2252
2233
  // Fetchers use an independent AbortController so that aborting a fetcher
2253
2234
  // (via deleteFetcher) does not abort the triggering navigation that
@@ -2303,7 +2284,7 @@ function createRouter(init) {
2303
2284
  let {
2304
2285
  loaderData,
2305
2286
  errors
2306
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
2287
+ } = processLoaderData(state, matches, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
2307
2288
  // Wire up subscribers to update loaderData as promises settle
2308
2289
  activeDeferreds.forEach((deferredData, routeId) => {
2309
2290
  deferredData.subscribe(aborted => {
@@ -2315,17 +2296,9 @@ function createRouter(init) {
2315
2296
  }
2316
2297
  });
2317
2298
  });
2318
- // During partial hydration, preserve SSR errors for routes that don't re-run
2299
+ // Preserve SSR errors during partial hydration
2319
2300
  if (future.v7_partialHydration && initialHydration && state.errors) {
2320
- Object.entries(state.errors).filter(_ref2 => {
2321
- let [id] = _ref2;
2322
- return !matchesToLoad.some(m => m.route.id === id);
2323
- }).forEach(_ref3 => {
2324
- let [routeId, error] = _ref3;
2325
- errors = Object.assign(errors || {}, {
2326
- [routeId]: error
2327
- });
2328
- });
2301
+ errors = _extends({}, state.errors, errors);
2329
2302
  }
2330
2303
  let updatedFetchers = markFetchRedirectsDone();
2331
2304
  let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
@@ -2367,7 +2340,7 @@ function createRouter(init) {
2367
2340
  if (isServer) {
2368
2341
  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.");
2369
2342
  }
2370
- if (fetchControllers.has(key)) abortFetcher(key);
2343
+ abortFetcher(key);
2371
2344
  let flushSync = (opts && opts.flushSync) === true;
2372
2345
  let routesToUse = inFlightDataRoutes || dataRoutes;
2373
2346
  let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, future.v7_relativeSplatPath, routeId, opts == null ? void 0 : opts.relative);
@@ -2396,9 +2369,9 @@ function createRouter(init) {
2396
2369
  return;
2397
2370
  }
2398
2371
  let match = getTargetMatch(matches, path);
2399
- pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2372
+ let preventScrollReset = (opts && opts.preventScrollReset) === true;
2400
2373
  if (submission && isMutationMethod(submission.formMethod)) {
2401
- handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2374
+ handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
2402
2375
  return;
2403
2376
  }
2404
2377
  // Store off the match so we can call it's shouldRevalidate on subsequent
@@ -2407,11 +2380,11 @@ function createRouter(init) {
2407
2380
  routeId,
2408
2381
  path
2409
2382
  });
2410
- handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
2383
+ handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
2411
2384
  }
2412
2385
  // Call the action for the matched fetcher.submit(), and then handle redirects,
2413
2386
  // errors, and revalidation
2414
- async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, submission) {
2387
+ async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, preventScrollReset, submission) {
2415
2388
  interruptActiveLoads();
2416
2389
  fetchLoadMatches.delete(key);
2417
2390
  function detectAndHandle405Error(m) {
@@ -2443,10 +2416,7 @@ function createRouter(init) {
2443
2416
  if (discoverResult.type === "aborted") {
2444
2417
  return;
2445
2418
  } else if (discoverResult.type === "error") {
2446
- let {
2447
- error
2448
- } = handleDiscoverRouteError(path, discoverResult);
2449
- setFetcherError(key, routeId, error, {
2419
+ setFetcherError(key, routeId, discoverResult.error, {
2450
2420
  flushSync
2451
2421
  });
2452
2422
  return;
@@ -2501,7 +2471,8 @@ function createRouter(init) {
2501
2471
  fetchRedirectIds.add(key);
2502
2472
  updateFetcherState(key, getLoadingFetcher(submission));
2503
2473
  return startRedirectNavigation(fetchRequest, actionResult, false, {
2504
- fetcherSubmission: submission
2474
+ fetcherSubmission: submission,
2475
+ preventScrollReset
2505
2476
  });
2506
2477
  }
2507
2478
  }
@@ -2536,9 +2507,7 @@ function createRouter(init) {
2536
2507
  let existingFetcher = state.fetchers.get(staleKey);
2537
2508
  let revalidatingFetcher = getLoadingFetcher(undefined, existingFetcher ? existingFetcher.data : undefined);
2538
2509
  state.fetchers.set(staleKey, revalidatingFetcher);
2539
- if (fetchControllers.has(staleKey)) {
2540
- abortFetcher(staleKey);
2541
- }
2510
+ abortFetcher(staleKey);
2542
2511
  if (rf.controller) {
2543
2512
  fetchControllers.set(staleKey, rf.controller);
2544
2513
  }
@@ -2561,7 +2530,9 @@ function createRouter(init) {
2561
2530
  revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
2562
2531
  let redirect = findRedirect(loaderResults);
2563
2532
  if (redirect) {
2564
- return startRedirectNavigation(revalidationRequest, redirect.result, false);
2533
+ return startRedirectNavigation(revalidationRequest, redirect.result, false, {
2534
+ preventScrollReset
2535
+ });
2565
2536
  }
2566
2537
  redirect = findRedirect(fetcherResults);
2567
2538
  if (redirect) {
@@ -2569,13 +2540,15 @@ function createRouter(init) {
2569
2540
  // fetchRedirectIds so it doesn't get revalidated on the next set of
2570
2541
  // loader executions
2571
2542
  fetchRedirectIds.add(redirect.key);
2572
- return startRedirectNavigation(revalidationRequest, redirect.result, false);
2543
+ return startRedirectNavigation(revalidationRequest, redirect.result, false, {
2544
+ preventScrollReset
2545
+ });
2573
2546
  }
2574
2547
  // Process and commit output from loaders
2575
2548
  let {
2576
2549
  loaderData,
2577
2550
  errors
2578
- } = processLoaderData(state, matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
2551
+ } = processLoaderData(state, matches, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
2579
2552
  // Since we let revalidations complete even if the submitting fetcher was
2580
2553
  // deleted, only put it back to idle if it hasn't been deleted
2581
2554
  if (state.fetchers.has(key)) {
@@ -2608,7 +2581,7 @@ function createRouter(init) {
2608
2581
  }
2609
2582
  }
2610
2583
  // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
2611
- async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, submission) {
2584
+ async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, preventScrollReset, submission) {
2612
2585
  let existingFetcher = state.fetchers.get(key);
2613
2586
  updateFetcherState(key, getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined), {
2614
2587
  flushSync
@@ -2620,10 +2593,7 @@ function createRouter(init) {
2620
2593
  if (discoverResult.type === "aborted") {
2621
2594
  return;
2622
2595
  } else if (discoverResult.type === "error") {
2623
- let {
2624
- error
2625
- } = handleDiscoverRouteError(path, discoverResult);
2626
- setFetcherError(key, routeId, error, {
2596
+ setFetcherError(key, routeId, discoverResult.error, {
2627
2597
  flushSync
2628
2598
  });
2629
2599
  return;
@@ -2674,7 +2644,9 @@ function createRouter(init) {
2674
2644
  return;
2675
2645
  } else {
2676
2646
  fetchRedirectIds.add(key);
2677
- await startRedirectNavigation(fetchRequest, result, false);
2647
+ await startRedirectNavigation(fetchRequest, result, false, {
2648
+ preventScrollReset
2649
+ });
2678
2650
  return;
2679
2651
  }
2680
2652
  }
@@ -2710,6 +2682,7 @@ function createRouter(init) {
2710
2682
  let {
2711
2683
  submission,
2712
2684
  fetcherSubmission,
2685
+ preventScrollReset,
2713
2686
  replace
2714
2687
  } = _temp2 === void 0 ? {} : _temp2;
2715
2688
  if (redirect.response.headers.has("X-Remix-Revalidate")) {
@@ -2767,7 +2740,7 @@ function createRouter(init) {
2767
2740
  formAction: location
2768
2741
  }),
2769
2742
  // Preserve these flags across redirects
2770
- preventScrollReset: pendingPreventScrollReset,
2743
+ preventScrollReset: preventScrollReset || pendingPreventScrollReset,
2771
2744
  enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
2772
2745
  });
2773
2746
  } else {
@@ -2779,7 +2752,7 @@ function createRouter(init) {
2779
2752
  // Send fetcher submissions through for shouldRevalidate
2780
2753
  fetcherSubmission,
2781
2754
  // Preserve these flags across redirects
2782
- preventScrollReset: pendingPreventScrollReset,
2755
+ preventScrollReset: preventScrollReset || pendingPreventScrollReset,
2783
2756
  enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
2784
2757
  });
2785
2758
  }
@@ -2856,8 +2829,8 @@ function createRouter(init) {
2856
2829
  fetchLoadMatches.forEach((_, key) => {
2857
2830
  if (fetchControllers.has(key)) {
2858
2831
  cancelledFetcherLoads.add(key);
2859
- abortFetcher(key);
2860
2832
  }
2833
+ abortFetcher(key);
2861
2834
  });
2862
2835
  }
2863
2836
  function updateFetcherState(key, fetcher, opts) {
@@ -2930,9 +2903,10 @@ function createRouter(init) {
2930
2903
  }
2931
2904
  function abortFetcher(key) {
2932
2905
  let controller = fetchControllers.get(key);
2933
- invariant(controller, "Expected fetch controller: " + key);
2934
- controller.abort();
2935
- fetchControllers.delete(key);
2906
+ if (controller) {
2907
+ controller.abort();
2908
+ fetchControllers.delete(key);
2909
+ }
2936
2910
  }
2937
2911
  function markFetchersDone(keys) {
2938
2912
  for (let key of keys) {
@@ -2995,12 +2969,12 @@ function createRouter(init) {
2995
2969
  blockers
2996
2970
  });
2997
2971
  }
2998
- function shouldBlockNavigation(_ref4) {
2972
+ function shouldBlockNavigation(_ref2) {
2999
2973
  let {
3000
2974
  currentLocation,
3001
2975
  nextLocation,
3002
2976
  historyAction
3003
- } = _ref4;
2977
+ } = _ref2;
3004
2978
  if (blockerFunctions.size === 0) {
3005
2979
  return;
3006
2980
  }
@@ -3044,16 +3018,6 @@ function createRouter(init) {
3044
3018
  error
3045
3019
  };
3046
3020
  }
3047
- function handleDiscoverRouteError(pathname, discoverResult) {
3048
- return {
3049
- boundaryId: findNearestBoundary(discoverResult.partialMatches).route.id,
3050
- error: getInternalRouterError(400, {
3051
- type: "route-discovery",
3052
- pathname,
3053
- message: discoverResult.error != null && "message" in discoverResult.error ? discoverResult.error : String(discoverResult.error)
3054
- })
3055
- };
3056
- }
3057
3021
  function cancelActiveDeferreds(predicate) {
3058
3022
  let cancelledRouteIds = [];
3059
3023
  activeDeferreds.forEach((dfd, routeId) => {
@@ -3142,12 +3106,26 @@ function createRouter(init) {
3142
3106
  };
3143
3107
  }
3144
3108
  async function discoverRoutes(matches, pathname, signal) {
3109
+ if (!patchRoutesOnNavigationImpl) {
3110
+ return {
3111
+ type: "success",
3112
+ matches
3113
+ };
3114
+ }
3145
3115
  let partialMatches = matches;
3146
3116
  while (true) {
3147
3117
  let isNonHMR = inFlightDataRoutes == null;
3148
3118
  let routesToUse = inFlightDataRoutes || dataRoutes;
3119
+ let localManifest = manifest;
3149
3120
  try {
3150
- await loadLazyRouteChildren(patchRoutesOnNavigationImpl, pathname, partialMatches, routesToUse, manifest, mapRouteProperties, pendingPatchRoutes, signal);
3121
+ await patchRoutesOnNavigationImpl({
3122
+ path: pathname,
3123
+ matches: partialMatches,
3124
+ patch: (routeId, children) => {
3125
+ if (signal.aborted) return;
3126
+ patchRoutesImpl(routeId, children, routesToUse, localManifest, mapRouteProperties);
3127
+ }
3128
+ });
3151
3129
  } catch (e) {
3152
3130
  return {
3153
3131
  type: "error",
@@ -3161,7 +3139,7 @@ function createRouter(init) {
3161
3139
  // trigger a re-run of memoized `router.routes` dependencies.
3162
3140
  // HMR will already update the identity and reflow when it lands
3163
3141
  // `inFlightDataRoutes` in `completeNavigation`
3164
- if (isNonHMR) {
3142
+ if (isNonHMR && !signal.aborted) {
3165
3143
  dataRoutes = [...dataRoutes];
3166
3144
  }
3167
3145
  }
@@ -3712,9 +3690,21 @@ function normalizeTo(location, matches, basename, prependBasename, to, v7_relati
3712
3690
  path.search = location.search;
3713
3691
  path.hash = location.hash;
3714
3692
  }
3715
- // Add an ?index param for matched index routes if we don't already have one
3716
- if ((to == null || to === "" || to === ".") && activeRouteMatch && activeRouteMatch.route.index && !hasNakedIndexQuery(path.search)) {
3717
- path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
3693
+ // Account for `?index` params when routing to the current location
3694
+ if ((to == null || to === "" || to === ".") && activeRouteMatch) {
3695
+ let nakedIndex = hasNakedIndexQuery(path.search);
3696
+ if (activeRouteMatch.route.index && !nakedIndex) {
3697
+ // Add one when we're targeting an index route
3698
+ path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
3699
+ } else if (!activeRouteMatch.route.index && nakedIndex) {
3700
+ // Remove existing ones when we're not
3701
+ let params = new URLSearchParams(path.search);
3702
+ let indexValues = params.getAll("index");
3703
+ params.delete("index");
3704
+ indexValues.filter(v => v).forEach(v => params.append("index", v));
3705
+ let qs = params.toString();
3706
+ path.search = qs ? "?" + qs : "";
3707
+ }
3718
3708
  }
3719
3709
  // If we're operating within a basename, prepend it to the pathname. If
3720
3710
  // this is a root navigation, then just use the raw basename which allows
@@ -3760,8 +3750,8 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
3760
3750
  }
3761
3751
  let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?
3762
3752
  // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data
3763
- Array.from(opts.body.entries()).reduce((acc, _ref5) => {
3764
- let [name, value] = _ref5;
3753
+ Array.from(opts.body.entries()).reduce((acc, _ref3) => {
3754
+ let [name, value] = _ref3;
3765
3755
  return "" + acc + name + "=" + value + "\n";
3766
3756
  }, "") : String(opts.body);
3767
3757
  return {
@@ -3849,25 +3839,36 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
3849
3839
  submission
3850
3840
  };
3851
3841
  }
3852
- // Filter out all routes below any caught error as they aren't going to
3842
+ // Filter out all routes at/below any caught error as they aren't going to
3853
3843
  // render so we don't need to load them
3854
- function getLoaderMatchesUntilBoundary(matches, boundaryId) {
3855
- let boundaryMatches = matches;
3856
- if (boundaryId) {
3857
- let index = matches.findIndex(m => m.route.id === boundaryId);
3858
- if (index >= 0) {
3859
- boundaryMatches = matches.slice(0, index);
3860
- }
3844
+ function getLoaderMatchesUntilBoundary(matches, boundaryId, includeBoundary) {
3845
+ if (includeBoundary === void 0) {
3846
+ includeBoundary = false;
3847
+ }
3848
+ let index = matches.findIndex(m => m.route.id === boundaryId);
3849
+ if (index >= 0) {
3850
+ return matches.slice(0, includeBoundary ? index + 1 : index);
3861
3851
  }
3862
- return boundaryMatches;
3852
+ return matches;
3863
3853
  }
3864
- function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
3854
+ function getMatchesToLoad(history, state, matches, submission, location, initialHydration, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
3865
3855
  let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
3866
3856
  let currentUrl = history.createURL(state.location);
3867
3857
  let nextUrl = history.createURL(location);
3868
3858
  // Pick navigation matches that are net-new or qualify for revalidation
3869
- let boundaryId = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[0] : undefined;
3870
- let boundaryMatches = boundaryId ? getLoaderMatchesUntilBoundary(matches, boundaryId) : matches;
3859
+ let boundaryMatches = matches;
3860
+ if (initialHydration && state.errors) {
3861
+ // On initial hydration, only consider matches up to _and including_ the boundary.
3862
+ // This is inclusive to handle cases where a server loader ran successfully,
3863
+ // a child server loader bubbled up to this route, but this route has
3864
+ // `clientLoader.hydrate` so we want to still run the `clientLoader` so that
3865
+ // we have a complete version of `loaderData`
3866
+ boundaryMatches = getLoaderMatchesUntilBoundary(matches, Object.keys(state.errors)[0], true);
3867
+ } else if (pendingActionResult && isErrorResult(pendingActionResult[1])) {
3868
+ // If an action threw an error, we call loaders up to, but not including the
3869
+ // boundary
3870
+ boundaryMatches = getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]);
3871
+ }
3871
3872
  // Don't revalidate loaders by default after action 4xx/5xx responses
3872
3873
  // when the flag is enabled. They can still opt-into revalidation via
3873
3874
  // `shouldRevalidate` via `actionResult`
@@ -3884,13 +3885,8 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
3884
3885
  if (route.loader == null) {
3885
3886
  return false;
3886
3887
  }
3887
- if (isInitialLoad) {
3888
- if (typeof route.loader !== "function" || route.loader.hydrate) {
3889
- return true;
3890
- }
3891
- return state.loaderData[route.id] === undefined && (
3892
- // Don't re-run if the loader ran and threw an error
3893
- !state.errors || state.errors[route.id] === undefined);
3888
+ if (initialHydration) {
3889
+ return shouldLoadRouteOnHydration(route, state.loaderData, state.errors);
3894
3890
  }
3895
3891
  // Always call the loader on new route instances and pending defer cancellations
3896
3892
  if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
@@ -3921,11 +3917,11 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
3921
3917
  let revalidatingFetchers = [];
3922
3918
  fetchLoadMatches.forEach((f, key) => {
3923
3919
  // Don't revalidate:
3924
- // - on initial load (shouldn't be any fetchers then anyway)
3920
+ // - on initial hydration (shouldn't be any fetchers then anyway)
3925
3921
  // - if fetcher won't be present in the subsequent render
3926
3922
  // - no longer matches the URL (v7_fetcherPersist=false)
3927
3923
  // - was unmounted but persisted due to v7_fetcherPersist=true
3928
- if (isInitialLoad || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
3924
+ if (initialHydration || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
3929
3925
  return;
3930
3926
  }
3931
3927
  let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
@@ -3989,6 +3985,28 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
3989
3985
  });
3990
3986
  return [navigationMatches, revalidatingFetchers];
3991
3987
  }
3988
+ function shouldLoadRouteOnHydration(route, loaderData, errors) {
3989
+ // We dunno if we have a loader - gotta find out!
3990
+ if (route.lazy) {
3991
+ return true;
3992
+ }
3993
+ // No loader, nothing to initialize
3994
+ if (!route.loader) {
3995
+ return false;
3996
+ }
3997
+ let hasData = loaderData != null && loaderData[route.id] !== undefined;
3998
+ let hasError = errors != null && errors[route.id] !== undefined;
3999
+ // Don't run if we error'd during SSR
4000
+ if (!hasData && hasError) {
4001
+ return false;
4002
+ }
4003
+ // Explicitly opting-in to running on hydration
4004
+ if (typeof route.loader === "function" && route.loader.hydrate === true) {
4005
+ return true;
4006
+ }
4007
+ // Otherwise, run if we're not yet initialized with anything
4008
+ return !hasData && !hasError;
4009
+ }
3992
4010
  function isNewLoader(currentLoaderData, currentMatch, match) {
3993
4011
  let isNew =
3994
4012
  // [a] -> [a, b]
@@ -4020,33 +4038,6 @@ function shouldRevalidateLoader(loaderMatch, arg) {
4020
4038
  }
4021
4039
  return arg.defaultShouldRevalidate;
4022
4040
  }
4023
- /**
4024
- * Idempotent utility to execute patchRoutesOnNavigation() to lazily load route
4025
- * definitions and update the routes/routeManifest
4026
- */
4027
- async function loadLazyRouteChildren(patchRoutesOnNavigationImpl, path, matches, routes, manifest, mapRouteProperties, pendingRouteChildren, signal) {
4028
- let key = [path, ...matches.map(m => m.route.id)].join("-");
4029
- try {
4030
- let pending = pendingRouteChildren.get(key);
4031
- if (!pending) {
4032
- pending = patchRoutesOnNavigationImpl({
4033
- path,
4034
- matches,
4035
- patch: (routeId, children) => {
4036
- if (!signal.aborted) {
4037
- patchRoutesImpl(routeId, children, routes, manifest, mapRouteProperties);
4038
- }
4039
- }
4040
- });
4041
- pendingRouteChildren.set(key, pending);
4042
- }
4043
- if (pending && isPromise(pending)) {
4044
- await pending;
4045
- }
4046
- } finally {
4047
- pendingRouteChildren.delete(key);
4048
- }
4049
- }
4050
4041
  function patchRoutesImpl(routeId, children, routesToUse, manifest, mapRouteProperties) {
4051
4042
  var _childrenToPatch;
4052
4043
  let childrenToPatch;
@@ -4063,10 +4054,31 @@ function patchRoutesImpl(routeId, children, routesToUse, manifest, mapRoutePrope
4063
4054
  // Don't patch in routes we already know about so that `patch` is idempotent
4064
4055
  // to simplify user-land code. This is useful because we re-call the
4065
4056
  // `patchRoutesOnNavigation` function for matched routes with params.
4066
- let uniqueChildren = children.filter(a => !childrenToPatch.some(b => a.index === b.index && a.path === b.path && a.caseSensitive === b.caseSensitive));
4057
+ let uniqueChildren = children.filter(newRoute => !childrenToPatch.some(existingRoute => isSameRoute(newRoute, existingRoute)));
4067
4058
  let newRoutes = convertRoutesToDataRoutes(uniqueChildren, mapRouteProperties, [routeId || "_", "patch", String(((_childrenToPatch = childrenToPatch) == null ? void 0 : _childrenToPatch.length) || "0")], manifest);
4068
4059
  childrenToPatch.push(...newRoutes);
4069
4060
  }
4061
+ function isSameRoute(newRoute, existingRoute) {
4062
+ // Most optimal check is by id
4063
+ if ("id" in newRoute && "id" in existingRoute && newRoute.id === existingRoute.id) {
4064
+ return true;
4065
+ }
4066
+ // Second is by pathing differences
4067
+ if (!(newRoute.index === existingRoute.index && newRoute.path === existingRoute.path && newRoute.caseSensitive === existingRoute.caseSensitive)) {
4068
+ return false;
4069
+ }
4070
+ // Pathless layout routes are trickier since we need to check children.
4071
+ // If they have no children then they're the same as far as we can tell
4072
+ if ((!newRoute.children || newRoute.children.length === 0) && (!existingRoute.children || existingRoute.children.length === 0)) {
4073
+ return true;
4074
+ }
4075
+ // Otherwise, we look to see if every child in the new route is already
4076
+ // represented in the existing route's children
4077
+ return newRoute.children.every((aChild, i) => {
4078
+ var _existingRoute$childr;
4079
+ return (_existingRoute$childr = existingRoute.children) == null ? void 0 : _existingRoute$childr.some(bChild => isSameRoute(aChild, bChild));
4080
+ });
4081
+ }
4070
4082
  /**
4071
4083
  * Execute route.lazy() methods to lazily load route modules (loader, action,
4072
4084
  * shouldRevalidate) and update the routeManifest in place which shares objects
@@ -4116,10 +4128,10 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
4116
4128
  }));
4117
4129
  }
4118
4130
  // Default implementation of `dataStrategy` which fetches all loaders in parallel
4119
- async function defaultDataStrategy(_ref6) {
4131
+ async function defaultDataStrategy(_ref4) {
4120
4132
  let {
4121
4133
  matches
4122
- } = _ref6;
4134
+ } = _ref4;
4123
4135
  let matchesToLoad = matches.filter(m => m.shouldLoad);
4124
4136
  let results = await Promise.all(matchesToLoad.map(m => m.resolve()));
4125
4137
  return results.reduce((acc, result, i) => Object.assign(acc, {
@@ -4522,7 +4534,7 @@ function processRouteLoaderData(matches, results, pendingActionResult, activeDef
4522
4534
  loaderHeaders
4523
4535
  };
4524
4536
  }
4525
- function processLoaderData(state, matches, matchesToLoad, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
4537
+ function processLoaderData(state, matches, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
4526
4538
  let {
4527
4539
  loaderData,
4528
4540
  errors
@@ -4634,9 +4646,7 @@ function getInternalRouterError(status, _temp5) {
4634
4646
  let errorMessage = "Unknown @remix-run/router error";
4635
4647
  if (status === 400) {
4636
4648
  statusText = "Bad Request";
4637
- if (type === "route-discovery") {
4638
- errorMessage = "Unable to match URL \"" + pathname + "\" - the `patchRoutesOnNavigation()` " + ("function threw the following error:\n" + message);
4639
- } else if (method && pathname && routeId) {
4649
+ if (method && pathname && routeId) {
4640
4650
  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.";
4641
4651
  } else if (type === "defer-action") {
4642
4652
  errorMessage = "defer() is not supported in actions";
@@ -4696,9 +4706,6 @@ function isHashChangeOnly(a, b) {
4696
4706
  // /page#hash -> /page
4697
4707
  return false;
4698
4708
  }
4699
- function isPromise(val) {
4700
- return typeof val === "object" && val != null && "then" in val;
4701
- }
4702
4709
  function isDataStrategyResult(result) {
4703
4710
  return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
4704
4711
  }