@remix-run/router 0.0.0-experimental-8f9ef191 → 0.0.0-experimental-9463fb5e

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,24 @@
1
1
  # `@remix-run/router`
2
2
 
3
+ ## 1.13.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Revert the `useResolvedPath` fix for splat routes due to a large number of applications that were relying on the buggy behavior (see https://github.com/remix-run/react-router/issues/11052#issuecomment-1836589329). We plan to re-introduce this fix behind a future flag in the next minor version. ([#11078](https://github.com/remix-run/react-router/pull/11078))
8
+
9
+ ## 1.13.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Export the `PathParam` type from the public API ([#10719](https://github.com/remix-run/react-router/pull/10719))
14
+
15
+ ### Patch Changes
16
+
17
+ - Fix bug with `resolveTo` in splat routes ([#11045](https://github.com/remix-run/react-router/pull/11045))
18
+ - This is a follow up to [#10983](https://github.com/remix-run/react-router/pull/10983) to handle the few other code paths using `getPathContributingMatches`
19
+ - This removes the `UNSAFE_getPathContributingMatches` export from `@remix-run/router` since we no longer need this in the `react-router`/`react-router-dom` layers
20
+ - Do not revalidate unmounted fetchers when `v7_fetcherPersist` is enabled ([#11044](https://github.com/remix-run/react-router/pull/11044))
21
+
3
22
  ## 1.12.0
4
23
 
5
24
  ### Minor Changes
package/README.md CHANGED
@@ -4,7 +4,7 @@ The `@remix-run/router` package is a framework-agnostic routing package (sometim
4
4
 
5
5
  If you're using React Router, you should never `import` anything directly from the `@remix-run/router` - you should have everything you need in `react-router-dom` (or `react-router`/`react-router-native` if you're not rendering in the browser). All of those packages should re-export everything you would otherwise need from `@remix-run/router`.
6
6
 
7
- > **Warning**
7
+ > [!WARNING]
8
8
  >
9
9
  > This router is a low-level package intended to be consumed by UI layer routing libraries. You should very likely not be using this package directly unless you are authoring a routing library such as [`react-router-dom`][react-router-repo] or one of it's other [UI ports][remix-routers-repo].
10
10
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v0.0.0-experimental-8f9ef191
2
+ * @remix-run/router v0.0.0-experimental-9463fb5e
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1194,9 +1194,16 @@ function getPathContributingMatches(matches) {
1194
1194
 
1195
1195
  // Return the array of pathnames for the current route matches - used to
1196
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);
1197
+ function getResolveToMatches(matches, v7_relativeSplatPath) {
1198
+ let pathMatches = getPathContributingMatches(matches);
1199
+
1200
+ // When v7_relativeSplatPath is enabled, use the full pathname for the leaf
1201
+ // match so we include splat values for "." links. See:
1202
+ // https://github.com/remix-run/react-router/issues/11052#issuecomment-1836589329
1203
+ if (v7_relativeSplatPath) {
1204
+ return pathMatches.map((match, idx) => idx === matches.length - 1 ? match.pathname : match.pathnameBase);
1205
+ }
1206
+ return pathMatches.map(match => match.pathnameBase);
1200
1207
  }
1201
1208
 
1202
1209
  /**
@@ -1230,37 +1237,21 @@ function resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {
1230
1237
  // to the current location's pathname and *not* the route pathname.
1231
1238
  if (toPathname == null) {
1232
1239
  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("/");
1246
1240
  } else {
1247
1241
  let routePathnameIndex = routePathnames.length - 1;
1248
- if (toPathname.startsWith("..")) {
1249
- let toSegments = toPathname.split("/");
1250
1242
 
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".
1243
+ // With relative="route" (the default), each leading .. segment means
1244
+ // "go up one route" instead of "go up one URL segment". This is a key
1245
+ // difference from how <a href> works and a major reason we call this a
1246
+ // "to" value instead of a "href".
1247
+ if (!isPathRelative && toPathname.startsWith("..")) {
1248
+ let toSegments = toPathname.split("/");
1255
1249
  while (toSegments[0] === "..") {
1256
1250
  toSegments.shift();
1257
1251
  routePathnameIndex -= 1;
1258
1252
  }
1259
1253
  to.pathname = toSegments.join("/");
1260
1254
  }
1261
-
1262
- // If there are more ".." segments than parent routes, resolve relative to
1263
- // the root / URL.
1264
1255
  from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
1265
1256
  }
1266
1257
  let path = resolvePath(to, from);
@@ -1687,7 +1678,8 @@ function createRouter(init) {
1687
1678
  v7_fetcherPersist: false,
1688
1679
  v7_normalizeFormMethod: false,
1689
1680
  v7_partialHydration: false,
1690
- v7_prependBasename: false
1681
+ v7_prependBasename: false,
1682
+ v7_relativeSplatPath: false
1691
1683
  }, init.future);
1692
1684
  // Cleanup function for history
1693
1685
  let unlistenHistory = null;
@@ -2093,7 +2085,7 @@ function createRouter(init) {
2093
2085
  init.history.go(to);
2094
2086
  return;
2095
2087
  }
2096
- let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, to, opts == null ? void 0 : opts.fromRouteId, opts == null ? void 0 : opts.relative);
2088
+ 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);
2097
2089
  let {
2098
2090
  path,
2099
2091
  submission,
@@ -2342,7 +2334,7 @@ function createRouter(init) {
2342
2334
  })
2343
2335
  };
2344
2336
  } else {
2345
- result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename);
2337
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
2346
2338
  if (request.signal.aborted) {
2347
2339
  return {
2348
2340
  shortCircuited: true
@@ -2552,7 +2544,7 @@ function createRouter(init) {
2552
2544
  if (fetchControllers.has(key)) abortFetcher(key);
2553
2545
  let flushSync = (opts && opts.unstable_flushSync) === true;
2554
2546
  let routesToUse = inFlightDataRoutes || dataRoutes;
2555
- let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, routeId, opts == null ? void 0 : opts.relative);
2547
+ let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, future.v7_relativeSplatPath, routeId, opts == null ? void 0 : opts.relative);
2556
2548
  let matches = matchRoutes(routesToUse, normalizedPath, basename);
2557
2549
  if (!matches) {
2558
2550
  setFetcherError(key, routeId, getInternalRouterError(404, {
@@ -2617,7 +2609,7 @@ function createRouter(init) {
2617
2609
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
2618
2610
  fetchControllers.set(key, abortController);
2619
2611
  let originatingLoadId = incrementingLoadId;
2620
- let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename);
2612
+ let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
2621
2613
  if (fetchRequest.signal.aborted) {
2622
2614
  // We can delete this so long as we weren't aborted by our own fetcher
2623
2615
  // re-submit which would have put _new_ controller is in fetchControllers
@@ -2626,32 +2618,40 @@ function createRouter(init) {
2626
2618
  }
2627
2619
  return;
2628
2620
  }
2629
- if (deletedFetchers.has(key)) {
2630
- updateFetcherState(key, getDoneFetcher(undefined));
2631
- return;
2632
- }
2633
- if (isRedirectResult(actionResult)) {
2634
- fetchControllers.delete(key);
2635
- if (pendingNavigationLoadId > originatingLoadId) {
2636
- // A new navigation was kicked off after our action started, so that
2637
- // should take precedence over this redirect navigation. We already
2638
- // set isRevalidationRequired so all loaders for the new route should
2639
- // fire unless opted out via shouldRevalidate
2621
+
2622
+ // When using v7_fetcherPersist, we don't want errors bubbling up to the UI
2623
+ // or redirects processed for unmounted fetchers so we just revert them to
2624
+ // idle
2625
+ if (future.v7_fetcherPersist && deletedFetchers.has(key)) {
2626
+ if (isRedirectResult(actionResult) || isErrorResult(actionResult)) {
2640
2627
  updateFetcherState(key, getDoneFetcher(undefined));
2641
2628
  return;
2642
- } else {
2643
- fetchRedirectIds.add(key);
2644
- updateFetcherState(key, getLoadingFetcher(submission));
2645
- return startRedirectNavigation(state, actionResult, {
2646
- fetcherSubmission: submission
2647
- });
2648
2629
  }
2649
- }
2630
+ // Let SuccessResult's fall through for revalidation
2631
+ } else {
2632
+ if (isRedirectResult(actionResult)) {
2633
+ fetchControllers.delete(key);
2634
+ if (pendingNavigationLoadId > originatingLoadId) {
2635
+ // A new navigation was kicked off after our action started, so that
2636
+ // should take precedence over this redirect navigation. We already
2637
+ // set isRevalidationRequired so all loaders for the new route should
2638
+ // fire unless opted out via shouldRevalidate
2639
+ updateFetcherState(key, getDoneFetcher(undefined));
2640
+ return;
2641
+ } else {
2642
+ fetchRedirectIds.add(key);
2643
+ updateFetcherState(key, getLoadingFetcher(submission));
2644
+ return startRedirectNavigation(state, actionResult, {
2645
+ fetcherSubmission: submission
2646
+ });
2647
+ }
2648
+ }
2650
2649
 
2651
- // Process any non-redirect errors thrown
2652
- if (isErrorResult(actionResult)) {
2653
- setFetcherError(key, routeId, actionResult.error);
2654
- return;
2650
+ // Process any non-redirect errors thrown
2651
+ if (isErrorResult(actionResult)) {
2652
+ setFetcherError(key, routeId, actionResult.error);
2653
+ return;
2654
+ }
2655
2655
  }
2656
2656
  if (isDeferredResult(actionResult)) {
2657
2657
  throw getInternalRouterError(400, {
@@ -2770,7 +2770,7 @@ function createRouter(init) {
2770
2770
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
2771
2771
  fetchControllers.set(key, abortController);
2772
2772
  let originatingLoadId = incrementingLoadId;
2773
- let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename);
2773
+ let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
2774
2774
 
2775
2775
  // Deferred isn't supported for fetcher loads, await everything and treat it
2776
2776
  // as a normal load. resolveDeferredData will return undefined if this
@@ -2788,6 +2788,9 @@ function createRouter(init) {
2788
2788
  if (fetchRequest.signal.aborted) {
2789
2789
  return;
2790
2790
  }
2791
+
2792
+ // We don't want errors bubbling up or redirects followed for unmounted
2793
+ // fetchers, so short circuit here if it was removed from the UI
2791
2794
  if (deletedFetchers.has(key)) {
2792
2795
  updateFetcherState(key, getDoneFetcher(undefined));
2793
2796
  return;
@@ -2918,9 +2921,9 @@ function createRouter(init) {
2918
2921
  // Call all navigation loaders and revalidating fetcher loaders in parallel,
2919
2922
  // then slice off the results into separate arrays so we can handle them
2920
2923
  // accordingly
2921
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename)), ...fetchersToLoad.map(f => {
2924
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath)), ...fetchersToLoad.map(f => {
2922
2925
  if (f.matches && f.match && f.controller) {
2923
- return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, f.controller.signal), f.match, f.matches, manifest, mapRouteProperties, basename);
2926
+ return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, f.controller.signal), f.match, f.matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
2924
2927
  } else {
2925
2928
  let error = {
2926
2929
  type: ResultType.error,
@@ -3239,6 +3242,11 @@ function createRouter(init) {
3239
3242
  ////////////////////////////////////////////////////////////////////////////////
3240
3243
 
3241
3244
  const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
3245
+
3246
+ /**
3247
+ * Future flags to toggle new feature behavior
3248
+ */
3249
+
3242
3250
  function createStaticHandler(routes, opts) {
3243
3251
  invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
3244
3252
  let manifest = {};
@@ -3255,6 +3263,11 @@ function createStaticHandler(routes, opts) {
3255
3263
  } else {
3256
3264
  mapRouteProperties = defaultMapRouteProperties;
3257
3265
  }
3266
+ // Config driven behavior flags
3267
+ let future = _extends({
3268
+ v7_relativeSplatPath: false,
3269
+ v7_throwAbortReason: false
3270
+ }, opts ? opts.future : null);
3258
3271
  let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
3259
3272
 
3260
3273
  /**
@@ -3470,14 +3483,13 @@ function createStaticHandler(routes, opts) {
3470
3483
  error
3471
3484
  };
3472
3485
  } else {
3473
- result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename, {
3486
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath, {
3474
3487
  isStaticRequest: true,
3475
3488
  isRouteRequest,
3476
3489
  requestContext
3477
3490
  });
3478
3491
  if (request.signal.aborted) {
3479
- let method = isRouteRequest ? "queryRoute" : "query";
3480
- throw new Error(method + "() call aborted: " + request.method + " " + request.url);
3492
+ throwStaticHandlerAbortedError(request, isRouteRequest, future);
3481
3493
  }
3482
3494
  }
3483
3495
  if (isRedirectResult(result)) {
@@ -3589,14 +3601,13 @@ function createStaticHandler(routes, opts) {
3589
3601
  activeDeferreds: null
3590
3602
  };
3591
3603
  }
3592
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, {
3604
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath, {
3593
3605
  isStaticRequest: true,
3594
3606
  isRouteRequest,
3595
3607
  requestContext
3596
3608
  }))]);
3597
3609
  if (request.signal.aborted) {
3598
- let method = isRouteRequest ? "queryRoute" : "query";
3599
- throw new Error(method + "() call aborted: " + request.method + " " + request.url);
3610
+ throwStaticHandlerAbortedError(request, isRouteRequest, future);
3600
3611
  }
3601
3612
 
3602
3613
  // Process and commit output from loaders
@@ -3641,10 +3652,18 @@ function getStaticContextFromError(routes, context, error) {
3641
3652
  });
3642
3653
  return newContext;
3643
3654
  }
3655
+ function throwStaticHandlerAbortedError(request, isRouteRequest, future) {
3656
+ let method = isRouteRequest ? "queryRoute" : "query";
3657
+ if (future.v7_throwAbortReason && request.signal.reason !== undefined) {
3658
+ throw request.signal.reason;
3659
+ } else {
3660
+ throw new Error(method + "() call aborted: " + request.method + " " + request.url);
3661
+ }
3662
+ }
3644
3663
  function isSubmissionNavigation(opts) {
3645
3664
  return opts != null && ("formData" in opts && opts.formData != null || "body" in opts && opts.body !== undefined);
3646
3665
  }
3647
- function normalizeTo(location, matches, basename, prependBasename, to, fromRouteId, relative) {
3666
+ function normalizeTo(location, matches, basename, prependBasename, to, v7_relativeSplatPath, fromRouteId, relative) {
3648
3667
  let contextualMatches;
3649
3668
  let activeRouteMatch;
3650
3669
  if (fromRouteId) {
@@ -3664,7 +3683,7 @@ function normalizeTo(location, matches, basename, prependBasename, to, fromRoute
3664
3683
  }
3665
3684
 
3666
3685
  // Resolve the relative path
3667
- let path = resolveTo(to ? to : ".", getResolveToMatches(contextualMatches), 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");
3668
3687
 
3669
3688
  // When `to` is not specified we inherit search/hash from the current
3670
3689
  // location, unlike when to="." and we just inherit the path.
@@ -3954,7 +3973,13 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
3954
3973
  // Is this route unhydrated (when v7_partialHydration=true) such that we need
3955
3974
  // to call it's loader on the initial router creation
3956
3975
  function isUnhydratedRoute(state, route) {
3957
- return route.loader != null && state.loaderData[route.id] === undefined && (!state.errors ||
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 ||
3958
3983
  // Loader ran but errored - don't re-run
3959
3984
  state.errors[route.id] === undefined);
3960
3985
  }
@@ -4044,7 +4069,7 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
4044
4069
  lazy: undefined
4045
4070
  }));
4046
4071
  }
4047
- 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) {
4048
4073
  if (opts === void 0) {
4049
4074
  opts = {};
4050
4075
  }
@@ -4134,7 +4159,7 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
4134
4159
 
4135
4160
  // Support relative routing in internal redirects
4136
4161
  if (!ABSOLUTE_URL_REGEX.test(location)) {
4137
- 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);
4138
4163
  } else if (!opts.isStaticRequest) {
4139
4164
  // Strip off the protocol+origin for same-origin + same-basename absolute
4140
4165
  // redirects. If this is a static request, we can let it go back to the
@@ -4175,13 +4200,20 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
4175
4200
  throw queryRouteResponse;
4176
4201
  }
4177
4202
  let data;
4178
- let contentType = result.headers.get("Content-Type");
4179
- // Check between word boundaries instead of startsWith() due to the last
4180
- // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
4181
- if (contentType && /\bapplication\/json\b/.test(contentType)) {
4182
- data = await result.json();
4183
- } else {
4184
- 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
+ };
4185
4217
  }
4186
4218
  if (resultType === ResultType.error) {
4187
4219
  return {