@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/CHANGELOG.md +55 -0
- package/dist/router.cjs.js +126 -39
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.js +126 -39
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +126 -39
- package/dist/router.umd.js.map +1 -1
- package/dist/router.umd.min.js +2 -2
- package/dist/router.umd.min.js.map +1 -1
- package/dist/utils.d.ts +5 -5
- package/package.json +1 -1
- package/router.ts +54 -30
- package/utils.ts +105 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# `@remix-run/router`
|
|
2
2
|
|
|
3
|
+
## 1.1.0-pre.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fix issue with deeply nested optional segments ([#9727](https://github.com/remix-run/react-router/pull/9727))
|
|
8
|
+
|
|
9
|
+
## 1.1.0-pre.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Support for optional path segments ([#9650](https://github.com/remix-run/react-router/pull/9650))
|
|
14
|
+
- You can now denote optional path segments with a `?` as the last character in a path segment
|
|
15
|
+
- Optional params examples
|
|
16
|
+
- `:lang?/about` will get expanded and match:
|
|
17
|
+
- `/:lang/about`
|
|
18
|
+
- `/about`
|
|
19
|
+
- `/multistep/:widget1?/widget2?/widget3?` will get expanded and match:
|
|
20
|
+
- `/multistep/:widget1/:widget2/:widget3`
|
|
21
|
+
- `/multistep/:widget1/:widget2`
|
|
22
|
+
- `/multistep/:widget1`
|
|
23
|
+
- `/multistep`
|
|
24
|
+
- Optional static segment example
|
|
25
|
+
- `/fr?/about` will get expanded and match:
|
|
26
|
+
- `/fr/about`
|
|
27
|
+
- `/about`
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- Stop incorrectly matching on partial named parameters, i.e. `<Route path="prefix-:param">`, to align with how splat parameters work. If you were previously relying on this behavior then it's recommended to extract the static portion of the path at the `useParams` call site: ([#9506](https://github.com/remix-run/react-router/pull/9506))
|
|
32
|
+
|
|
33
|
+
```jsx
|
|
34
|
+
// Old behavior at URL /prefix-123
|
|
35
|
+
<Route path="prefix-:id" element={<Comp /> }>
|
|
36
|
+
|
|
37
|
+
function Comp() {
|
|
38
|
+
let params = useParams(); // { id: '123' }
|
|
39
|
+
let id = params.id; // "123"
|
|
40
|
+
...
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// New behavior at URL /prefix-123
|
|
44
|
+
<Route path=":id" element={<Comp /> }>
|
|
45
|
+
|
|
46
|
+
function Comp() {
|
|
47
|
+
let params = useParams(); // { id: 'prefix-123' }
|
|
48
|
+
let id = params.id.replace(/^prefix-/, ''); // "123"
|
|
49
|
+
...
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- Fix requests sent to revalidating loaders so they reflect a GET request ([#9660](https://github.com/remix-run/react-router/pull/9660))
|
|
54
|
+
- Persist `headers` on `loader` `request`'s after SSR document `action` request ([#9721](https://github.com/remix-run/react-router/pull/9721))
|
|
55
|
+
- `GET` forms now expose a submission on the loading navigation ([#9695](https://github.com/remix-run/react-router/pull/9695))
|
|
56
|
+
- Fix error boundary tracking for multiple errors bubbling to the same boundary ([#9702](https://github.com/remix-run/react-router/pull/9702))
|
|
57
|
+
|
|
3
58
|
## 1.0.5
|
|
4
59
|
|
|
5
60
|
### Patch Changes
|
package/dist/router.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @remix-run/router v1.0.
|
|
2
|
+
* @remix-run/router v1.1.0-pre.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -611,9 +611,9 @@ function flattenRoutes(routes, branches, parentsMeta, parentPath) {
|
|
|
611
611
|
parentPath = "";
|
|
612
612
|
}
|
|
613
613
|
|
|
614
|
-
|
|
614
|
+
let flattenRoute = (route, index, relativePath) => {
|
|
615
615
|
let meta = {
|
|
616
|
-
relativePath: route.path || "",
|
|
616
|
+
relativePath: relativePath === undefined ? route.path || "" : relativePath,
|
|
617
617
|
caseSensitive: route.caseSensitive === true,
|
|
618
618
|
childrenIndex: index,
|
|
619
619
|
route
|
|
@@ -647,9 +647,71 @@ function flattenRoutes(routes, branches, parentsMeta, parentPath) {
|
|
|
647
647
|
score: computeScore(path, route.index),
|
|
648
648
|
routesMeta
|
|
649
649
|
});
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
routes.forEach((route, index) => {
|
|
653
|
+
var _route$path;
|
|
654
|
+
|
|
655
|
+
// coarse-grain check for optional params
|
|
656
|
+
if (route.path === "" || !((_route$path = route.path) != null && _route$path.includes("?"))) {
|
|
657
|
+
flattenRoute(route, index);
|
|
658
|
+
} else {
|
|
659
|
+
for (let exploded of explodeOptionalSegments(route.path)) {
|
|
660
|
+
flattenRoute(route, index, exploded);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
650
663
|
});
|
|
651
664
|
return branches;
|
|
652
665
|
}
|
|
666
|
+
/**
|
|
667
|
+
* Computes all combinations of optional path segments for a given path,
|
|
668
|
+
* excluding combinations that are ambiguous and of lower priority.
|
|
669
|
+
*
|
|
670
|
+
* For example, `/one/:two?/three/:four?/:five?` explodes to:
|
|
671
|
+
* - `/one/three`
|
|
672
|
+
* - `/one/:two/three`
|
|
673
|
+
* - `/one/three/:four`
|
|
674
|
+
* - `/one/three/:five`
|
|
675
|
+
* - `/one/:two/three/:four`
|
|
676
|
+
* - `/one/:two/three/:five`
|
|
677
|
+
* - `/one/three/:four/:five`
|
|
678
|
+
* - `/one/:two/three/:four/:five`
|
|
679
|
+
*/
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
function explodeOptionalSegments(path) {
|
|
683
|
+
let segments = path.split("/");
|
|
684
|
+
if (segments.length === 0) return [];
|
|
685
|
+
let [first, ...rest] = segments; // Optional path segments are denoted by a trailing `?`
|
|
686
|
+
|
|
687
|
+
let isOptional = first.endsWith("?"); // Compute the corresponding required segment: `foo?` -> `foo`
|
|
688
|
+
|
|
689
|
+
let required = first.replace(/\?$/, "");
|
|
690
|
+
|
|
691
|
+
if (rest.length === 0) {
|
|
692
|
+
// Intepret empty string as omitting an optional segment
|
|
693
|
+
// `["one", "", "three"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three`
|
|
694
|
+
return isOptional ? [required, ""] : [required];
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
let restExploded = explodeOptionalSegments(rest.join("/"));
|
|
698
|
+
let result = []; // All child paths with the prefix. Do this for all children before the
|
|
699
|
+
// optional version for all children so we get consistent ordering where the
|
|
700
|
+
// parent optional aspect is preferred as required. Otherwise, we can get
|
|
701
|
+
// child sections interspersed where deeper optional segments are higher than
|
|
702
|
+
// parent optional segments, where for example, /:two would explodes _earlier_
|
|
703
|
+
// then /:one. By always including the parent as required _for all children_
|
|
704
|
+
// first, we avoid this issue
|
|
705
|
+
|
|
706
|
+
result.push(...restExploded.map(subpath => subpath === "" ? required : [required, subpath].join("/"))); // Then if this is an optional value, add all child versions without
|
|
707
|
+
|
|
708
|
+
if (isOptional) {
|
|
709
|
+
result.push(...restExploded);
|
|
710
|
+
} // for absolute paths, ensure `/` instead of empty segment
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
return result.map(exploded => path.startsWith("/") && exploded === "" ? "/" : exploded);
|
|
714
|
+
}
|
|
653
715
|
|
|
654
716
|
function rankRouteBranches(branches) {
|
|
655
717
|
branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
|
|
@@ -733,14 +795,24 @@ function matchRouteBranch(branch, pathname) {
|
|
|
733
795
|
*/
|
|
734
796
|
|
|
735
797
|
|
|
736
|
-
function generatePath(
|
|
798
|
+
function generatePath(originalPath, params) {
|
|
737
799
|
if (params === void 0) {
|
|
738
800
|
params = {};
|
|
739
801
|
}
|
|
740
802
|
|
|
741
|
-
|
|
803
|
+
let path = originalPath;
|
|
804
|
+
|
|
805
|
+
if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
|
|
806
|
+
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(/\*$/, "/*") + "\"."));
|
|
807
|
+
path = path.replace(/\*$/, "/*");
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return path.replace(/^:(\w+)/g, (_, key) => {
|
|
742
811
|
invariant(params[key] != null, "Missing \":" + key + "\" param");
|
|
743
812
|
return params[key];
|
|
813
|
+
}).replace(/\/:(\w+)/g, (_, key) => {
|
|
814
|
+
invariant(params[key] != null, "Missing \":" + key + "\" param");
|
|
815
|
+
return "/" + params[key];
|
|
744
816
|
}).replace(/(\/?)\*/, (_, prefix, __, str) => {
|
|
745
817
|
const star = "*";
|
|
746
818
|
|
|
@@ -812,9 +884,9 @@ function compilePath(path, caseSensitive, end) {
|
|
|
812
884
|
let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
|
|
813
885
|
.replace(/^\/*/, "/") // Make sure it has a leading /
|
|
814
886
|
.replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
|
|
815
|
-
.replace(
|
|
887
|
+
.replace(/\/:(\w+)/g, (_, paramName) => {
|
|
816
888
|
paramNames.push(paramName);
|
|
817
|
-
return "([^\\/]+)";
|
|
889
|
+
return "/([^\\/]+)";
|
|
818
890
|
});
|
|
819
891
|
|
|
820
892
|
if (path.endsWith("*")) {
|
|
@@ -1295,9 +1367,9 @@ function isRouteErrorResponse(e) {
|
|
|
1295
1367
|
* A Router instance manages all navigation and data loading/mutations
|
|
1296
1368
|
*/
|
|
1297
1369
|
|
|
1298
|
-
const
|
|
1299
|
-
const
|
|
1300
|
-
const validRequestMethodsArr = ["get", ...
|
|
1370
|
+
const validMutationMethodsArr = ["post", "put", "patch", "delete"];
|
|
1371
|
+
const validMutationMethods = new Set(validMutationMethodsArr);
|
|
1372
|
+
const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
|
|
1301
1373
|
const validRequestMethods = new Set(validRequestMethodsArr);
|
|
1302
1374
|
const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
|
|
1303
1375
|
const redirectPreserveMethodStatusCodes = new Set([307, 308]);
|
|
@@ -1534,7 +1606,7 @@ function createRouter(init) {
|
|
|
1534
1606
|
// without having to touch history
|
|
1535
1607
|
|
|
1536
1608
|
location = _extends({}, location, init.history.encodeLocation(location));
|
|
1537
|
-
let historyAction = (opts && opts.replace) === true || submission != null ? exports.Action.Replace : exports.Action.Push;
|
|
1609
|
+
let historyAction = (opts && opts.replace) === true || submission != null && isMutationMethod(submission.formMethod) ? exports.Action.Replace : exports.Action.Push;
|
|
1538
1610
|
let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
|
|
1539
1611
|
return await startNavigation(historyAction, location, {
|
|
1540
1612
|
submission,
|
|
@@ -1638,7 +1710,7 @@ function createRouter(init) {
|
|
|
1638
1710
|
pendingError = {
|
|
1639
1711
|
[findNearestBoundary(matches).route.id]: opts.pendingError
|
|
1640
1712
|
};
|
|
1641
|
-
} else if (opts && opts.submission) {
|
|
1713
|
+
} else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
|
|
1642
1714
|
// Call action if we received an action submission
|
|
1643
1715
|
let actionOutput = await handleAction(request, location, opts.submission, matches, {
|
|
1644
1716
|
replace: opts.replace
|
|
@@ -1765,14 +1837,15 @@ function createRouter(init) {
|
|
|
1765
1837
|
let loadingNavigation = overrideNavigation;
|
|
1766
1838
|
|
|
1767
1839
|
if (!loadingNavigation) {
|
|
1768
|
-
let navigation = {
|
|
1840
|
+
let navigation = _extends({
|
|
1769
1841
|
state: "loading",
|
|
1770
1842
|
location,
|
|
1771
1843
|
formMethod: undefined,
|
|
1772
1844
|
formAction: undefined,
|
|
1773
1845
|
formEncType: undefined,
|
|
1774
1846
|
formData: undefined
|
|
1775
|
-
};
|
|
1847
|
+
}, submission);
|
|
1848
|
+
|
|
1776
1849
|
loadingNavigation = navigation;
|
|
1777
1850
|
}
|
|
1778
1851
|
|
|
@@ -1907,7 +1980,7 @@ function createRouter(init) {
|
|
|
1907
1980
|
} = normalizeNavigateOptions(href, opts, true);
|
|
1908
1981
|
let match = getTargetMatch(matches, path);
|
|
1909
1982
|
|
|
1910
|
-
if (submission) {
|
|
1983
|
+
if (submission && isMutationMethod(submission.formMethod)) {
|
|
1911
1984
|
handleFetcherAction(key, routeId, path, match, matches, submission);
|
|
1912
1985
|
return;
|
|
1913
1986
|
} // Store off the match so we can call it's shouldRevalidate on subsequent
|
|
@@ -1915,7 +1988,7 @@ function createRouter(init) {
|
|
|
1915
1988
|
|
|
1916
1989
|
|
|
1917
1990
|
fetchLoadMatches.set(key, [path, match, matches]);
|
|
1918
|
-
handleFetcherLoader(key, routeId, path, match, matches);
|
|
1991
|
+
handleFetcherLoader(key, routeId, path, match, matches, submission);
|
|
1919
1992
|
} // Call the action for the matched fetcher.submit(), and then handle redirects,
|
|
1920
1993
|
// errors, and revalidation
|
|
1921
1994
|
|
|
@@ -2096,17 +2169,19 @@ function createRouter(init) {
|
|
|
2096
2169
|
} // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
|
|
2097
2170
|
|
|
2098
2171
|
|
|
2099
|
-
async function handleFetcherLoader(key, routeId, path, match, matches) {
|
|
2172
|
+
async function handleFetcherLoader(key, routeId, path, match, matches, submission) {
|
|
2100
2173
|
let existingFetcher = state.fetchers.get(key); // Put this fetcher into it's loading state
|
|
2101
2174
|
|
|
2102
|
-
let loadingFetcher = {
|
|
2175
|
+
let loadingFetcher = _extends({
|
|
2103
2176
|
state: "loading",
|
|
2104
2177
|
formMethod: undefined,
|
|
2105
2178
|
formAction: undefined,
|
|
2106
2179
|
formEncType: undefined,
|
|
2107
|
-
formData: undefined
|
|
2180
|
+
formData: undefined
|
|
2181
|
+
}, submission, {
|
|
2108
2182
|
data: existingFetcher && existingFetcher.data
|
|
2109
|
-
};
|
|
2183
|
+
});
|
|
2184
|
+
|
|
2110
2185
|
state.fetchers.set(key, loadingFetcher);
|
|
2111
2186
|
updateState({
|
|
2112
2187
|
fetchers: new Map(state.fetchers)
|
|
@@ -2226,10 +2301,10 @@ function createRouter(init) {
|
|
|
2226
2301
|
formEncType,
|
|
2227
2302
|
formData
|
|
2228
2303
|
} = state.navigation; // If this was a 307/308 submission we want to preserve the HTTP method and
|
|
2229
|
-
// re-submit the POST/PUT/PATCH/DELETE as a submission navigation to the
|
|
2304
|
+
// re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
|
|
2230
2305
|
// redirected location
|
|
2231
2306
|
|
|
2232
|
-
if (redirectPreserveMethodStatusCodes.has(redirect.status) && formMethod &&
|
|
2307
|
+
if (redirectPreserveMethodStatusCodes.has(redirect.status) && formMethod && isMutationMethod(formMethod) && formEncType && formData) {
|
|
2233
2308
|
await startNavigation(redirectHistoryAction, redirectLocation, {
|
|
2234
2309
|
submission: {
|
|
2235
2310
|
formMethod,
|
|
@@ -2640,7 +2715,7 @@ function unstable_createStaticHandler(routes, opts) {
|
|
|
2640
2715
|
invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
|
|
2641
2716
|
|
|
2642
2717
|
try {
|
|
2643
|
-
if (
|
|
2718
|
+
if (isMutationMethod(request.method.toLowerCase())) {
|
|
2644
2719
|
let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
|
|
2645
2720
|
return result;
|
|
2646
2721
|
}
|
|
@@ -2757,6 +2832,8 @@ function unstable_createStaticHandler(routes, opts) {
|
|
|
2757
2832
|
|
|
2758
2833
|
|
|
2759
2834
|
let loaderRequest = new Request(request.url, {
|
|
2835
|
+
headers: request.headers,
|
|
2836
|
+
redirect: request.redirect,
|
|
2760
2837
|
signal: request.signal
|
|
2761
2838
|
});
|
|
2762
2839
|
let context = await loadRouteData(loaderRequest, matches, requestContext);
|
|
@@ -2872,16 +2949,22 @@ function normalizeNavigateOptions(to, opts, isFetcher) {
|
|
|
2872
2949
|
} // Create a Submission on non-GET navigations
|
|
2873
2950
|
|
|
2874
2951
|
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
}
|
|
2952
|
+
let submission;
|
|
2953
|
+
|
|
2954
|
+
if (opts.formData) {
|
|
2955
|
+
submission = {
|
|
2956
|
+
formMethod: opts.formMethod || "get",
|
|
2957
|
+
formAction: stripHashFromPath(path),
|
|
2958
|
+
formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
|
|
2959
|
+
formData: opts.formData
|
|
2884
2960
|
};
|
|
2961
|
+
|
|
2962
|
+
if (isMutationMethod(submission.formMethod)) {
|
|
2963
|
+
return {
|
|
2964
|
+
path,
|
|
2965
|
+
submission
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2885
2968
|
} // Flatten submission onto URLSearchParams for GET submissions
|
|
2886
2969
|
|
|
2887
2970
|
|
|
@@ -2905,7 +2988,8 @@ function normalizeNavigateOptions(to, opts, isFetcher) {
|
|
|
2905
2988
|
}
|
|
2906
2989
|
|
|
2907
2990
|
return {
|
|
2908
|
-
path: createPath(parsedPath)
|
|
2991
|
+
path: createPath(parsedPath),
|
|
2992
|
+
submission
|
|
2909
2993
|
};
|
|
2910
2994
|
} // Filter out all routes below any caught error as they aren't going to
|
|
2911
2995
|
// render so we don't need to load them
|
|
@@ -3149,7 +3233,7 @@ function createClientSideRequest(location, signal, submission) {
|
|
|
3149
3233
|
signal
|
|
3150
3234
|
};
|
|
3151
3235
|
|
|
3152
|
-
if (submission) {
|
|
3236
|
+
if (submission && isMutationMethod(submission.formMethod)) {
|
|
3153
3237
|
let {
|
|
3154
3238
|
formMethod,
|
|
3155
3239
|
formEncType,
|
|
@@ -3199,11 +3283,14 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
3199
3283
|
pendingError = undefined;
|
|
3200
3284
|
}
|
|
3201
3285
|
|
|
3202
|
-
errors =
|
|
3203
|
-
|
|
3204
|
-
|
|
3286
|
+
errors = errors || {}; // Prefer higher error values if lower errors bubble to the same boundary
|
|
3287
|
+
|
|
3288
|
+
if (errors[boundaryMatch.route.id] == null) {
|
|
3289
|
+
errors[boundaryMatch.route.id] = error;
|
|
3290
|
+
} // Once we find our first (highest) error, we set the status code and
|
|
3205
3291
|
// prevent deeper status codes from overriding
|
|
3206
3292
|
|
|
3293
|
+
|
|
3207
3294
|
if (!foundError) {
|
|
3208
3295
|
foundError = true;
|
|
3209
3296
|
statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
|
|
@@ -3419,8 +3506,8 @@ function isValidMethod(method) {
|
|
|
3419
3506
|
return validRequestMethods.has(method);
|
|
3420
3507
|
}
|
|
3421
3508
|
|
|
3422
|
-
function
|
|
3423
|
-
return
|
|
3509
|
+
function isMutationMethod(method) {
|
|
3510
|
+
return validMutationMethods.has(method);
|
|
3424
3511
|
}
|
|
3425
3512
|
|
|
3426
3513
|
async function resolveDeferredResults(currentMatches, matchesToLoad, results, signal, isFetcher, currentLoaderData) {
|