@remix-run/router 0.0.0-experimental-e192105b → 0.0.0-experimental-178bc9ee

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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # `@remix-run/router`
2
2
 
3
+ ## 1.12.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add `unstable_flushSync` option to `router.navigate` and `router.fetch` to tell the React Router layer to opt-out of `React.startTransition` and into `ReactDOM.flushSync` for state updates ([#11005](https://github.com/remix-run/react-router/pull/11005))
8
+
9
+ ### Patch Changes
10
+
11
+ - Fix `relative="path"` bug where relative path calculations started from the full location pathname, instead of from the current contextual route pathname. ([#11006](https://github.com/remix-run/react-router/pull/11006))
12
+
13
+ ```jsx
14
+ <Route path="/a">
15
+ <Route path="/b" element={<Component />}>
16
+ <Route path="/c" />
17
+ </Route>
18
+ </Route>;
19
+
20
+ function Component() {
21
+ return (
22
+ <>
23
+ {/* This is now correctly relative to /a/b, not /a/b/c */}
24
+ <Link to=".." relative="path" />
25
+ <Outlet />
26
+ </>
27
+ );
28
+ }
29
+ ```
30
+
3
31
  ## 1.11.0
4
32
 
5
33
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- export type { ActionFunction, ActionFunctionArgs, AgnosticDataIndexRouteObject, AgnosticDataNonIndexRouteObject, AgnosticDataRouteMatch, AgnosticDataRouteObject, AgnosticIndexRouteObject, AgnosticNonIndexRouteObject, AgnosticRouteMatch, AgnosticRouteObject, ErrorResponse, FormEncType, FormMethod, HTMLFormMethod, JsonFunction, LazyRouteFunction, LoaderFunction, LoaderFunctionArgs, ParamParseKey, Params, PathMatch, PathPattern, RedirectFunction, ShouldRevalidateFunction, ShouldRevalidateFunctionArgs, TrackedPromise, UIMatch, V7_FormMethod, } from "./utils";
1
+ export type { ActionFunction, ActionFunctionArgs, AgnosticDataIndexRouteObject, AgnosticDataNonIndexRouteObject, AgnosticDataRouteMatch, AgnosticDataRouteObject, AgnosticIndexRouteObject, AgnosticNonIndexRouteObject, AgnosticRouteMatch, AgnosticRouteObject, ErrorResponse, FormEncType, FormMethod, HTMLFormMethod, JsonFunction, LazyRouteFunction, LoaderFunction, LoaderFunctionArgs, ParamParseKey, Params, PathMatch, PathParam, PathPattern, RedirectFunction, ShouldRevalidateFunction, ShouldRevalidateFunctionArgs, TrackedPromise, UIMatch, V7_FormMethod, } from "./utils";
2
2
  export { AbortedDeferredError, defer, generatePath, getToPathname, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, redirect, redirectDocument, resolvePath, resolveTo, stripBasename, } from "./utils";
3
3
  export type { BrowserHistory, BrowserHistoryOptions, HashHistory, HashHistoryOptions, History, InitialEntry, Location, MemoryHistory, MemoryHistoryOptions, Path, To, } from "./history";
4
4
  export { Action, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, parsePath, } from "./history";
5
5
  export * from "./router";
6
6
  /** @internal */
7
7
  export type { RouteManifest as UNSAFE_RouteManifest } from "./utils";
8
- export { DeferredData as UNSAFE_DeferredData, ErrorResponseImpl as UNSAFE_ErrorResponseImpl, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, convertRouteMatchToUiMatch as UNSAFE_convertRouteMatchToUiMatch, getPathContributingMatches as UNSAFE_getPathContributingMatches, } from "./utils";
8
+ export { DeferredData as UNSAFE_DeferredData, ErrorResponseImpl as UNSAFE_ErrorResponseImpl, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, convertRouteMatchToUiMatch as UNSAFE_convertRouteMatchToUiMatch, getResolveToMatches as UNSAFE_getResolveToMatches, } from "./utils";
9
9
  export { invariant as UNSAFE_invariant, warning as UNSAFE_warning, } from "./history";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v0.0.0-experimental-e192105b
2
+ * @remix-run/router v0.0.0-experimental-178bc9ee
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1192,6 +1192,13 @@ function getPathContributingMatches(matches) {
1192
1192
  return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
1193
1193
  }
1194
1194
 
1195
+ // Return the array of pathnames for the current route matches - used to
1196
+ // generate the routePathnames input for resolveTo()
1197
+ function getResolveToMatches(matches) {
1198
+ // Use the full pathname for the leaf match so we include splat values for "." links
1199
+ return getPathContributingMatches(matches).map((match, idx) => idx === matches.length - 1 ? match.pathname : match.pathnameBase);
1200
+ }
1201
+
1195
1202
  /**
1196
1203
  * @private
1197
1204
  */
@@ -1221,16 +1228,30 @@ function resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {
1221
1228
  // `to` values that do not provide a pathname. `to` can simply be a search or
1222
1229
  // hash string, in which case we should assume that the navigation is relative
1223
1230
  // to the current location's pathname and *not* the route pathname.
1224
- if (isPathRelative || toPathname == null) {
1231
+ if (toPathname == null) {
1225
1232
  from = locationPathname;
1233
+ } else if (isPathRelative) {
1234
+ let fromSegments = routePathnames[routePathnames.length - 1].replace(/^\//, "").split("/");
1235
+ if (toPathname.startsWith("..")) {
1236
+ let toSegments = toPathname.split("/");
1237
+
1238
+ // With relative="path", each leading .. segment means "go up one URL segment"
1239
+ while (toSegments[0] === "..") {
1240
+ toSegments.shift();
1241
+ fromSegments.pop();
1242
+ }
1243
+ to.pathname = toSegments.join("/");
1244
+ }
1245
+ from = "/" + fromSegments.join("/");
1226
1246
  } else {
1227
1247
  let routePathnameIndex = routePathnames.length - 1;
1228
1248
  if (toPathname.startsWith("..")) {
1229
1249
  let toSegments = toPathname.split("/");
1230
1250
 
1231
- // Each leading .. segment means "go up one route" instead of "go up one
1232
- // URL segment". This is a key difference from how <a href> works and a
1233
- // major reason we call this a "to" value instead of a "href".
1251
+ // With relative="route" (the default), each leading .. segment means
1252
+ // "go up one route" instead of "go up one URL segment". This is a key
1253
+ // difference from how <a href> works and a major reason we call this a
1254
+ // "to" value instead of a "href".
1234
1255
  while (toSegments[0] === "..") {
1235
1256
  toSegments.shift();
1236
1257
  routePathnameIndex -= 1;
@@ -1665,6 +1686,7 @@ function createRouter(init) {
1665
1686
  let future = _extends({
1666
1687
  v7_fetcherPersist: false,
1667
1688
  v7_normalizeFormMethod: false,
1689
+ v7_partialHydration: false,
1668
1690
  v7_prependBasename: false
1669
1691
  }, init.future);
1670
1692
  // Cleanup function for history
@@ -1701,7 +1723,13 @@ function createRouter(init) {
1701
1723
  [route.id]: error
1702
1724
  };
1703
1725
  }
1704
- let initialized =
1726
+
1727
+ // "Initialized" here really means "Can `RouterProvider` render my route tree?"
1728
+ // Prior to `route.Fallback`, we only had a root `fallbackElement` so we used
1729
+ // `state.initialized` to render that instead of `<DataRoutes>`. Now that we
1730
+ // support route level fallbacks we can always render and we'll just render
1731
+ // as deep as we have data for and detect the nearest ancestor Fallback
1732
+ let initialized = future.v7_partialHydration ||
1705
1733
  // All initialMatches need to be loaded before we're ready. If we have lazy
1706
1734
  // functions around still then we'll need to run them in initialize()
1707
1735
  !initialMatches.some(m => m.route.lazy) && (
@@ -1739,9 +1767,6 @@ function createRouter(init) {
1739
1767
  // Should the current navigation enable document.startViewTransition?
1740
1768
  let pendingViewTransitionEnabled = false;
1741
1769
 
1742
- // Should the current navigation use flushSync for state updates?
1743
- let pendingFlushSync = false;
1744
-
1745
1770
  // Store applied view transitions so we can apply them on POP
1746
1771
  let appliedViewTransitions = new Map();
1747
1772
 
@@ -1855,8 +1880,6 @@ function createRouter(init) {
1855
1880
  blockers.set(blockerKey, IDLE_BLOCKER);
1856
1881
  updateState({
1857
1882
  blockers
1858
- }, {
1859
- flushSync: false
1860
1883
  });
1861
1884
  }
1862
1885
  });
@@ -1878,8 +1901,10 @@ function createRouter(init) {
1878
1901
  // in the normal navigation flow. For SSR it's expected that lazy modules are
1879
1902
  // resolved prior to router creation since we can't go into a fallbackElement
1880
1903
  // UI for SSR'd apps
1881
- if (!state.initialized) {
1882
- startNavigation(Action.Pop, state.location);
1904
+ if (!state.initialized || future.v7_partialHydration && state.matches.some(m => m.route.loader && state.loaderData[m.route.id] === undefined)) {
1905
+ startNavigation(Action.Pop, state.location, {
1906
+ initialHydration: true
1907
+ });
1883
1908
  }
1884
1909
  return router;
1885
1910
  }
@@ -1906,6 +1931,9 @@ function createRouter(init) {
1906
1931
 
1907
1932
  // Update our state and notify the calling context of the change
1908
1933
  function updateState(newState, opts) {
1934
+ if (opts === void 0) {
1935
+ opts = {};
1936
+ }
1909
1937
  state = _extends({}, state, newState);
1910
1938
 
1911
1939
  // Prep fetcher cleanup so we can tell the UI which fetcher data entries
@@ -1933,7 +1961,7 @@ function createRouter(init) {
1933
1961
  [...subscribers].forEach(subscriber => subscriber(state, {
1934
1962
  deletedFetchers: deletedFetchersKeys,
1935
1963
  unstable_viewTransitionOpts: opts.viewTransitionOpts,
1936
- unstable_flushSync: opts.flushSync
1964
+ unstable_flushSync: opts.flushSync === true
1937
1965
  }));
1938
1966
 
1939
1967
  // Remove idle fetchers from state since we only care about in-flight fetchers.
@@ -1948,8 +1976,11 @@ function createRouter(init) {
1948
1976
  // - Location is a required param
1949
1977
  // - Navigation will always be set to IDLE_NAVIGATION
1950
1978
  // - Can pass any other state in newState
1951
- function completeNavigation(location, newState) {
1979
+ function completeNavigation(location, newState, _temp) {
1952
1980
  var _location$state, _location$state2;
1981
+ let {
1982
+ flushSync
1983
+ } = _temp === void 0 ? {} : _temp;
1953
1984
  // Deduce if we're in a loading/actionReload state:
1954
1985
  // - We have committed actionData in the store
1955
1986
  // - The current navigation was a mutation submission
@@ -2042,14 +2073,13 @@ function createRouter(init) {
2042
2073
  blockers
2043
2074
  }), {
2044
2075
  viewTransitionOpts,
2045
- flushSync: pendingFlushSync
2076
+ flushSync: flushSync === true
2046
2077
  });
2047
2078
 
2048
2079
  // Reset stateful navigation vars
2049
2080
  pendingAction = Action.Pop;
2050
2081
  pendingPreventScrollReset = false;
2051
2082
  pendingViewTransitionEnabled = false;
2052
- pendingFlushSync = false;
2053
2083
  isUninterruptedRevalidation = false;
2054
2084
  isRevalidationRequired = false;
2055
2085
  cancelledDeferredRoutes = [];
@@ -2116,8 +2146,6 @@ function createRouter(init) {
2116
2146
  blockers.set(blockerKey, IDLE_BLOCKER);
2117
2147
  updateState({
2118
2148
  blockers
2119
- }, {
2120
- flushSync
2121
2149
  });
2122
2150
  }
2123
2151
  });
@@ -2142,8 +2170,6 @@ function createRouter(init) {
2142
2170
  interruptActiveLoads();
2143
2171
  updateState({
2144
2172
  revalidation: "loading"
2145
- }, {
2146
- flushSync: false
2147
2173
  });
2148
2174
 
2149
2175
  // If we're currently submitting an action, we don't need to start a new
@@ -2187,10 +2213,10 @@ function createRouter(init) {
2187
2213
  saveScrollPosition(state.location, state.matches);
2188
2214
  pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2189
2215
  pendingViewTransitionEnabled = (opts && opts.enableViewTransition) === true;
2190
- pendingFlushSync = (opts && opts.flushSync) === true;
2191
2216
  let routesToUse = inFlightDataRoutes || dataRoutes;
2192
2217
  let loadingNavigation = opts && opts.overrideNavigation;
2193
2218
  let matches = matchRoutes(routesToUse, location, basename);
2219
+ let flushSync = (opts && opts.flushSync) === true;
2194
2220
 
2195
2221
  // Short circuit with a 404 on the root error boundary if we match nothing
2196
2222
  if (!matches) {
@@ -2209,6 +2235,8 @@ function createRouter(init) {
2209
2235
  errors: {
2210
2236
  [route.id]: error
2211
2237
  }
2238
+ }, {
2239
+ flushSync
2212
2240
  });
2213
2241
  return;
2214
2242
  }
@@ -2222,13 +2250,15 @@ function createRouter(init) {
2222
2250
  if (state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
2223
2251
  completeNavigation(location, {
2224
2252
  matches
2253
+ }, {
2254
+ flushSync
2225
2255
  });
2226
2256
  return;
2227
2257
  }
2228
2258
 
2229
2259
  // Create a controller/Request for this navigation
2230
2260
  pendingNavigationController = new AbortController();
2231
- let request = createClientSideRequest(init.history, location, pendingNavigationController.signal, opts && opts.submission);
2261
+ let request = createClientSideRequest(init.history, location, pendingNavigationController.signal, opts && opts.initialHydration === true, opts && opts.submission);
2232
2262
  let pendingActionData;
2233
2263
  let pendingError;
2234
2264
  if (opts && opts.pendingError) {
@@ -2242,7 +2272,8 @@ function createRouter(init) {
2242
2272
  } else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
2243
2273
  // Call action if we received an action submission
2244
2274
  let actionOutput = await handleAction(request, location, opts.submission, matches, {
2245
- replace: opts.replace
2275
+ replace: opts.replace,
2276
+ flushSync
2246
2277
  });
2247
2278
  if (actionOutput.shortCircuited) {
2248
2279
  return;
@@ -2250,6 +2281,7 @@ function createRouter(init) {
2250
2281
  pendingActionData = actionOutput.pendingActionData;
2251
2282
  pendingError = actionOutput.pendingActionError;
2252
2283
  loadingNavigation = getLoadingNavigation(location, opts.submission);
2284
+ flushSync = false;
2253
2285
 
2254
2286
  // Create a GET request for the loaders
2255
2287
  request = new Request(request.url, {
@@ -2262,7 +2294,7 @@ function createRouter(init) {
2262
2294
  shortCircuited,
2263
2295
  loaderData,
2264
2296
  errors
2265
- } = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, pendingActionData, pendingError);
2297
+ } = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync, pendingActionData, pendingError);
2266
2298
  if (shortCircuited) {
2267
2299
  return;
2268
2300
  }
@@ -2294,7 +2326,7 @@ function createRouter(init) {
2294
2326
  updateState({
2295
2327
  navigation
2296
2328
  }, {
2297
- flushSync: pendingFlushSync
2329
+ flushSync: opts.flushSync === true
2298
2330
  });
2299
2331
 
2300
2332
  // Call our action and get the result
@@ -2369,7 +2401,7 @@ function createRouter(init) {
2369
2401
 
2370
2402
  // Call all applicable loaders for the given matches, handling redirects,
2371
2403
  // errors, etc.
2372
- async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, pendingActionData, pendingError) {
2404
+ async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync, pendingActionData, pendingError) {
2373
2405
  // Figure out the right navigation we want to use for data loading
2374
2406
  let loadingNavigation = overrideNavigation || getLoadingNavigation(location, submission);
2375
2407
 
@@ -2377,7 +2409,7 @@ function createRouter(init) {
2377
2409
  // we have it on the loading navigation so use that if available
2378
2410
  let activeSubmission = submission || fetcherSubmission || getSubmissionFromNavigation(loadingNavigation);
2379
2411
  let routesToUse = inFlightDataRoutes || dataRoutes;
2380
- let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionData, pendingError);
2412
+ let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, future.v7_partialHydration && initialHydration === true, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionData, pendingError);
2381
2413
 
2382
2414
  // Cancel pending deferreds for no-longer-matched routes or routes we're
2383
2415
  // about to reload. Note that if this is an action reload we would have
@@ -2397,7 +2429,9 @@ function createRouter(init) {
2397
2429
  actionData: pendingActionData
2398
2430
  } : {}, updatedFetchers ? {
2399
2431
  fetchers: new Map(state.fetchers)
2400
- } : {}));
2432
+ } : {}), {
2433
+ flushSync
2434
+ });
2401
2435
  return {
2402
2436
  shortCircuited: true
2403
2437
  };
@@ -2423,7 +2457,7 @@ function createRouter(init) {
2423
2457
  } : {}, revalidatingFetchers.length > 0 ? {
2424
2458
  fetchers: new Map(state.fetchers)
2425
2459
  } : {}), {
2426
- flushSync: pendingFlushSync
2460
+ flushSync
2427
2461
  });
2428
2462
  }
2429
2463
  revalidatingFetchers.forEach(rf => {
@@ -2578,7 +2612,7 @@ function createRouter(init) {
2578
2612
 
2579
2613
  // Call the action for the fetcher
2580
2614
  let abortController = new AbortController();
2581
- let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
2615
+ let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, false, submission);
2582
2616
  fetchControllers.set(key, abortController);
2583
2617
  let originatingLoadId = incrementingLoadId;
2584
2618
  let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename);
@@ -2591,9 +2625,7 @@ function createRouter(init) {
2591
2625
  return;
2592
2626
  }
2593
2627
  if (deletedFetchers.has(key)) {
2594
- updateFetcherState(key, getDoneFetcher(undefined), {
2595
- flushSync
2596
- });
2628
+ updateFetcherState(key, getDoneFetcher(undefined));
2597
2629
  return;
2598
2630
  }
2599
2631
  if (isRedirectResult(actionResult)) {
@@ -2603,15 +2635,11 @@ function createRouter(init) {
2603
2635
  // should take precedence over this redirect navigation. We already
2604
2636
  // set isRevalidationRequired so all loaders for the new route should
2605
2637
  // fire unless opted out via shouldRevalidate
2606
- updateFetcherState(key, getDoneFetcher(undefined), {
2607
- flushSync
2608
- });
2638
+ updateFetcherState(key, getDoneFetcher(undefined));
2609
2639
  return;
2610
2640
  } else {
2611
2641
  fetchRedirectIds.add(key);
2612
- updateFetcherState(key, getLoadingFetcher(submission), {
2613
- flushSync
2614
- });
2642
+ updateFetcherState(key, getLoadingFetcher(submission));
2615
2643
  return startRedirectNavigation(state, actionResult, {
2616
2644
  fetcherSubmission: submission
2617
2645
  });
@@ -2620,9 +2648,7 @@ function createRouter(init) {
2620
2648
 
2621
2649
  // Process any non-redirect errors thrown
2622
2650
  if (isErrorResult(actionResult)) {
2623
- setFetcherError(key, routeId, actionResult.error, {
2624
- flushSync
2625
- });
2651
+ setFetcherError(key, routeId, actionResult.error);
2626
2652
  return;
2627
2653
  }
2628
2654
  if (isDeferredResult(actionResult)) {
@@ -2634,7 +2660,7 @@ function createRouter(init) {
2634
2660
  // Start the data load for current matches, or the next location if we're
2635
2661
  // in the middle of a navigation
2636
2662
  let nextLocation = state.navigation.location || state.location;
2637
- let revalidationRequest = createClientSideRequest(init.history, nextLocation, abortController.signal);
2663
+ let revalidationRequest = createClientSideRequest(init.history, nextLocation, abortController.signal, false);
2638
2664
  let routesToUse = inFlightDataRoutes || dataRoutes;
2639
2665
  let matches = state.navigation.state !== "idle" ? matchRoutes(routesToUse, state.navigation.location, basename) : state.matches;
2640
2666
  invariant(matches, "Didn't find any matches after fetcher action");
@@ -2642,7 +2668,7 @@ function createRouter(init) {
2642
2668
  fetchReloadIds.set(key, loadId);
2643
2669
  let loadFetcher = getLoadingFetcher(submission, actionResult.data);
2644
2670
  state.fetchers.set(key, loadFetcher);
2645
- let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, {
2671
+ let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, false, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, {
2646
2672
  [match.route.id]: actionResult.data
2647
2673
  }, undefined // No need to send through errors since we short circuit above
2648
2674
  );
@@ -2664,8 +2690,6 @@ function createRouter(init) {
2664
2690
  });
2665
2691
  updateState({
2666
2692
  fetchers: new Map(state.fetchers)
2667
- }, {
2668
- flushSync
2669
2693
  });
2670
2694
  let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(rf => abortFetcher(rf.key));
2671
2695
  abortController.signal.addEventListener("abort", abortPendingFetchRevalidations);
@@ -2727,8 +2751,6 @@ function createRouter(init) {
2727
2751
  errors,
2728
2752
  loaderData: mergeLoaderData(state.loaderData, loaderData, matches, errors),
2729
2753
  fetchers: new Map(state.fetchers)
2730
- }, {
2731
- flushSync
2732
2754
  });
2733
2755
  isRevalidationRequired = false;
2734
2756
  }
@@ -2743,7 +2765,7 @@ function createRouter(init) {
2743
2765
 
2744
2766
  // Call the loader for this fetcher route match
2745
2767
  let abortController = new AbortController();
2746
- let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
2768
+ let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, false);
2747
2769
  fetchControllers.set(key, abortController);
2748
2770
  let originatingLoadId = incrementingLoadId;
2749
2771
  let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename);
@@ -2765,9 +2787,7 @@ function createRouter(init) {
2765
2787
  return;
2766
2788
  }
2767
2789
  if (deletedFetchers.has(key)) {
2768
- updateFetcherState(key, getDoneFetcher(undefined), {
2769
- flushSync
2770
- });
2790
+ updateFetcherState(key, getDoneFetcher(undefined));
2771
2791
  return;
2772
2792
  }
2773
2793
 
@@ -2776,9 +2796,7 @@ function createRouter(init) {
2776
2796
  if (pendingNavigationLoadId > originatingLoadId) {
2777
2797
  // A new navigation was kicked off after our loader started, so that
2778
2798
  // should take precedence over this redirect navigation
2779
- updateFetcherState(key, getDoneFetcher(undefined), {
2780
- flushSync
2781
- });
2799
+ updateFetcherState(key, getDoneFetcher(undefined));
2782
2800
  return;
2783
2801
  } else {
2784
2802
  fetchRedirectIds.add(key);
@@ -2789,17 +2807,13 @@ function createRouter(init) {
2789
2807
 
2790
2808
  // Process any non-redirect errors thrown
2791
2809
  if (isErrorResult(result)) {
2792
- setFetcherError(key, routeId, result.error, {
2793
- flushSync
2794
- });
2810
+ setFetcherError(key, routeId, result.error);
2795
2811
  return;
2796
2812
  }
2797
2813
  invariant(!isDeferredResult(result), "Unhandled fetcher deferred data");
2798
2814
 
2799
2815
  // Put the fetcher back into an idle state
2800
- updateFetcherState(key, getDoneFetcher(result.data), {
2801
- flushSync
2802
- });
2816
+ updateFetcherState(key, getDoneFetcher(result.data));
2803
2817
  }
2804
2818
 
2805
2819
  /**
@@ -2821,12 +2835,12 @@ function createRouter(init) {
2821
2835
  * actually touch history until we've processed redirects, so we just use
2822
2836
  * the history action from the original navigation (PUSH or REPLACE).
2823
2837
  */
2824
- async function startRedirectNavigation(state, redirect, _temp) {
2838
+ async function startRedirectNavigation(state, redirect, _temp2) {
2825
2839
  let {
2826
2840
  submission,
2827
2841
  fetcherSubmission,
2828
2842
  replace
2829
- } = _temp === void 0 ? {} : _temp;
2843
+ } = _temp2 === void 0 ? {} : _temp2;
2830
2844
  if (redirect.revalidate) {
2831
2845
  isRevalidationRequired = true;
2832
2846
  }
@@ -2904,7 +2918,7 @@ function createRouter(init) {
2904
2918
  // accordingly
2905
2919
  let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename)), ...fetchersToLoad.map(f => {
2906
2920
  if (f.matches && f.match && f.controller) {
2907
- return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, f.controller.signal), f.match, f.matches, manifest, mapRouteProperties, basename);
2921
+ return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, f.controller.signal, false), f.match, f.matches, manifest, mapRouteProperties, basename);
2908
2922
  } else {
2909
2923
  let error = {
2910
2924
  type: ResultType.error,
@@ -2941,14 +2955,20 @@ function createRouter(init) {
2941
2955
  });
2942
2956
  }
2943
2957
  function updateFetcherState(key, fetcher, opts) {
2958
+ if (opts === void 0) {
2959
+ opts = {};
2960
+ }
2944
2961
  state.fetchers.set(key, fetcher);
2945
2962
  updateState({
2946
2963
  fetchers: new Map(state.fetchers)
2947
2964
  }, {
2948
- flushSync: opts.flushSync
2965
+ flushSync: (opts && opts.flushSync) === true
2949
2966
  });
2950
2967
  }
2951
2968
  function setFetcherError(key, routeId, error, opts) {
2969
+ if (opts === void 0) {
2970
+ opts = {};
2971
+ }
2952
2972
  let boundaryMatch = findNearestBoundary(state.matches, routeId);
2953
2973
  deleteFetcher(key);
2954
2974
  updateState({
@@ -2957,7 +2977,7 @@ function createRouter(init) {
2957
2977
  },
2958
2978
  fetchers: new Map(state.fetchers)
2959
2979
  }, {
2960
- flushSync: opts.flushSync
2980
+ flushSync: (opts && opts.flushSync) === true
2961
2981
  });
2962
2982
  }
2963
2983
  function getFetcher(key) {
@@ -2999,8 +3019,6 @@ function createRouter(init) {
2999
3019
  }
3000
3020
  updateState({
3001
3021
  fetchers: new Map(state.fetchers)
3002
- }, {
3003
- flushSync: false
3004
3022
  });
3005
3023
  }
3006
3024
  function abortFetcher(key) {
@@ -3070,8 +3088,6 @@ function createRouter(init) {
3070
3088
  blockers.set(key, newBlocker);
3071
3089
  updateState({
3072
3090
  blockers
3073
- }, {
3074
- flushSync: false
3075
3091
  });
3076
3092
  }
3077
3093
  function shouldBlockNavigation(_ref2) {
@@ -3139,8 +3155,6 @@ function createRouter(init) {
3139
3155
  if (y != null) {
3140
3156
  updateState({
3141
3157
  restoreScrollPosition: y
3142
- }, {
3143
- flushSync: false
3144
3158
  });
3145
3159
  }
3146
3160
  }
@@ -3181,6 +3195,9 @@ function createRouter(init) {
3181
3195
  get basename() {
3182
3196
  return basename;
3183
3197
  },
3198
+ get future() {
3199
+ return future;
3200
+ },
3184
3201
  get state() {
3185
3202
  return state;
3186
3203
  },
@@ -3257,10 +3274,10 @@ function createStaticHandler(routes, opts) {
3257
3274
  * propagate that out and return the raw Response so the HTTP server can
3258
3275
  * return it directly.
3259
3276
  */
3260
- async function query(request, _temp2) {
3277
+ async function query(request, _temp3) {
3261
3278
  let {
3262
3279
  requestContext
3263
- } = _temp2 === void 0 ? {} : _temp2;
3280
+ } = _temp3 === void 0 ? {} : _temp3;
3264
3281
  let url = new URL(request.url);
3265
3282
  let method = request.method;
3266
3283
  let location = createLocation("", createPath(url), null, "default");
@@ -3346,11 +3363,11 @@ function createStaticHandler(routes, opts) {
3346
3363
  * code. Examples here are 404 and 405 errors that occur prior to reaching
3347
3364
  * any user-defined loaders.
3348
3365
  */
3349
- async function queryRoute(request, _temp3) {
3366
+ async function queryRoute(request, _temp4) {
3350
3367
  let {
3351
3368
  routeId,
3352
3369
  requestContext
3353
- } = _temp3 === void 0 ? {} : _temp3;
3370
+ } = _temp4 === void 0 ? {} : _temp4;
3354
3371
  let url = new URL(request.url);
3355
3372
  let method = request.method;
3356
3373
  let location = createLocation("", createPath(url), null, "default");
@@ -3628,11 +3645,9 @@ function isSubmissionNavigation(opts) {
3628
3645
  function normalizeTo(location, matches, basename, prependBasename, to, fromRouteId, relative) {
3629
3646
  let contextualMatches;
3630
3647
  let activeRouteMatch;
3631
- if (fromRouteId != null && relative !== "path") {
3648
+ if (fromRouteId) {
3632
3649
  // Grab matches up to the calling route so our route-relative logic is
3633
- // relative to the correct source route. When using relative:path,
3634
- // fromRouteId is ignored since that is always relative to the current
3635
- // location path
3650
+ // relative to the correct source route
3636
3651
  contextualMatches = [];
3637
3652
  for (let match of matches) {
3638
3653
  contextualMatches.push(match);
@@ -3647,7 +3662,7 @@ function normalizeTo(location, matches, basename, prependBasename, to, fromRoute
3647
3662
  }
3648
3663
 
3649
3664
  // Resolve the relative path
3650
- let path = resolveTo(to ? to : ".", getPathContributingMatches(contextualMatches).map(m => m.pathnameBase), stripBasename(location.pathname, basename) || location.pathname, relative === "path");
3665
+ let path = resolveTo(to ? to : ".", getResolveToMatches(contextualMatches), stripBasename(location.pathname, basename) || location.pathname, relative === "path");
3651
3666
 
3652
3667
  // When `to` is not specified we inherit search/hash from the current
3653
3668
  // location, unlike when to="." and we just inherit the path.
@@ -3811,7 +3826,7 @@ function getLoaderMatchesUntilBoundary(matches, boundaryId) {
3811
3826
  }
3812
3827
  return boundaryMatches;
3813
3828
  }
3814
- function getMatchesToLoad(history, state, matches, submission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionData, pendingError) {
3829
+ function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionData, pendingError) {
3815
3830
  let actionResult = pendingError ? Object.values(pendingError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : undefined;
3816
3831
  let currentUrl = history.createURL(state.location);
3817
3832
  let nextUrl = history.createURL(location);
@@ -3820,6 +3835,11 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
3820
3835
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
3821
3836
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
3822
3837
  let navigationMatches = boundaryMatches.filter((match, index) => {
3838
+ if (isInitialLoad) {
3839
+ // On initial hydration we don't do any shouldRevalidate stuff - we just
3840
+ // call the loaders that don't have hydration data
3841
+ return match.route.loader && state.loaderData[match.route.id] === undefined;
3842
+ }
3823
3843
  if (match.route.lazy) {
3824
3844
  // We haven't loaded this route yet so we don't know if it's got a loader!
3825
3845
  return true;
@@ -3859,8 +3879,12 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
3859
3879
  // Pick fetcher.loads that need to be revalidated
3860
3880
  let revalidatingFetchers = [];
3861
3881
  fetchLoadMatches.forEach((f, key) => {
3862
- // Don't revalidate if fetcher won't be present in the subsequent render
3863
- if (!matches.some(m => m.route.id === f.routeId)) {
3882
+ // Don't revalidate:
3883
+ // - on initial load (shouldn't be any fetchers then anyway)
3884
+ // - if fetcher won't be present in the subsequent render
3885
+ // - no longer matches the URL (v7_fetcherPersist=false)
3886
+ // - was unmounted but persisted due to v7_fetcherPersist=true
3887
+ if (isInitialLoad || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
3864
3888
  return;
3865
3889
  }
3866
3890
  let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
@@ -4187,11 +4211,16 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
4187
4211
  // Utility method for creating the Request instances for loaders/actions during
4188
4212
  // client-side navigations and fetches. During SSR we will always have a
4189
4213
  // Request instance from the static handler (query/queryRoute)
4190
- function createClientSideRequest(history, location, signal, submission) {
4214
+ function createClientSideRequest(history, location, signal, initial, submission) {
4191
4215
  let url = history.createURL(stripHashFromPath(location)).toString();
4192
4216
  let init = {
4193
4217
  signal
4194
4218
  };
4219
+ if (initial) {
4220
+ init.headers = {
4221
+ "X-Remix-Initial-Load": "yes"
4222
+ };
4223
+ }
4195
4224
  if (submission && isMutationMethod(submission.formMethod)) {
4196
4225
  let {
4197
4226
  formMethod,
@@ -4399,13 +4428,13 @@ function getShortCircuitMatches(routes) {
4399
4428
  route
4400
4429
  };
4401
4430
  }
4402
- function getInternalRouterError(status, _temp4) {
4431
+ function getInternalRouterError(status, _temp5) {
4403
4432
  let {
4404
4433
  pathname,
4405
4434
  routeId,
4406
4435
  method,
4407
4436
  type
4408
- } = _temp4 === void 0 ? {} : _temp4;
4437
+ } = _temp5 === void 0 ? {} : _temp5;
4409
4438
  let statusText = "Unknown Server Error";
4410
4439
  let errorMessage = "Unknown @remix-run/router error";
4411
4440
  if (status === 400) {
@@ -4746,7 +4775,7 @@ exports.UNSAFE_DeferredData = DeferredData;
4746
4775
  exports.UNSAFE_ErrorResponseImpl = ErrorResponseImpl;
4747
4776
  exports.UNSAFE_convertRouteMatchToUiMatch = convertRouteMatchToUiMatch;
4748
4777
  exports.UNSAFE_convertRoutesToDataRoutes = convertRoutesToDataRoutes;
4749
- exports.UNSAFE_getPathContributingMatches = getPathContributingMatches;
4778
+ exports.UNSAFE_getResolveToMatches = getResolveToMatches;
4750
4779
  exports.UNSAFE_invariant = invariant;
4751
4780
  exports.UNSAFE_warning = warning;
4752
4781
  exports.createBrowserHistory = createBrowserHistory;