@remix-run/router 1.3.2 → 1.3.3-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.
package/index.ts CHANGED
@@ -63,7 +63,6 @@ export {
63
63
  createPath,
64
64
  createHashHistory,
65
65
  createMemoryHistory,
66
- invariant,
67
66
  parsePath,
68
67
  } from "./history";
69
68
 
@@ -82,3 +81,5 @@ export {
82
81
  convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes,
83
82
  getPathContributingMatches as UNSAFE_getPathContributingMatches,
84
83
  } from "./utils";
84
+
85
+ export { invariant as UNSAFE_invariant } from "./history";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remix-run/router",
3
- "version": "1.3.2",
3
+ "version": "1.3.3-pre.0",
4
4
  "description": "Nested/Data-driven/Framework-agnostic Routing",
5
5
  "keywords": [
6
6
  "remix",
package/router.ts CHANGED
@@ -211,6 +211,15 @@ export interface Router {
211
211
  */
212
212
  deleteBlocker(key: string): void;
213
213
 
214
+ /**
215
+ * @internal
216
+ * PRIVATE - DO NOT USE
217
+ *
218
+ * HMR needs to pass in-flight route updates to React Router
219
+ * TODO: Replace this with granular route update APIs (addRoute, updateRoute, deleteRoute)
220
+ */
221
+ _internalSetRoutes(routes: AgnosticRouteObject[]): void;
222
+
214
223
  /**
215
224
  * @internal
216
225
  * PRIVATE - DO NOT USE
@@ -556,8 +565,6 @@ interface HandleLoadersResult extends ShortCircuitable {
556
565
  interface FetchLoadMatch {
557
566
  routeId: string;
558
567
  path: string;
559
- match: AgnosticDataRouteMatch;
560
- matches: AgnosticDataRouteMatch[];
561
568
  }
562
569
 
563
570
  /**
@@ -565,6 +572,8 @@ interface FetchLoadMatch {
565
572
  */
566
573
  interface RevalidatingFetcher extends FetchLoadMatch {
567
574
  key: string;
575
+ match: AgnosticDataRouteMatch | null;
576
+ matches: AgnosticDataRouteMatch[] | null;
568
577
  }
569
578
 
570
579
  /**
@@ -644,6 +653,7 @@ export function createRouter(init: RouterInit): Router {
644
653
  );
645
654
 
646
655
  let dataRoutes = convertRoutesToDataRoutes(init.routes);
656
+ let inFlightDataRoutes: AgnosticDataRouteObject[] | undefined;
647
657
  // Cleanup function for history
648
658
  let unlistenHistory: (() => void) | null = null;
649
659
  // Externally-provided functions to call on all state changes
@@ -921,6 +931,11 @@ export function createRouter(init: RouterInit): Router {
921
931
  isMutationMethod(state.navigation.formMethod) &&
922
932
  location.state?._isRedirect !== true);
923
933
 
934
+ if (inFlightDataRoutes) {
935
+ dataRoutes = inFlightDataRoutes;
936
+ inFlightDataRoutes = undefined;
937
+ }
938
+
924
939
  updateState({
925
940
  ...newState, // matches, errors, fetchers go through as-is
926
941
  actionData,
@@ -1108,14 +1123,15 @@ export function createRouter(init: RouterInit): Router {
1108
1123
  saveScrollPosition(state.location, state.matches);
1109
1124
  pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
1110
1125
 
1126
+ let routesToUse = inFlightDataRoutes || dataRoutes;
1111
1127
  let loadingNavigation = opts && opts.overrideNavigation;
1112
- let matches = matchRoutes(dataRoutes, location, init.basename);
1128
+ let matches = matchRoutes(routesToUse, location, init.basename);
1113
1129
 
1114
1130
  // Short circuit with a 404 on the root error boundary if we match nothing
1115
1131
  if (!matches) {
1116
1132
  let error = getInternalRouterError(404, { pathname: location.pathname });
1117
1133
  let { matches: notFoundMatches, route } =
1118
- getShortCircuitMatches(dataRoutes);
1134
+ getShortCircuitMatches(routesToUse);
1119
1135
  // Cancel all pending deferred on 404s since we don't keep any routes
1120
1136
  cancelActiveDeferreds();
1121
1137
  completeNavigation(location, {
@@ -1352,6 +1368,7 @@ export function createRouter(init: RouterInit): Router {
1352
1368
  }
1353
1369
  : undefined;
1354
1370
 
1371
+ let routesToUse = inFlightDataRoutes || dataRoutes;
1355
1372
  let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(
1356
1373
  init.history,
1357
1374
  state,
@@ -1361,9 +1378,11 @@ export function createRouter(init: RouterInit): Router {
1361
1378
  isRevalidationRequired,
1362
1379
  cancelledDeferredRoutes,
1363
1380
  cancelledFetcherLoads,
1381
+ fetchLoadMatches,
1382
+ routesToUse,
1383
+ init.basename,
1364
1384
  pendingActionData,
1365
- pendingError,
1366
- fetchLoadMatches
1385
+ pendingError
1367
1386
  );
1368
1387
 
1369
1388
  // Cancel pending deferreds for no-longer-matched routes or routes we're
@@ -1506,7 +1525,8 @@ export function createRouter(init: RouterInit): Router {
1506
1525
 
1507
1526
  if (fetchControllers.has(key)) abortFetcher(key);
1508
1527
 
1509
- let matches = matchRoutes(dataRoutes, href, init.basename);
1528
+ let routesToUse = inFlightDataRoutes || dataRoutes;
1529
+ let matches = matchRoutes(routesToUse, href, init.basename);
1510
1530
  if (!matches) {
1511
1531
  setFetcherError(
1512
1532
  key,
@@ -1528,7 +1548,7 @@ export function createRouter(init: RouterInit): Router {
1528
1548
 
1529
1549
  // Store off the match so we can call it's shouldRevalidate on subsequent
1530
1550
  // revalidations
1531
- fetchLoadMatches.set(key, { routeId, path, match, matches });
1551
+ fetchLoadMatches.set(key, { routeId, path });
1532
1552
  handleFetcherLoader(key, routeId, path, match, matches, submission);
1533
1553
  }
1534
1554
 
@@ -1629,9 +1649,10 @@ export function createRouter(init: RouterInit): Router {
1629
1649
  nextLocation,
1630
1650
  abortController.signal
1631
1651
  );
1652
+ let routesToUse = inFlightDataRoutes || dataRoutes;
1632
1653
  let matches =
1633
1654
  state.navigation.state !== "idle"
1634
- ? matchRoutes(dataRoutes, state.navigation.location, init.basename)
1655
+ ? matchRoutes(routesToUse, state.navigation.location, init.basename)
1635
1656
  : state.matches;
1636
1657
 
1637
1658
  invariant(matches, "Didn't find any matches after fetcher action");
@@ -1656,9 +1677,11 @@ export function createRouter(init: RouterInit): Router {
1656
1677
  isRevalidationRequired,
1657
1678
  cancelledDeferredRoutes,
1658
1679
  cancelledFetcherLoads,
1680
+ fetchLoadMatches,
1681
+ routesToUse,
1682
+ init.basename,
1659
1683
  { [match.route.id]: actionResult.data },
1660
- undefined, // No need to send through errors since we short circuit above
1661
- fetchLoadMatches
1684
+ undefined // No need to send through errors since we short circuit above
1662
1685
  );
1663
1686
 
1664
1687
  // Put all revalidating fetchers into the loading state, except for the
@@ -1997,15 +2020,23 @@ export function createRouter(init: RouterInit): Router {
1997
2020
  ...matchesToLoad.map((match) =>
1998
2021
  callLoaderOrAction("loader", request, match, matches, router.basename)
1999
2022
  ),
2000
- ...fetchersToLoad.map((f) =>
2001
- callLoaderOrAction(
2002
- "loader",
2003
- createClientSideRequest(init.history, f.path, request.signal),
2004
- f.match,
2005
- f.matches,
2006
- router.basename
2007
- )
2008
- ),
2023
+ ...fetchersToLoad.map((f) => {
2024
+ if (f.matches && f.match) {
2025
+ return callLoaderOrAction(
2026
+ "loader",
2027
+ createClientSideRequest(init.history, f.path, request.signal),
2028
+ f.match,
2029
+ f.matches,
2030
+ router.basename
2031
+ );
2032
+ } else {
2033
+ let error: ErrorResult = {
2034
+ type: ResultType.error,
2035
+ error: getInternalRouterError(404, { pathname: f.path }),
2036
+ };
2037
+ return error;
2038
+ }
2039
+ }),
2009
2040
  ]);
2010
2041
  let loaderResults = results.slice(0, matchesToLoad.length);
2011
2042
  let fetcherResults = results.slice(matchesToLoad.length);
@@ -2266,6 +2297,10 @@ export function createRouter(init: RouterInit): Router {
2266
2297
  return null;
2267
2298
  }
2268
2299
 
2300
+ function _internalSetRoutes(newRoutes: AgnosticDataRouteObject[]) {
2301
+ inFlightDataRoutes = newRoutes;
2302
+ }
2303
+
2269
2304
  router = {
2270
2305
  get basename() {
2271
2306
  return init.basename;
@@ -2293,6 +2328,9 @@ export function createRouter(init: RouterInit): Router {
2293
2328
  deleteBlocker,
2294
2329
  _internalFetchControllers: fetchControllers,
2295
2330
  _internalActiveDeferreds: activeDeferreds,
2331
+ // TODO: Remove setRoutes, it's temporary to avoid dealing with
2332
+ // updating the tree while validating the update algorithm.
2333
+ _internalSetRoutes,
2296
2334
  };
2297
2335
 
2298
2336
  return router;
@@ -2891,9 +2929,11 @@ function getMatchesToLoad(
2891
2929
  isRevalidationRequired: boolean,
2892
2930
  cancelledDeferredRoutes: string[],
2893
2931
  cancelledFetcherLoads: string[],
2932
+ fetchLoadMatches: Map<string, FetchLoadMatch>,
2933
+ routesToUse: AgnosticDataRouteObject[],
2934
+ basename: string | undefined,
2894
2935
  pendingActionData?: RouteData,
2895
- pendingError?: RouteData,
2896
- fetchLoadMatches?: Map<string, FetchLoadMatch>
2936
+ pendingError?: RouteData
2897
2937
  ): [AgnosticDataRouteMatch[], RevalidatingFetcher[]] {
2898
2938
  let actionResult = pendingError
2899
2939
  ? Object.values(pendingError)[0]
@@ -2951,34 +2991,55 @@ function getMatchesToLoad(
2951
2991
 
2952
2992
  // Pick fetcher.loads that need to be revalidated
2953
2993
  let revalidatingFetchers: RevalidatingFetcher[] = [];
2954
- fetchLoadMatches &&
2955
- fetchLoadMatches.forEach((f, key) => {
2956
- if (!matches.some((m) => m.route.id === f.routeId)) {
2957
- // This fetcher is not going to be present in the subsequent render so
2958
- // there's no need to revalidate it
2959
- return;
2960
- } else if (cancelledFetcherLoads.includes(key)) {
2961
- // This fetcher was cancelled from a prior action submission - force reload
2962
- revalidatingFetchers.push({ key, ...f });
2963
- } else {
2964
- // Revalidating fetchers are decoupled from the route matches since they
2965
- // hit a static href, so they _always_ check shouldRevalidate and the
2966
- // default is strictly if a revalidation is explicitly required (action
2967
- // submissions, useRevalidator, X-Remix-Revalidate).
2968
- let shouldRevalidate = shouldRevalidateLoader(f.match, {
2969
- currentUrl,
2970
- currentParams: state.matches[state.matches.length - 1].params,
2971
- nextUrl,
2972
- nextParams: matches[matches.length - 1].params,
2973
- ...submission,
2974
- actionResult,
2975
- defaultShouldRevalidate,
2976
- });
2977
- if (shouldRevalidate) {
2978
- revalidatingFetchers.push({ key, ...f });
2979
- }
2980
- }
2994
+ fetchLoadMatches.forEach((f, key) => {
2995
+ // Don't revalidate if fetcher won't be present in the subsequent render
2996
+ if (!matches.some((m) => m.route.id === f.routeId)) {
2997
+ return;
2998
+ }
2999
+
3000
+ let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
3001
+
3002
+ // If the fetcher path no longer matches, push it in with null matches so
3003
+ // we can trigger a 404 in callLoadersAndMaybeResolveData
3004
+ if (!fetcherMatches) {
3005
+ revalidatingFetchers.push({ key, ...f, matches: null, match: null });
3006
+ return;
3007
+ }
3008
+
3009
+ let fetcherMatch = getTargetMatch(fetcherMatches, f.path);
3010
+
3011
+ if (cancelledFetcherLoads.includes(key)) {
3012
+ revalidatingFetchers.push({
3013
+ key,
3014
+ matches: fetcherMatches,
3015
+ match: fetcherMatch,
3016
+ ...f,
3017
+ });
3018
+ return;
3019
+ }
3020
+
3021
+ // Revalidating fetchers are decoupled from the route matches since they
3022
+ // hit a static href, so they _always_ check shouldRevalidate and the
3023
+ // default is strictly if a revalidation is explicitly required (action
3024
+ // submissions, useRevalidator, X-Remix-Revalidate).
3025
+ let shouldRevalidate = shouldRevalidateLoader(fetcherMatch, {
3026
+ currentUrl,
3027
+ currentParams: state.matches[state.matches.length - 1].params,
3028
+ nextUrl,
3029
+ nextParams: matches[matches.length - 1].params,
3030
+ ...submission,
3031
+ actionResult,
3032
+ defaultShouldRevalidate,
2981
3033
  });
3034
+ if (shouldRevalidate) {
3035
+ revalidatingFetchers.push({
3036
+ key,
3037
+ matches: fetcherMatches,
3038
+ match: fetcherMatch,
3039
+ ...f,
3040
+ });
3041
+ }
3042
+ });
2982
3043
 
2983
3044
  return [navigationMatches, revalidatingFetchers];
2984
3045
  }
@@ -3183,7 +3244,12 @@ async function callLoaderOrAction(
3183
3244
  }
3184
3245
 
3185
3246
  if (result instanceof DeferredData) {
3186
- return { type: ResultType.deferred, deferredData: result };
3247
+ return {
3248
+ type: ResultType.deferred,
3249
+ deferredData: result,
3250
+ statusCode: result.init?.status,
3251
+ headers: result.init?.headers && new Headers(result.init.headers),
3252
+ };
3187
3253
  }
3188
3254
 
3189
3255
  return { type: ResultType.data, data: result };
@@ -3356,7 +3422,7 @@ function processLoaderData(
3356
3422
 
3357
3423
  // Process fetcher non-redirect errors
3358
3424
  if (isErrorResult(result)) {
3359
- let boundaryMatch = findNearestBoundary(state.matches, match.route.id);
3425
+ let boundaryMatch = findNearestBoundary(state.matches, match?.route.id);
3360
3426
  if (!(errors && errors[boundaryMatch.route.id])) {
3361
3427
  errors = {
3362
3428
  ...errors,
@@ -3406,7 +3472,9 @@ function mergeLoaderData(
3406
3472
  // incoming object with an undefined value, which is how we unset a prior
3407
3473
  // loaderData if we encounter a loader error
3408
3474
  }
3409
- } else if (loaderData[id] !== undefined) {
3475
+ } else if (loaderData[id] !== undefined && match.route.loader) {
3476
+ // Preserve existing keys not included in newLoaderData and where a loader
3477
+ // wasn't removed by HMR
3410
3478
  mergedLoaderData[id] = loaderData[id];
3411
3479
  }
3412
3480
 
@@ -3580,7 +3648,7 @@ function isMutationMethod(method?: string): method is MutationFormMethod {
3580
3648
 
3581
3649
  async function resolveDeferredResults(
3582
3650
  currentMatches: AgnosticDataRouteMatch[],
3583
- matchesToLoad: AgnosticDataRouteMatch[],
3651
+ matchesToLoad: (AgnosticDataRouteMatch | null)[],
3584
3652
  results: DataResult[],
3585
3653
  signal: AbortSignal,
3586
3654
  isFetcher: boolean,
@@ -3589,8 +3657,15 @@ async function resolveDeferredResults(
3589
3657
  for (let index = 0; index < results.length; index++) {
3590
3658
  let result = results[index];
3591
3659
  let match = matchesToLoad[index];
3660
+ // If we don't have a match, then we can have a deferred result to do
3661
+ // anything with. This is for revalidating fetchers where the route was
3662
+ // removed during HMR
3663
+ if (!match) {
3664
+ continue;
3665
+ }
3666
+
3592
3667
  let currentMatch = currentMatches.find(
3593
- (m) => m.route.id === match.route.id
3668
+ (m) => m.route.id === match!.route.id
3594
3669
  );
3595
3670
  let isRevalidatingLoader =
3596
3671
  currentMatch != null &&