@remix-run/router 1.13.1 → 1.14.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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v1.13.1
2
+ * @remix-run/router v1.14.0-pre.0
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,7 +1733,13 @@
1717
1733
  [route.id]: error
1718
1734
  };
1719
1735
  }
1720
- let initialized =
1736
+
1737
+ // "Initialized" here really means "Can `RouterProvider` render my route tree?"
1738
+ // Prior to `route.HydrateFallback`, we only had a root `fallbackElement` so we used
1739
+ // `state.initialized` to render that instead of `<DataRoutes>`. Now that we
1740
+ // support route level fallbacks we can always render and we'll just render
1741
+ // as deep as we have data for and detect the nearest ancestor HydrateFallback
1742
+ let initialized = future.v7_partialHydration ||
1721
1743
  // All initialMatches need to be loaded before we're ready. If we have lazy
1722
1744
  // functions around still then we'll need to run them in initialize()
1723
1745
  !initialMatches.some(m => m.route.lazy) && (
@@ -1889,8 +1911,10 @@
1889
1911
  // in the normal navigation flow. For SSR it's expected that lazy modules are
1890
1912
  // resolved prior to router creation since we can't go into a fallbackElement
1891
1913
  // UI for SSR'd apps
1892
- if (!state.initialized) {
1893
- startNavigation(Action.Pop, state.location);
1914
+ if (!state.initialized || future.v7_partialHydration && state.matches.some(m => isUnhydratedRoute(state, m.route))) {
1915
+ startNavigation(Action.Pop, state.location, {
1916
+ initialHydration: true
1917
+ });
1894
1918
  }
1895
1919
  return router;
1896
1920
  }
@@ -2079,7 +2103,7 @@
2079
2103
  init.history.go(to);
2080
2104
  return;
2081
2105
  }
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);
2106
+ 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
2107
  let {
2084
2108
  path,
2085
2109
  submission,
@@ -2280,7 +2304,7 @@
2280
2304
  shortCircuited,
2281
2305
  loaderData,
2282
2306
  errors
2283
- } = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, flushSync, pendingActionData, pendingError);
2307
+ } = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync, pendingActionData, pendingError);
2284
2308
  if (shortCircuited) {
2285
2309
  return;
2286
2310
  }
@@ -2328,7 +2352,7 @@
2328
2352
  })
2329
2353
  };
2330
2354
  } else {
2331
- result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename);
2355
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
2332
2356
  if (request.signal.aborted) {
2333
2357
  return {
2334
2358
  shortCircuited: true
@@ -2387,7 +2411,7 @@
2387
2411
 
2388
2412
  // Call all applicable loaders for the given matches, handling redirects,
2389
2413
  // errors, etc.
2390
- async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, flushSync, pendingActionData, pendingError) {
2414
+ async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync, pendingActionData, pendingError) {
2391
2415
  // Figure out the right navigation we want to use for data loading
2392
2416
  let loadingNavigation = overrideNavigation || getLoadingNavigation(location, submission);
2393
2417
 
@@ -2395,7 +2419,7 @@
2395
2419
  // we have it on the loading navigation so use that if available
2396
2420
  let activeSubmission = submission || fetcherSubmission || getSubmissionFromNavigation(loadingNavigation);
2397
2421
  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);
2422
+ 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
2423
 
2400
2424
  // Cancel pending deferreds for no-longer-matched routes or routes we're
2401
2425
  // about to reload. Note that if this is an action reload we would have
@@ -2427,7 +2451,9 @@
2427
2451
  // state. If not, we need to switch to our loading state and load data,
2428
2452
  // preserving any new action data or existing action data (in the case of
2429
2453
  // a revalidation interrupting an actionReload)
2430
- if (!isUninterruptedRevalidation) {
2454
+ // If we have partialHydration enabled, then don't update the state for the
2455
+ // initial data load since iot's not a "navigation"
2456
+ if (!isUninterruptedRevalidation && (!future.v7_partialHydration || !initialHydration)) {
2431
2457
  revalidatingFetchers.forEach(rf => {
2432
2458
  let fetcher = state.fetchers.get(rf.key);
2433
2459
  let revalidatingFetcher = getLoadingFetcher(undefined, fetcher ? fetcher.data : undefined);
@@ -2536,7 +2562,7 @@
2536
2562
  if (fetchControllers.has(key)) abortFetcher(key);
2537
2563
  let flushSync = (opts && opts.unstable_flushSync) === true;
2538
2564
  let routesToUse = inFlightDataRoutes || dataRoutes;
2539
- let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, routeId, opts == null ? void 0 : opts.relative);
2565
+ let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, future.v7_relativeSplatPath, routeId, opts == null ? void 0 : opts.relative);
2540
2566
  let matches = matchRoutes(routesToUse, normalizedPath, basename);
2541
2567
  if (!matches) {
2542
2568
  setFetcherError(key, routeId, getInternalRouterError(404, {
@@ -2601,7 +2627,7 @@
2601
2627
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
2602
2628
  fetchControllers.set(key, abortController);
2603
2629
  let originatingLoadId = incrementingLoadId;
2604
- let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename);
2630
+ let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
2605
2631
  if (fetchRequest.signal.aborted) {
2606
2632
  // We can delete this so long as we weren't aborted by our own fetcher
2607
2633
  // re-submit which would have put _new_ controller is in fetchControllers
@@ -2654,7 +2680,7 @@
2654
2680
  fetchReloadIds.set(key, loadId);
2655
2681
  let loadFetcher = getLoadingFetcher(submission, actionResult.data);
2656
2682
  state.fetchers.set(key, loadFetcher);
2657
- let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, {
2683
+ let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, false, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, {
2658
2684
  [match.route.id]: actionResult.data
2659
2685
  }, undefined // No need to send through errors since we short circuit above
2660
2686
  );
@@ -2754,7 +2780,7 @@
2754
2780
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
2755
2781
  fetchControllers.set(key, abortController);
2756
2782
  let originatingLoadId = incrementingLoadId;
2757
- let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename);
2783
+ let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
2758
2784
 
2759
2785
  // Deferred isn't supported for fetcher loads, await everything and treat it
2760
2786
  // as a normal load. resolveDeferredData will return undefined if this
@@ -2902,9 +2928,9 @@
2902
2928
  // Call all navigation loaders and revalidating fetcher loaders in parallel,
2903
2929
  // then slice off the results into separate arrays so we can handle them
2904
2930
  // accordingly
2905
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename)), ...fetchersToLoad.map(f => {
2931
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath)), ...fetchersToLoad.map(f => {
2906
2932
  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);
2933
+ return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, f.controller.signal), f.match, f.matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
2908
2934
  } else {
2909
2935
  let error = {
2910
2936
  type: ResultType.error,
@@ -3181,6 +3207,9 @@
3181
3207
  get basename() {
3182
3208
  return basename;
3183
3209
  },
3210
+ get future() {
3211
+ return future;
3212
+ },
3184
3213
  get state() {
3185
3214
  return state;
3186
3215
  },
@@ -3220,6 +3249,11 @@
3220
3249
  ////////////////////////////////////////////////////////////////////////////////
3221
3250
 
3222
3251
  const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
3252
+
3253
+ /**
3254
+ * Future flags to toggle new feature behavior
3255
+ */
3256
+
3223
3257
  function createStaticHandler(routes, opts) {
3224
3258
  invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
3225
3259
  let manifest = {};
@@ -3236,6 +3270,10 @@
3236
3270
  } else {
3237
3271
  mapRouteProperties = defaultMapRouteProperties;
3238
3272
  }
3273
+ // Config driven behavior flags
3274
+ let future = _extends({
3275
+ v7_relativeSplatPath: false
3276
+ }, opts ? opts.future : null);
3239
3277
  let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
3240
3278
 
3241
3279
  /**
@@ -3451,7 +3489,7 @@
3451
3489
  error
3452
3490
  };
3453
3491
  } else {
3454
- result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename, {
3492
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath, {
3455
3493
  isStaticRequest: true,
3456
3494
  isRouteRequest,
3457
3495
  requestContext
@@ -3570,7 +3608,7 @@
3570
3608
  activeDeferreds: null
3571
3609
  };
3572
3610
  }
3573
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, {
3611
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath, {
3574
3612
  isStaticRequest: true,
3575
3613
  isRouteRequest,
3576
3614
  requestContext
@@ -3625,7 +3663,7 @@
3625
3663
  function isSubmissionNavigation(opts) {
3626
3664
  return opts != null && ("formData" in opts && opts.formData != null || "body" in opts && opts.body !== undefined);
3627
3665
  }
3628
- function normalizeTo(location, matches, basename, prependBasename, to, fromRouteId, relative) {
3666
+ function normalizeTo(location, matches, basename, prependBasename, to, v7_relativeSplatPath, fromRouteId, relative) {
3629
3667
  let contextualMatches;
3630
3668
  let activeRouteMatch;
3631
3669
  if (fromRouteId) {
@@ -3645,7 +3683,7 @@
3645
3683
  }
3646
3684
 
3647
3685
  // Resolve the relative path
3648
- let path = resolveTo(to ? to : ".", getPathContributingMatches(contextualMatches).map(m => m.pathnameBase), stripBasename(location.pathname, basename) || location.pathname, relative === "path");
3686
+ let path = resolveTo(to ? to : ".", getResolveToMatches(contextualMatches, v7_relativeSplatPath), stripBasename(location.pathname, basename) || location.pathname, relative === "path");
3649
3687
 
3650
3688
  // When `to` is not specified we inherit search/hash from the current
3651
3689
  // location, unlike when to="." and we just inherit the path.
@@ -3809,7 +3847,7 @@
3809
3847
  }
3810
3848
  return boundaryMatches;
3811
3849
  }
3812
- function getMatchesToLoad(history, state, matches, submission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionData, pendingError) {
3850
+ function getMatchesToLoad(history, state, matches, submission, location, isInitialLoad, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionData, pendingError) {
3813
3851
  let actionResult = pendingError ? Object.values(pendingError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : undefined;
3814
3852
  let currentUrl = history.createURL(state.location);
3815
3853
  let nextUrl = history.createURL(location);
@@ -3818,6 +3856,11 @@
3818
3856
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
3819
3857
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
3820
3858
  let navigationMatches = boundaryMatches.filter((match, index) => {
3859
+ if (isInitialLoad) {
3860
+ // On initial hydration we don't do any shouldRevalidate stuff - we just
3861
+ // call the unhydrated loaders
3862
+ return isUnhydratedRoute(state, match.route);
3863
+ }
3821
3864
  if (match.route.lazy) {
3822
3865
  // We haven't loaded this route yet so we don't know if it's got a loader!
3823
3866
  return true;
@@ -3857,8 +3900,12 @@
3857
3900
  // Pick fetcher.loads that need to be revalidated
3858
3901
  let revalidatingFetchers = [];
3859
3902
  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)) {
3903
+ // Don't revalidate:
3904
+ // - on initial load (shouldn't be any fetchers then anyway)
3905
+ // - if fetcher won't be present in the subsequent render
3906
+ // - no longer matches the URL (v7_fetcherPersist=false)
3907
+ // - was unmounted but persisted due to v7_fetcherPersist=true
3908
+ if (isInitialLoad || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
3862
3909
  return;
3863
3910
  }
3864
3911
  let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
@@ -3922,6 +3969,20 @@
3922
3969
  });
3923
3970
  return [navigationMatches, revalidatingFetchers];
3924
3971
  }
3972
+
3973
+ // Is this route unhydrated (when v7_partialHydration=true) such that we need
3974
+ // to call it's loader on the initial router creation
3975
+ function isUnhydratedRoute(state, route) {
3976
+ if (!route.loader) {
3977
+ return false;
3978
+ }
3979
+ if (route.loader.hydrate) {
3980
+ return true;
3981
+ }
3982
+ return state.loaderData[route.id] === undefined && (!state.errors ||
3983
+ // Loader ran but errored - don't re-run
3984
+ state.errors[route.id] === undefined);
3985
+ }
3925
3986
  function isNewLoader(currentLoaderData, currentMatch, match) {
3926
3987
  let isNew =
3927
3988
  // [a] -> [a, b]
@@ -4008,7 +4069,7 @@
4008
4069
  lazy: undefined
4009
4070
  }));
4010
4071
  }
4011
- async function callLoaderOrAction(type, request, match, matches, manifest, mapRouteProperties, basename, opts) {
4072
+ async function callLoaderOrAction(type, request, match, matches, manifest, mapRouteProperties, basename, v7_relativeSplatPath, opts) {
4012
4073
  if (opts === void 0) {
4013
4074
  opts = {};
4014
4075
  }
@@ -4098,7 +4159,7 @@
4098
4159
 
4099
4160
  // Support relative routing in internal redirects
4100
4161
  if (!ABSOLUTE_URL_REGEX.test(location)) {
4101
- location = normalizeTo(new URL(request.url), matches.slice(0, matches.indexOf(match) + 1), basename, true, location);
4162
+ location = normalizeTo(new URL(request.url), matches.slice(0, matches.indexOf(match) + 1), basename, true, location, v7_relativeSplatPath);
4102
4163
  } else if (!opts.isStaticRequest) {
4103
4164
  // Strip off the protocol+origin for same-origin + same-basename absolute
4104
4165
  // redirects. If this is a static request, we can let it go back to the
@@ -4139,13 +4200,20 @@
4139
4200
  throw queryRouteResponse;
4140
4201
  }
4141
4202
  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();
4203
+ try {
4204
+ let contentType = result.headers.get("Content-Type");
4205
+ // Check between word boundaries instead of startsWith() due to the last
4206
+ // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
4207
+ if (contentType && /\bapplication\/json\b/.test(contentType)) {
4208
+ data = await result.json();
4209
+ } else {
4210
+ data = await result.text();
4211
+ }
4212
+ } catch (e) {
4213
+ return {
4214
+ type: ResultType.error,
4215
+ error: e
4216
+ };
4149
4217
  }
4150
4218
  if (resultType === ResultType.error) {
4151
4219
  return {
@@ -4744,7 +4812,7 @@
4744
4812
  exports.UNSAFE_ErrorResponseImpl = ErrorResponseImpl;
4745
4813
  exports.UNSAFE_convertRouteMatchToUiMatch = convertRouteMatchToUiMatch;
4746
4814
  exports.UNSAFE_convertRoutesToDataRoutes = convertRoutesToDataRoutes;
4747
- exports.UNSAFE_getPathContributingMatches = getPathContributingMatches;
4815
+ exports.UNSAFE_getResolveToMatches = getResolveToMatches;
4748
4816
  exports.UNSAFE_invariant = invariant;
4749
4817
  exports.UNSAFE_warning = warning;
4750
4818
  exports.createBrowserHistory = createBrowserHistory;