@remix-run/router 1.13.1 → 1.14.0-pre.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v1.13.1
2
+ * @remix-run/router v1.14.0-pre.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1194,6 +1194,20 @@
1194
1194
  return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
1195
1195
  }
1196
1196
 
1197
+ // Return the array of pathnames for the current route matches - used to
1198
+ // generate the routePathnames input for resolveTo()
1199
+ function getResolveToMatches(matches, v7_relativeSplatPath) {
1200
+ let pathMatches = getPathContributingMatches(matches);
1201
+
1202
+ // When v7_relativeSplatPath is enabled, use the full pathname for the leaf
1203
+ // match so we include splat values for "." links. See:
1204
+ // https://github.com/remix-run/react-router/issues/11052#issuecomment-1836589329
1205
+ if (v7_relativeSplatPath) {
1206
+ return pathMatches.map((match, idx) => idx === matches.length - 1 ? match.pathname : match.pathnameBase);
1207
+ }
1208
+ return pathMatches.map(match => match.pathnameBase);
1209
+ }
1210
+
1197
1211
  /**
1198
1212
  * @private
1199
1213
  */
@@ -1226,7 +1240,7 @@
1226
1240
  if (toPathname == null) {
1227
1241
  from = locationPathname;
1228
1242
  } else if (isPathRelative) {
1229
- let fromSegments = routePathnames[routePathnames.length - 1].replace(/^\//, "").split("/");
1243
+ let fromSegments = routePathnames.length === 0 ? [] : routePathnames[routePathnames.length - 1].replace(/^\//, "").split("/");
1230
1244
  if (toPathname.startsWith("..")) {
1231
1245
  let toSegments = toPathname.split("/");
1232
1246
 
@@ -1681,7 +1695,9 @@
1681
1695
  let future = _extends({
1682
1696
  v7_fetcherPersist: false,
1683
1697
  v7_normalizeFormMethod: false,
1684
- v7_prependBasename: false
1698
+ v7_partialHydration: false,
1699
+ v7_prependBasename: false,
1700
+ v7_relativeSplatPath: false
1685
1701
  }, init.future);
1686
1702
  // Cleanup function for history
1687
1703
  let unlistenHistory = null;
@@ -1717,12 +1733,28 @@
1717
1733
  [route.id]: error
1718
1734
  };
1719
1735
  }
1720
- let initialized =
1721
- // All initialMatches need to be loaded before we're ready. If we have lazy
1722
- // functions around still then we'll need to run them in initialize()
1723
- !initialMatches.some(m => m.route.lazy) && (
1724
- // And we have to either have no loaders or have been provided hydrationData
1725
- !initialMatches.some(m => m.route.loader) || init.hydrationData != null);
1736
+ let initialized;
1737
+ let hasLazyRoutes = initialMatches.some(m => m.route.lazy);
1738
+ let hasLoaders = initialMatches.some(m => m.route.loader);
1739
+ if (hasLazyRoutes) {
1740
+ // All initialMatches need to be loaded before we're ready. If we have lazy
1741
+ // functions around still then we'll need to run them in initialize()
1742
+ initialized = false;
1743
+ } else if (!hasLoaders) {
1744
+ // If we've got no loaders to run, then we're good to go
1745
+ initialized = true;
1746
+ } else if (future.v7_partialHydration) {
1747
+ // If partial hydration is enabled, we're initialized so long as we were
1748
+ // provided with hydrationData for every route with a loader, and no loaders
1749
+ // were marked for explicit hydration
1750
+ let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
1751
+ let errors = init.hydrationData ? init.hydrationData.errors : null;
1752
+ initialized = initialMatches.every(m => m.route.loader && m.route.loader.hydrate !== true && (loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined));
1753
+ } else {
1754
+ // Without partial hydration - we're initialized if we were provided any
1755
+ // hydrationData - which is expected to be complete
1756
+ initialized = init.hydrationData != null;
1757
+ }
1726
1758
  let router;
1727
1759
  let state = {
1728
1760
  historyAction: init.history.action,
@@ -1890,7 +1922,9 @@
1890
1922
  // resolved prior to router creation since we can't go into a fallbackElement
1891
1923
  // UI for SSR'd apps
1892
1924
  if (!state.initialized) {
1893
- startNavigation(Action.Pop, state.location);
1925
+ startNavigation(Action.Pop, state.location, {
1926
+ initialHydration: true
1927
+ });
1894
1928
  }
1895
1929
  return router;
1896
1930
  }
@@ -2079,7 +2113,7 @@
2079
2113
  init.history.go(to);
2080
2114
  return;
2081
2115
  }
2082
- let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, to, opts == null ? void 0 : opts.fromRouteId, opts == null ? void 0 : opts.relative);
2116
+ let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, to, future.v7_relativeSplatPath, opts == null ? void 0 : opts.fromRouteId, opts == null ? void 0 : opts.relative);
2083
2117
  let {
2084
2118
  path,
2085
2119
  submission,
@@ -2280,7 +2314,7 @@
2280
2314
  shortCircuited,
2281
2315
  loaderData,
2282
2316
  errors
2283
- } = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, flushSync, pendingActionData, pendingError);
2317
+ } = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync, pendingActionData, pendingError);
2284
2318
  if (shortCircuited) {
2285
2319
  return;
2286
2320
  }
@@ -2328,7 +2362,7 @@
2328
2362
  })
2329
2363
  };
2330
2364
  } else {
2331
- result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename);
2365
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
2332
2366
  if (request.signal.aborted) {
2333
2367
  return {
2334
2368
  shortCircuited: true
@@ -2387,7 +2421,7 @@
2387
2421
 
2388
2422
  // Call all applicable loaders for the given matches, handling redirects,
2389
2423
  // errors, etc.
2390
- async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, flushSync, pendingActionData, pendingError) {
2424
+ async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync, pendingActionData, pendingError) {
2391
2425
  // Figure out the right navigation we want to use for data loading
2392
2426
  let loadingNavigation = overrideNavigation || getLoadingNavigation(location, submission);
2393
2427
 
@@ -2395,7 +2429,7 @@
2395
2429
  // we have it on the loading navigation so use that if available
2396
2430
  let activeSubmission = submission || fetcherSubmission || getSubmissionFromNavigation(loadingNavigation);
2397
2431
  let routesToUse = inFlightDataRoutes || dataRoutes;
2398
- let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionData, pendingError);
2432
+ 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);
2399
2433
 
2400
2434
  // Cancel pending deferreds for no-longer-matched routes or routes we're
2401
2435
  // about to reload. Note that if this is an action reload we would have
@@ -2427,7 +2461,9 @@
2427
2461
  // state. If not, we need to switch to our loading state and load data,
2428
2462
  // preserving any new action data or existing action data (in the case of
2429
2463
  // a revalidation interrupting an actionReload)
2430
- if (!isUninterruptedRevalidation) {
2464
+ // If we have partialHydration enabled, then don't update the state for the
2465
+ // initial data load since iot's not a "navigation"
2466
+ if (!isUninterruptedRevalidation && (!future.v7_partialHydration || !initialHydration)) {
2431
2467
  revalidatingFetchers.forEach(rf => {
2432
2468
  let fetcher = state.fetchers.get(rf.key);
2433
2469
  let revalidatingFetcher = getLoadingFetcher(undefined, fetcher ? fetcher.data : undefined);
@@ -2536,7 +2572,7 @@
2536
2572
  if (fetchControllers.has(key)) abortFetcher(key);
2537
2573
  let flushSync = (opts && opts.unstable_flushSync) === true;
2538
2574
  let routesToUse = inFlightDataRoutes || dataRoutes;
2539
- let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, routeId, opts == null ? void 0 : opts.relative);
2575
+ let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, future.v7_relativeSplatPath, routeId, opts == null ? void 0 : opts.relative);
2540
2576
  let matches = matchRoutes(routesToUse, normalizedPath, basename);
2541
2577
  if (!matches) {
2542
2578
  setFetcherError(key, routeId, getInternalRouterError(404, {
@@ -2601,7 +2637,7 @@
2601
2637
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
2602
2638
  fetchControllers.set(key, abortController);
2603
2639
  let originatingLoadId = incrementingLoadId;
2604
- let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename);
2640
+ let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
2605
2641
  if (fetchRequest.signal.aborted) {
2606
2642
  // We can delete this so long as we weren't aborted by our own fetcher
2607
2643
  // re-submit which would have put _new_ controller is in fetchControllers
@@ -2654,7 +2690,7 @@
2654
2690
  fetchReloadIds.set(key, loadId);
2655
2691
  let loadFetcher = getLoadingFetcher(submission, actionResult.data);
2656
2692
  state.fetchers.set(key, loadFetcher);
2657
- let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, {
2693
+ let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, false, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, {
2658
2694
  [match.route.id]: actionResult.data
2659
2695
  }, undefined // No need to send through errors since we short circuit above
2660
2696
  );
@@ -2754,7 +2790,7 @@
2754
2790
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
2755
2791
  fetchControllers.set(key, abortController);
2756
2792
  let originatingLoadId = incrementingLoadId;
2757
- let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename);
2793
+ let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
2758
2794
 
2759
2795
  // Deferred isn't supported for fetcher loads, await everything and treat it
2760
2796
  // as a normal load. resolveDeferredData will return undefined if this
@@ -2902,9 +2938,9 @@
2902
2938
  // Call all navigation loaders and revalidating fetcher loaders in parallel,
2903
2939
  // then slice off the results into separate arrays so we can handle them
2904
2940
  // accordingly
2905
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename)), ...fetchersToLoad.map(f => {
2941
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath)), ...fetchersToLoad.map(f => {
2906
2942
  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);
2943
+ return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, f.controller.signal), f.match, f.matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
2908
2944
  } else {
2909
2945
  let error = {
2910
2946
  type: ResultType.error,
@@ -3181,6 +3217,9 @@
3181
3217
  get basename() {
3182
3218
  return basename;
3183
3219
  },
3220
+ get future() {
3221
+ return future;
3222
+ },
3184
3223
  get state() {
3185
3224
  return state;
3186
3225
  },
@@ -3220,6 +3259,11 @@
3220
3259
  ////////////////////////////////////////////////////////////////////////////////
3221
3260
 
3222
3261
  const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
3262
+
3263
+ /**
3264
+ * Future flags to toggle new feature behavior
3265
+ */
3266
+
3223
3267
  function createStaticHandler(routes, opts) {
3224
3268
  invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
3225
3269
  let manifest = {};
@@ -3236,6 +3280,10 @@
3236
3280
  } else {
3237
3281
  mapRouteProperties = defaultMapRouteProperties;
3238
3282
  }
3283
+ // Config driven behavior flags
3284
+ let future = _extends({
3285
+ v7_relativeSplatPath: false
3286
+ }, opts ? opts.future : null);
3239
3287
  let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
3240
3288
 
3241
3289
  /**
@@ -3451,7 +3499,7 @@
3451
3499
  error
3452
3500
  };
3453
3501
  } else {
3454
- result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename, {
3502
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath, {
3455
3503
  isStaticRequest: true,
3456
3504
  isRouteRequest,
3457
3505
  requestContext
@@ -3570,7 +3618,7 @@
3570
3618
  activeDeferreds: null
3571
3619
  };
3572
3620
  }
3573
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, {
3621
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath, {
3574
3622
  isStaticRequest: true,
3575
3623
  isRouteRequest,
3576
3624
  requestContext
@@ -3625,7 +3673,7 @@
3625
3673
  function isSubmissionNavigation(opts) {
3626
3674
  return opts != null && ("formData" in opts && opts.formData != null || "body" in opts && opts.body !== undefined);
3627
3675
  }
3628
- function normalizeTo(location, matches, basename, prependBasename, to, fromRouteId, relative) {
3676
+ function normalizeTo(location, matches, basename, prependBasename, to, v7_relativeSplatPath, fromRouteId, relative) {
3629
3677
  let contextualMatches;
3630
3678
  let activeRouteMatch;
3631
3679
  if (fromRouteId) {
@@ -3645,7 +3693,7 @@
3645
3693
  }
3646
3694
 
3647
3695
  // Resolve the relative path
3648
- let path = resolveTo(to ? to : ".", getPathContributingMatches(contextualMatches).map(m => m.pathnameBase), stripBasename(location.pathname, basename) || location.pathname, relative === "path");
3696
+ let path = resolveTo(to ? to : ".", getResolveToMatches(contextualMatches, v7_relativeSplatPath), stripBasename(location.pathname, basename) || location.pathname, relative === "path");
3649
3697
 
3650
3698
  // When `to` is not specified we inherit search/hash from the current
3651
3699
  // location, unlike when to="." and we just inherit the path.
@@ -3809,7 +3857,7 @@
3809
3857
  }
3810
3858
  return boundaryMatches;
3811
3859
  }
3812
- function getMatchesToLoad(history, state, matches, submission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionData, pendingError) {
3860
+ function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionData, pendingError) {
3813
3861
  let actionResult = pendingError ? Object.values(pendingError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : undefined;
3814
3862
  let currentUrl = history.createURL(state.location);
3815
3863
  let nextUrl = history.createURL(location);
@@ -3818,6 +3866,11 @@
3818
3866
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
3819
3867
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
3820
3868
  let navigationMatches = boundaryMatches.filter((match, index) => {
3869
+ if (isInitialLoad) {
3870
+ // On initial hydration we don't do any shouldRevalidate stuff - we just
3871
+ // call the unhydrated loaders
3872
+ return isUnhydratedRoute(state, match.route);
3873
+ }
3821
3874
  if (match.route.lazy) {
3822
3875
  // We haven't loaded this route yet so we don't know if it's got a loader!
3823
3876
  return true;
@@ -3857,8 +3910,12 @@
3857
3910
  // Pick fetcher.loads that need to be revalidated
3858
3911
  let revalidatingFetchers = [];
3859
3912
  fetchLoadMatches.forEach((f, key) => {
3860
- // Don't revalidate if fetcher won't be present in the subsequent render
3861
- if (!matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
3913
+ // Don't revalidate:
3914
+ // - on initial load (shouldn't be any fetchers then anyway)
3915
+ // - if fetcher won't be present in the subsequent render
3916
+ // - no longer matches the URL (v7_fetcherPersist=false)
3917
+ // - was unmounted but persisted due to v7_fetcherPersist=true
3918
+ if (isInitialLoad || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
3862
3919
  return;
3863
3920
  }
3864
3921
  let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
@@ -3922,6 +3979,20 @@
3922
3979
  });
3923
3980
  return [navigationMatches, revalidatingFetchers];
3924
3981
  }
3982
+
3983
+ // Is this route unhydrated (when v7_partialHydration=true) such that we need
3984
+ // to call it's loader on the initial router creation
3985
+ function isUnhydratedRoute(state, route) {
3986
+ if (!route.loader) {
3987
+ return false;
3988
+ }
3989
+ if (route.loader.hydrate) {
3990
+ return true;
3991
+ }
3992
+ return state.loaderData[route.id] === undefined && (!state.errors ||
3993
+ // Loader ran but errored - don't re-run
3994
+ state.errors[route.id] === undefined);
3995
+ }
3925
3996
  function isNewLoader(currentLoaderData, currentMatch, match) {
3926
3997
  let isNew =
3927
3998
  // [a] -> [a, b]
@@ -4008,7 +4079,7 @@
4008
4079
  lazy: undefined
4009
4080
  }));
4010
4081
  }
4011
- async function callLoaderOrAction(type, request, match, matches, manifest, mapRouteProperties, basename, opts) {
4082
+ async function callLoaderOrAction(type, request, match, matches, manifest, mapRouteProperties, basename, v7_relativeSplatPath, opts) {
4012
4083
  if (opts === void 0) {
4013
4084
  opts = {};
4014
4085
  }
@@ -4098,7 +4169,7 @@
4098
4169
 
4099
4170
  // Support relative routing in internal redirects
4100
4171
  if (!ABSOLUTE_URL_REGEX.test(location)) {
4101
- location = normalizeTo(new URL(request.url), matches.slice(0, matches.indexOf(match) + 1), basename, true, location);
4172
+ location = normalizeTo(new URL(request.url), matches.slice(0, matches.indexOf(match) + 1), basename, true, location, v7_relativeSplatPath);
4102
4173
  } else if (!opts.isStaticRequest) {
4103
4174
  // Strip off the protocol+origin for same-origin + same-basename absolute
4104
4175
  // redirects. If this is a static request, we can let it go back to the
@@ -4139,13 +4210,20 @@
4139
4210
  throw queryRouteResponse;
4140
4211
  }
4141
4212
  let data;
4142
- let contentType = result.headers.get("Content-Type");
4143
- // Check between word boundaries instead of startsWith() due to the last
4144
- // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
4145
- if (contentType && /\bapplication\/json\b/.test(contentType)) {
4146
- data = await result.json();
4147
- } else {
4148
- data = await result.text();
4213
+ try {
4214
+ let contentType = result.headers.get("Content-Type");
4215
+ // Check between word boundaries instead of startsWith() due to the last
4216
+ // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
4217
+ if (contentType && /\bapplication\/json\b/.test(contentType)) {
4218
+ data = await result.json();
4219
+ } else {
4220
+ data = await result.text();
4221
+ }
4222
+ } catch (e) {
4223
+ return {
4224
+ type: ResultType.error,
4225
+ error: e
4226
+ };
4149
4227
  }
4150
4228
  if (resultType === ResultType.error) {
4151
4229
  return {
@@ -4744,7 +4822,7 @@
4744
4822
  exports.UNSAFE_ErrorResponseImpl = ErrorResponseImpl;
4745
4823
  exports.UNSAFE_convertRouteMatchToUiMatch = convertRouteMatchToUiMatch;
4746
4824
  exports.UNSAFE_convertRoutesToDataRoutes = convertRoutesToDataRoutes;
4747
- exports.UNSAFE_getPathContributingMatches = getPathContributingMatches;
4825
+ exports.UNSAFE_getResolveToMatches = getResolveToMatches;
4748
4826
  exports.UNSAFE_invariant = invariant;
4749
4827
  exports.UNSAFE_warning = warning;
4750
4828
  exports.createBrowserHistory = createBrowserHistory;