@remix-run/router 1.10.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remix-run/router",
3
- "version": "1.10.0",
3
+ "version": "1.11.0-pre.0",
4
4
  "description": "Nested/Data-driven/Framework-agnostic Routing",
5
5
  "keywords": [
6
6
  "remix",
package/router.ts CHANGED
@@ -193,7 +193,7 @@ export interface Router {
193
193
  * Get/create a fetcher for the given key
194
194
  * @param key
195
195
  */
196
- getFetcher<TData = any>(key?: string): Fetcher<TData>;
196
+ getFetcher<TData = any>(key: string): Fetcher<TData>;
197
197
 
198
198
  /**
199
199
  * @internal
@@ -202,7 +202,7 @@ export interface Router {
202
202
  * Delete the fetcher for a given key
203
203
  * @param key
204
204
  */
205
- deleteFetcher(key?: string): void;
205
+ deleteFetcher(key: string): void;
206
206
 
207
207
  /**
208
208
  * @internal
@@ -343,6 +343,7 @@ export type HydrationState = Partial<
343
343
  * Future flags to toggle new feature behavior
344
344
  */
345
345
  export interface FutureConfig {
346
+ v7_fetcherPersist: boolean;
346
347
  v7_normalizeFormMethod: boolean;
347
348
  v7_prependBasename: boolean;
348
349
  }
@@ -763,6 +764,7 @@ export function createRouter(init: RouterInit): Router {
763
764
  let basename = init.basename || "/";
764
765
  // Config driven behavior flags
765
766
  let future: FutureConfig = {
767
+ v7_fetcherPersist: false,
766
768
  v7_normalizeFormMethod: false,
767
769
  v7_prependBasename: false,
768
770
  ...init.future,
@@ -885,6 +887,10 @@ export function createRouter(init: RouterInit): Router {
885
887
  // Most recent href/match for fetcher.load calls for fetchers
886
888
  let fetchLoadMatches = new Map<string, FetchLoadMatch>();
887
889
 
890
+ // Fetchers that have requested a delete when using v7_fetcherPersist,
891
+ // they'll be officially removed after they return to idle
892
+ let deletedFetchers = new Set<string>();
893
+
888
894
  // Store DeferredData instances for active route matches. When a
889
895
  // route loader returns defer() we stick one in here. Then, when a nested
890
896
  // promise resolves we update loaderData. If a new navigation starts we
@@ -1017,6 +1023,24 @@ export function createRouter(init: RouterInit): Router {
1017
1023
  subscribers.forEach((subscriber) =>
1018
1024
  subscriber(state, { unstable_viewTransitionOpts: viewTransitionOpts })
1019
1025
  );
1026
+
1027
+ // Remove idle fetchers from state since we only care about in-flight fetchers.
1028
+ if (future.v7_fetcherPersist) {
1029
+ state.fetchers.forEach((fetcher, key) => {
1030
+ if (fetcher.state === "idle") {
1031
+ if (deletedFetchers.has(key)) {
1032
+ // If the fetcher has unmounted and called router.deleteFetcher(),
1033
+ // we can totally delete the fetcher
1034
+ deleteFetcher(key);
1035
+ } else {
1036
+ // Otherwise, it must still be mounted in the UI so we just remove
1037
+ // it from state now that we've handed off the data to the React
1038
+ // layer. Things such as fetchLoadMatches remain for revalidation.
1039
+ state.fetchers.delete(key);
1040
+ }
1041
+ }
1042
+ });
1043
+ }
1020
1044
  }
1021
1045
 
1022
1046
  // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
@@ -2009,7 +2033,7 @@ export function createRouter(init: RouterInit): Router {
2009
2033
  state.fetchers.set(key, doneFetcher);
2010
2034
  }
2011
2035
 
2012
- let didAbortFetchLoads = abortStaleFetchLoads(loadId);
2036
+ abortStaleFetchLoads(loadId);
2013
2037
 
2014
2038
  // If we are currently in a navigation loading state and this fetcher is
2015
2039
  // more recent than the navigation, we want the newer data so abort the
@@ -2039,9 +2063,7 @@ export function createRouter(init: RouterInit): Router {
2039
2063
  matches,
2040
2064
  errors
2041
2065
  ),
2042
- ...(didAbortFetchLoads || revalidatingFetchers.length > 0
2043
- ? { fetchers: new Map(state.fetchers) }
2044
- : {}),
2066
+ fetchers: new Map(state.fetchers),
2045
2067
  });
2046
2068
  isRevalidationRequired = false;
2047
2069
  }
@@ -2376,9 +2398,19 @@ export function createRouter(init: RouterInit): Router {
2376
2398
  fetchLoadMatches.delete(key);
2377
2399
  fetchReloadIds.delete(key);
2378
2400
  fetchRedirectIds.delete(key);
2401
+ deletedFetchers.delete(key);
2379
2402
  state.fetchers.delete(key);
2380
2403
  }
2381
2404
 
2405
+ function deleteFetcherAndUpdateState(key: string): void {
2406
+ if (future.v7_fetcherPersist) {
2407
+ deletedFetchers.add(key);
2408
+ } else {
2409
+ deleteFetcher(key);
2410
+ }
2411
+ updateState({ fetchers: new Map(state.fetchers) });
2412
+ }
2413
+
2382
2414
  function abortFetcher(key: string) {
2383
2415
  let controller = fetchControllers.get(key);
2384
2416
  invariant(controller, `Expected fetch controller: ${key}`);
@@ -2613,7 +2645,7 @@ export function createRouter(init: RouterInit): Router {
2613
2645
  createHref: (to: To) => init.history.createHref(to),
2614
2646
  encodeLocation: (to: To) => init.history.encodeLocation(to),
2615
2647
  getFetcher,
2616
- deleteFetcher,
2648
+ deleteFetcher: deleteFetcherAndUpdateState,
2617
2649
  dispose,
2618
2650
  getBlocker,
2619
2651
  deleteBlocker,
package/utils.ts CHANGED
@@ -903,7 +903,7 @@ export function matchPath<
903
903
  pattern = { path: pattern, caseSensitive: false, end: true };
904
904
  }
905
905
 
906
- let [matcher, paramNames] = compilePath(
906
+ let [matcher, compiledParams] = compilePath(
907
907
  pattern.path,
908
908
  pattern.caseSensitive,
909
909
  pattern.end
@@ -915,8 +915,8 @@ export function matchPath<
915
915
  let matchedPathname = match[0];
916
916
  let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
917
917
  let captureGroups = match.slice(1);
918
- let params: Params = paramNames.reduce<Mutable<Params>>(
919
- (memo, paramName, index) => {
918
+ let params: Params = compiledParams.reduce<Mutable<Params>>(
919
+ (memo, { paramName, isOptional }, index) => {
920
920
  // We need to compute the pathnameBase here using the raw splat value
921
921
  // instead of using params["*"] later because it will be decoded then
922
922
  if (paramName === "*") {
@@ -926,10 +926,12 @@ export function matchPath<
926
926
  .replace(/(.)\/+$/, "$1");
927
927
  }
928
928
 
929
- memo[paramName] = safelyDecodeURIComponent(
930
- captureGroups[index] || "",
931
- paramName
932
- );
929
+ const value = captureGroups[index];
930
+ if (isOptional && !value) {
931
+ memo[paramName] = undefined;
932
+ } else {
933
+ memo[paramName] = safelyDecodeURIComponent(value || "", paramName);
934
+ }
933
935
  return memo;
934
936
  },
935
937
  {}
@@ -943,11 +945,13 @@ export function matchPath<
943
945
  };
944
946
  }
945
947
 
948
+ type CompiledPathParam = { paramName: string; isOptional?: boolean };
949
+
946
950
  function compilePath(
947
951
  path: string,
948
952
  caseSensitive = false,
949
953
  end = true
950
- ): [RegExp, string[]] {
954
+ ): [RegExp, CompiledPathParam[]] {
951
955
  warning(
952
956
  path === "*" || !path.endsWith("*") || path.endsWith("/*"),
953
957
  `Route path "${path}" will be treated as if it were ` +
@@ -956,20 +960,20 @@ function compilePath(
956
960
  `please change the route path to "${path.replace(/\*$/, "/*")}".`
957
961
  );
958
962
 
959
- let paramNames: string[] = [];
963
+ let params: CompiledPathParam[] = [];
960
964
  let regexpSource =
961
965
  "^" +
962
966
  path
963
967
  .replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
964
968
  .replace(/^\/*/, "/") // Make sure it has a leading /
965
- .replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
966
- .replace(/\/:(\w+)/g, (_: string, paramName: string) => {
967
- paramNames.push(paramName);
968
- return "/([^\\/]+)";
969
+ .replace(/[\\.*+^${}|()[\]]/g, "\\$&") // Escape special regex chars
970
+ .replace(/\/:(\w+)(\?)?/g, (_: string, paramName: string, isOptional) => {
971
+ params.push({ paramName, isOptional: isOptional != null });
972
+ return isOptional ? "/?([^\\/]+)?" : "/([^\\/]+)";
969
973
  });
970
974
 
971
975
  if (path.endsWith("*")) {
972
- paramNames.push("*");
976
+ params.push({ paramName: "*" });
973
977
  regexpSource +=
974
978
  path === "*" || path === "/*"
975
979
  ? "(.*)$" // Already matched the initial /, just match the rest
@@ -992,7 +996,7 @@ function compilePath(
992
996
 
993
997
  let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
994
998
 
995
- return [matcher, paramNames];
999
+ return [matcher, params];
996
1000
  }
997
1001
 
998
1002
  function safelyDecodeURI(value: string) {