@remix-run/router 1.0.5 → 1.1.0-pre.1

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/dist/router.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v1.0.5
2
+ * @remix-run/router v1.1.0-pre.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -573,9 +573,9 @@ function flattenRoutes(routes, branches, parentsMeta, parentPath) {
573
573
  parentPath = "";
574
574
  }
575
575
 
576
- routes.forEach((route, index) => {
576
+ let flattenRoute = (route, index, relativePath) => {
577
577
  let meta = {
578
- relativePath: route.path || "",
578
+ relativePath: relativePath === undefined ? route.path || "" : relativePath,
579
579
  caseSensitive: route.caseSensitive === true,
580
580
  childrenIndex: index,
581
581
  route
@@ -609,9 +609,71 @@ function flattenRoutes(routes, branches, parentsMeta, parentPath) {
609
609
  score: computeScore(path, route.index),
610
610
  routesMeta
611
611
  });
612
+ };
613
+
614
+ routes.forEach((route, index) => {
615
+ var _route$path;
616
+
617
+ // coarse-grain check for optional params
618
+ if (route.path === "" || !((_route$path = route.path) != null && _route$path.includes("?"))) {
619
+ flattenRoute(route, index);
620
+ } else {
621
+ for (let exploded of explodeOptionalSegments(route.path)) {
622
+ flattenRoute(route, index, exploded);
623
+ }
624
+ }
612
625
  });
613
626
  return branches;
614
627
  }
628
+ /**
629
+ * Computes all combinations of optional path segments for a given path,
630
+ * excluding combinations that are ambiguous and of lower priority.
631
+ *
632
+ * For example, `/one/:two?/three/:four?/:five?` explodes to:
633
+ * - `/one/three`
634
+ * - `/one/:two/three`
635
+ * - `/one/three/:four`
636
+ * - `/one/three/:five`
637
+ * - `/one/:two/three/:four`
638
+ * - `/one/:two/three/:five`
639
+ * - `/one/three/:four/:five`
640
+ * - `/one/:two/three/:four/:five`
641
+ */
642
+
643
+
644
+ function explodeOptionalSegments(path) {
645
+ let segments = path.split("/");
646
+ if (segments.length === 0) return [];
647
+ let [first, ...rest] = segments; // Optional path segments are denoted by a trailing `?`
648
+
649
+ let isOptional = first.endsWith("?"); // Compute the corresponding required segment: `foo?` -> `foo`
650
+
651
+ let required = first.replace(/\?$/, "");
652
+
653
+ if (rest.length === 0) {
654
+ // Intepret empty string as omitting an optional segment
655
+ // `["one", "", "three"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three`
656
+ return isOptional ? [required, ""] : [required];
657
+ }
658
+
659
+ let restExploded = explodeOptionalSegments(rest.join("/"));
660
+ let result = []; // All child paths with the prefix. Do this for all children before the
661
+ // optional version for all children so we get consistent ordering where the
662
+ // parent optional aspect is preferred as required. Otherwise, we can get
663
+ // child sections interspersed where deeper optional segments are higher than
664
+ // parent optional segments, where for example, /:two would explodes _earlier_
665
+ // then /:one. By always including the parent as required _for all children_
666
+ // first, we avoid this issue
667
+
668
+ result.push(...restExploded.map(subpath => subpath === "" ? required : [required, subpath].join("/"))); // Then if this is an optional value, add all child versions without
669
+
670
+ if (isOptional) {
671
+ result.push(...restExploded);
672
+ } // for absolute paths, ensure `/` instead of empty segment
673
+
674
+
675
+ return result.map(exploded => path.startsWith("/") && exploded === "" ? "/" : exploded);
676
+ }
615
677
 
616
678
  function rankRouteBranches(branches) {
617
679
  branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
@@ -695,14 +757,24 @@ function matchRouteBranch(branch, pathname) {
695
757
  */
696
758
 
697
759
 
698
- function generatePath(path, params) {
760
+ function generatePath(originalPath, params) {
699
761
  if (params === void 0) {
700
762
  params = {};
701
763
  }
702
764
 
703
- return path.replace(/:(\w+)/g, (_, key) => {
765
+ let path = originalPath;
766
+
767
+ if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
768
+ warning(false, "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(/\*$/, "/*") + "\"."));
769
+ path = path.replace(/\*$/, "/*");
770
+ }
771
+
772
+ return path.replace(/^:(\w+)/g, (_, key) => {
704
773
  invariant(params[key] != null, "Missing \":" + key + "\" param");
705
774
  return params[key];
775
+ }).replace(/\/:(\w+)/g, (_, key) => {
776
+ invariant(params[key] != null, "Missing \":" + key + "\" param");
777
+ return "/" + params[key];
706
778
  }).replace(/(\/?)\*/, (_, prefix, __, str) => {
707
779
  const star = "*";
708
780
 
@@ -771,9 +843,9 @@ function compilePath(path, caseSensitive, end) {
771
843
  let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
772
844
  .replace(/^\/*/, "/") // Make sure it has a leading /
773
845
  .replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
774
- .replace(/:(\w+)/g, (_, paramName) => {
846
+ .replace(/\/:(\w+)/g, (_, paramName) => {
775
847
  paramNames.push(paramName);
776
- return "([^\\/]+)";
848
+ return "/([^\\/]+)";
777
849
  });
778
850
 
779
851
  if (path.endsWith("*")) {
@@ -1247,9 +1319,9 @@ function isRouteErrorResponse(e) {
1247
1319
  return e instanceof ErrorResponse;
1248
1320
  }
1249
1321
 
1250
- const validActionMethodsArr = ["post", "put", "patch", "delete"];
1251
- const validActionMethods = new Set(validActionMethodsArr);
1252
- const validRequestMethodsArr = ["get", ...validActionMethodsArr];
1322
+ const validMutationMethodsArr = ["post", "put", "patch", "delete"];
1323
+ const validMutationMethods = new Set(validMutationMethodsArr);
1324
+ const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
1253
1325
  const validRequestMethods = new Set(validRequestMethodsArr);
1254
1326
  const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
1255
1327
  const redirectPreserveMethodStatusCodes = new Set([307, 308]);
@@ -1486,7 +1558,7 @@ function createRouter(init) {
1486
1558
  // without having to touch history
1487
1559
 
1488
1560
  location = _extends({}, location, init.history.encodeLocation(location));
1489
- let historyAction = (opts && opts.replace) === true || submission != null ? Action.Replace : Action.Push;
1561
+ let historyAction = (opts && opts.replace) === true || submission != null && isMutationMethod(submission.formMethod) ? Action.Replace : Action.Push;
1490
1562
  let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
1491
1563
  return await startNavigation(historyAction, location, {
1492
1564
  submission,
@@ -1590,7 +1662,7 @@ function createRouter(init) {
1590
1662
  pendingError = {
1591
1663
  [findNearestBoundary(matches).route.id]: opts.pendingError
1592
1664
  };
1593
- } else if (opts && opts.submission) {
1665
+ } else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
1594
1666
  // Call action if we received an action submission
1595
1667
  let actionOutput = await handleAction(request, location, opts.submission, matches, {
1596
1668
  replace: opts.replace
@@ -1717,14 +1789,15 @@ function createRouter(init) {
1717
1789
  let loadingNavigation = overrideNavigation;
1718
1790
 
1719
1791
  if (!loadingNavigation) {
1720
- let navigation = {
1792
+ let navigation = _extends({
1721
1793
  state: "loading",
1722
1794
  location,
1723
1795
  formMethod: undefined,
1724
1796
  formAction: undefined,
1725
1797
  formEncType: undefined,
1726
1798
  formData: undefined
1727
- };
1799
+ }, submission);
1800
+
1728
1801
  loadingNavigation = navigation;
1729
1802
  }
1730
1803
 
@@ -1859,7 +1932,7 @@ function createRouter(init) {
1859
1932
  } = normalizeNavigateOptions(href, opts, true);
1860
1933
  let match = getTargetMatch(matches, path);
1861
1934
 
1862
- if (submission) {
1935
+ if (submission && isMutationMethod(submission.formMethod)) {
1863
1936
  handleFetcherAction(key, routeId, path, match, matches, submission);
1864
1937
  return;
1865
1938
  } // Store off the match so we can call it's shouldRevalidate on subsequent
@@ -1867,7 +1940,7 @@ function createRouter(init) {
1867
1940
 
1868
1941
 
1869
1942
  fetchLoadMatches.set(key, [path, match, matches]);
1870
- handleFetcherLoader(key, routeId, path, match, matches);
1943
+ handleFetcherLoader(key, routeId, path, match, matches, submission);
1871
1944
  } // Call the action for the matched fetcher.submit(), and then handle redirects,
1872
1945
  // errors, and revalidation
1873
1946
 
@@ -2048,17 +2121,19 @@ function createRouter(init) {
2048
2121
  } // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
2049
2122
 
2050
2123
 
2051
- async function handleFetcherLoader(key, routeId, path, match, matches) {
2124
+ async function handleFetcherLoader(key, routeId, path, match, matches, submission) {
2052
2125
  let existingFetcher = state.fetchers.get(key); // Put this fetcher into it's loading state
2053
2126
 
2054
- let loadingFetcher = {
2127
+ let loadingFetcher = _extends({
2055
2128
  state: "loading",
2056
2129
  formMethod: undefined,
2057
2130
  formAction: undefined,
2058
2131
  formEncType: undefined,
2059
- formData: undefined,
2132
+ formData: undefined
2133
+ }, submission, {
2060
2134
  data: existingFetcher && existingFetcher.data
2061
- };
2135
+ });
2136
+
2062
2137
  state.fetchers.set(key, loadingFetcher);
2063
2138
  updateState({
2064
2139
  fetchers: new Map(state.fetchers)
@@ -2178,10 +2253,10 @@ function createRouter(init) {
2178
2253
  formEncType,
2179
2254
  formData
2180
2255
  } = state.navigation; // If this was a 307/308 submission we want to preserve the HTTP method and
2181
- // re-submit the POST/PUT/PATCH/DELETE as a submission navigation to the
2256
+ // re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
2182
2257
  // redirected location
2183
2258
 
2184
- if (redirectPreserveMethodStatusCodes.has(redirect.status) && formMethod && isSubmissionMethod(formMethod) && formEncType && formData) {
2259
+ if (redirectPreserveMethodStatusCodes.has(redirect.status) && formMethod && isMutationMethod(formMethod) && formEncType && formData) {
2185
2260
  await startNavigation(redirectHistoryAction, redirectLocation, {
2186
2261
  submission: {
2187
2262
  formMethod,
@@ -2592,7 +2667,7 @@ function unstable_createStaticHandler(routes, opts) {
2592
2667
  invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
2593
2668
 
2594
2669
  try {
2595
- if (isSubmissionMethod(request.method.toLowerCase())) {
2670
+ if (isMutationMethod(request.method.toLowerCase())) {
2596
2671
  let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
2597
2672
  return result;
2598
2673
  }
@@ -2709,6 +2784,8 @@ function unstable_createStaticHandler(routes, opts) {
2709
2784
 
2710
2785
 
2711
2786
  let loaderRequest = new Request(request.url, {
2787
+ headers: request.headers,
2788
+ redirect: request.redirect,
2712
2789
  signal: request.signal
2713
2790
  });
2714
2791
  let context = await loadRouteData(loaderRequest, matches, requestContext);
@@ -2824,16 +2901,22 @@ function normalizeNavigateOptions(to, opts, isFetcher) {
2824
2901
  } // Create a Submission on non-GET navigations
2825
2902
 
2826
2903
 
2827
- if (opts.formMethod && isSubmissionMethod(opts.formMethod)) {
2828
- return {
2829
- path,
2830
- submission: {
2831
- formMethod: opts.formMethod,
2832
- formAction: stripHashFromPath(path),
2833
- formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
2834
- formData: opts.formData
2835
- }
2904
+ let submission;
2905
+
2906
+ if (opts.formData) {
2907
+ submission = {
2908
+ formMethod: opts.formMethod || "get",
2909
+ formAction: stripHashFromPath(path),
2910
+ formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
2911
+ formData: opts.formData
2836
2912
  };
2913
+
2914
+ if (isMutationMethod(submission.formMethod)) {
2915
+ return {
2916
+ path,
2917
+ submission
2918
+ };
2919
+ }
2837
2920
  } // Flatten submission onto URLSearchParams for GET submissions
2838
2921
 
2839
2922
 
@@ -2857,7 +2940,8 @@ function normalizeNavigateOptions(to, opts, isFetcher) {
2857
2940
  }
2858
2941
 
2859
2942
  return {
2860
- path: createPath(parsedPath)
2943
+ path: createPath(parsedPath),
2944
+ submission
2861
2945
  };
2862
2946
  } // Filter out all routes below any caught error as they aren't going to
2863
2947
  // render so we don't need to load them
@@ -3101,7 +3185,7 @@ function createClientSideRequest(location, signal, submission) {
3101
3185
  signal
3102
3186
  };
3103
3187
 
3104
- if (submission) {
3188
+ if (submission && isMutationMethod(submission.formMethod)) {
3105
3189
  let {
3106
3190
  formMethod,
3107
3191
  formEncType,
@@ -3151,11 +3235,14 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
3151
3235
  pendingError = undefined;
3152
3236
  }
3153
3237
 
3154
- errors = Object.assign(errors || {}, {
3155
- [boundaryMatch.route.id]: error
3156
- }); // Once we find our first (highest) error, we set the status code and
3238
+ errors = errors || {}; // Prefer higher error values if lower errors bubble to the same boundary
3239
+
3240
+ if (errors[boundaryMatch.route.id] == null) {
3241
+ errors[boundaryMatch.route.id] = error;
3242
+ } // Once we find our first (highest) error, we set the status code and
3157
3243
  // prevent deeper status codes from overriding
3158
3244
 
3245
+
3159
3246
  if (!foundError) {
3160
3247
  foundError = true;
3161
3248
  statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
@@ -3371,8 +3458,8 @@ function isValidMethod(method) {
3371
3458
  return validRequestMethods.has(method);
3372
3459
  }
3373
3460
 
3374
- function isSubmissionMethod(method) {
3375
- return validActionMethods.has(method);
3461
+ function isMutationMethod(method) {
3462
+ return validMutationMethods.has(method);
3376
3463
  }
3377
3464
 
3378
3465
  async function resolveDeferredResults(currentMatches, matchesToLoad, results, signal, isFetcher, currentLoaderData) {