@remix-run/router 1.10.0-pre.0 → 1.11.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.10.0-pre.0
2
+ * @remix-run/router v1.11.0-pre.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1019,20 +1019,29 @@
1019
1019
  end: true
1020
1020
  };
1021
1021
  }
1022
- let [matcher, paramNames] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
1022
+ let [matcher, compiledParams] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
1023
1023
  let match = pathname.match(matcher);
1024
1024
  if (!match) return null;
1025
1025
  let matchedPathname = match[0];
1026
1026
  let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
1027
1027
  let captureGroups = match.slice(1);
1028
- let params = paramNames.reduce((memo, paramName, index) => {
1028
+ let params = compiledParams.reduce((memo, _ref, index) => {
1029
+ let {
1030
+ paramName,
1031
+ isOptional
1032
+ } = _ref;
1029
1033
  // We need to compute the pathnameBase here using the raw splat value
1030
1034
  // instead of using params["*"] later because it will be decoded then
1031
1035
  if (paramName === "*") {
1032
1036
  let splatValue = captureGroups[index] || "";
1033
1037
  pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
1034
1038
  }
1035
- memo[paramName] = safelyDecodeURIComponent(captureGroups[index] || "", paramName);
1039
+ const value = captureGroups[index];
1040
+ if (isOptional && !value) {
1041
+ memo[paramName] = undefined;
1042
+ } else {
1043
+ memo[paramName] = safelyDecodeURIComponent(value || "", paramName);
1044
+ }
1036
1045
  return memo;
1037
1046
  }, {});
1038
1047
  return {
@@ -1050,16 +1059,21 @@
1050
1059
  end = true;
1051
1060
  }
1052
1061
  warning(path === "*" || !path.endsWith("*") || path.endsWith("/*"), "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
1053
- let paramNames = [];
1062
+ let params = [];
1054
1063
  let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
1055
1064
  .replace(/^\/*/, "/") // Make sure it has a leading /
1056
- .replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
1057
- .replace(/\/:(\w+)/g, (_, paramName) => {
1058
- paramNames.push(paramName);
1059
- return "/([^\\/]+)";
1065
+ .replace(/[\\.*+^${}|()[\]]/g, "\\$&") // Escape special regex chars
1066
+ .replace(/\/:(\w+)(\?)?/g, (_, paramName, isOptional) => {
1067
+ params.push({
1068
+ paramName,
1069
+ isOptional: isOptional != null
1070
+ });
1071
+ return isOptional ? "/?([^\\/]+)?" : "/([^\\/]+)";
1060
1072
  });
1061
1073
  if (path.endsWith("*")) {
1062
- paramNames.push("*");
1074
+ params.push({
1075
+ paramName: "*"
1076
+ });
1063
1077
  regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
1064
1078
  : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
1065
1079
  } else if (end) {
@@ -1076,7 +1090,7 @@
1076
1090
  regexpSource += "(?:(?=\\/|$))";
1077
1091
  } else ;
1078
1092
  let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
1079
- return [matcher, paramNames];
1093
+ return [matcher, params];
1080
1094
  }
1081
1095
  function safelyDecodeURI(value) {
1082
1096
  try {
@@ -1304,8 +1318,8 @@
1304
1318
  let onAbort = () => reject(new AbortedDeferredError("Deferred data aborted"));
1305
1319
  this.unlistenAbortSignal = () => this.controller.signal.removeEventListener("abort", onAbort);
1306
1320
  this.controller.signal.addEventListener("abort", onAbort);
1307
- this.data = Object.entries(data).reduce((acc, _ref) => {
1308
- let [key, value] = _ref;
1321
+ this.data = Object.entries(data).reduce((acc, _ref2) => {
1322
+ let [key, value] = _ref2;
1309
1323
  return Object.assign(acc, {
1310
1324
  [key]: this.trackPromise(key, value)
1311
1325
  });
@@ -1405,8 +1419,8 @@
1405
1419
  }
1406
1420
  get unwrappedData() {
1407
1421
  invariant(this.data !== null && this.done, "Can only unwrap data on initialized and settled deferreds");
1408
- return Object.entries(this.data).reduce((acc, _ref2) => {
1409
- let [key, value] = _ref2;
1422
+ return Object.entries(this.data).reduce((acc, _ref3) => {
1423
+ let [key, value] = _ref3;
1410
1424
  return Object.assign(acc, {
1411
1425
  [key]: unwrapTrackedPromise(value)
1412
1426
  });
@@ -1651,6 +1665,7 @@
1651
1665
  let basename = init.basename || "/";
1652
1666
  // Config driven behavior flags
1653
1667
  let future = _extends({
1668
+ v7_fetcherPersist: false,
1654
1669
  v7_normalizeFormMethod: false,
1655
1670
  v7_prependBasename: false
1656
1671
  }, init.future);
@@ -1770,6 +1785,10 @@
1770
1785
  // Most recent href/match for fetcher.load calls for fetchers
1771
1786
  let fetchLoadMatches = new Map();
1772
1787
 
1788
+ // Fetchers that have requested a delete when using v7_fetcherPersist,
1789
+ // they'll be officially removed after they return to idle
1790
+ let deletedFetchers = new Set();
1791
+
1773
1792
  // Store DeferredData instances for active route matches. When a
1774
1793
  // route loader returns defer() we stick one in here. Then, when a nested
1775
1794
  // promise resolves we update loaderData. If a new navigation starts we
@@ -1885,6 +1904,24 @@
1885
1904
  subscribers.forEach(subscriber => subscriber(state, {
1886
1905
  unstable_viewTransitionOpts: viewTransitionOpts
1887
1906
  }));
1907
+
1908
+ // Remove idle fetchers from state since we only care about in-flight fetchers.
1909
+ if (future.v7_fetcherPersist) {
1910
+ state.fetchers.forEach((fetcher, key) => {
1911
+ if (fetcher.state === "idle") {
1912
+ if (deletedFetchers.has(key)) {
1913
+ // If the fetcher has unmounted and called router.deleteFetcher(),
1914
+ // we can totally delete the fetcher
1915
+ deleteFetcher(key);
1916
+ } else {
1917
+ // Otherwise, it must still be mounted in the UI so we just remove
1918
+ // it from state now that we've handed off the data to the React
1919
+ // layer. Things such as fetchLoadMatches remain for revalidation.
1920
+ state.fetchers.delete(key);
1921
+ }
1922
+ }
1923
+ });
1924
+ }
1888
1925
  }
1889
1926
 
1890
1927
  // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
@@ -2626,7 +2663,7 @@
2626
2663
  let doneFetcher = getDoneFetcher(actionResult.data);
2627
2664
  state.fetchers.set(key, doneFetcher);
2628
2665
  }
2629
- let didAbortFetchLoads = abortStaleFetchLoads(loadId);
2666
+ abortStaleFetchLoads(loadId);
2630
2667
 
2631
2668
  // If we are currently in a navigation loading state and this fetcher is
2632
2669
  // more recent than the navigation, we want the newer data so abort the
@@ -2644,12 +2681,11 @@
2644
2681
  // otherwise just update with the fetcher data, preserving any existing
2645
2682
  // loaderData for loaders that did not need to reload. We have to
2646
2683
  // manually merge here since we aren't going through completeNavigation
2647
- updateState(_extends({
2684
+ updateState({
2648
2685
  errors,
2649
- loaderData: mergeLoaderData(state.loaderData, loaderData, matches, errors)
2650
- }, didAbortFetchLoads || revalidatingFetchers.length > 0 ? {
2686
+ loaderData: mergeLoaderData(state.loaderData, loaderData, matches, errors),
2651
2687
  fetchers: new Map(state.fetchers)
2652
- } : {}));
2688
+ });
2653
2689
  isRevalidationRequired = false;
2654
2690
  }
2655
2691
  }
@@ -2890,8 +2926,19 @@
2890
2926
  fetchLoadMatches.delete(key);
2891
2927
  fetchReloadIds.delete(key);
2892
2928
  fetchRedirectIds.delete(key);
2929
+ deletedFetchers.delete(key);
2893
2930
  state.fetchers.delete(key);
2894
2931
  }
2932
+ function deleteFetcherAndUpdateState(key) {
2933
+ if (future.v7_fetcherPersist) {
2934
+ deletedFetchers.add(key);
2935
+ } else {
2936
+ deleteFetcher(key);
2937
+ }
2938
+ updateState({
2939
+ fetchers: new Map(state.fetchers)
2940
+ });
2941
+ }
2895
2942
  function abortFetcher(key) {
2896
2943
  let controller = fetchControllers.get(key);
2897
2944
  invariant(controller, "Expected fetch controller: " + key);
@@ -3086,7 +3133,7 @@
3086
3133
  createHref: to => init.history.createHref(to),
3087
3134
  encodeLocation: to => init.history.encodeLocation(to),
3088
3135
  getFetcher,
3089
- deleteFetcher,
3136
+ deleteFetcher: deleteFetcherAndUpdateState,
3090
3137
  dispose,
3091
3138
  getBlocker,
3092
3139
  deleteBlocker,