@remix-run/router 1.10.0 → 1.11.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 +27 -0
- package/dist/router.cjs.js +109 -33
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +4 -2
- package/dist/router.js +105 -33
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +109 -33
- 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/package.json +1 -1
- package/router.ts +84 -20
- package/utils.ts +19 -15
package/dist/router.d.ts
CHANGED
|
@@ -120,7 +120,7 @@ export interface Router {
|
|
|
120
120
|
* Get/create a fetcher for the given key
|
|
121
121
|
* @param key
|
|
122
122
|
*/
|
|
123
|
-
getFetcher<TData = any>(key
|
|
123
|
+
getFetcher<TData = any>(key: string): Fetcher<TData>;
|
|
124
124
|
/**
|
|
125
125
|
* @internal
|
|
126
126
|
* PRIVATE - DO NOT USE
|
|
@@ -128,7 +128,7 @@ export interface Router {
|
|
|
128
128
|
* Delete the fetcher for a given key
|
|
129
129
|
* @param key
|
|
130
130
|
*/
|
|
131
|
-
deleteFetcher(key
|
|
131
|
+
deleteFetcher(key: string): void;
|
|
132
132
|
/**
|
|
133
133
|
* @internal
|
|
134
134
|
* PRIVATE - DO NOT USE
|
|
@@ -246,6 +246,7 @@ export type HydrationState = Partial<Pick<RouterState, "loaderData" | "actionDat
|
|
|
246
246
|
* Future flags to toggle new feature behavior
|
|
247
247
|
*/
|
|
248
248
|
export interface FutureConfig {
|
|
249
|
+
v7_fetcherPersist: boolean;
|
|
249
250
|
v7_normalizeFormMethod: boolean;
|
|
250
251
|
v7_prependBasename: boolean;
|
|
251
252
|
}
|
|
@@ -303,6 +304,7 @@ type ViewTransitionOpts = {
|
|
|
303
304
|
*/
|
|
304
305
|
export interface RouterSubscriber {
|
|
305
306
|
(state: RouterState, opts: {
|
|
307
|
+
deletedFetchers: string[];
|
|
306
308
|
unstable_viewTransitionOpts?: ViewTransitionOpts;
|
|
307
309
|
}): void;
|
|
308
310
|
}
|
package/dist/router.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @remix-run/router v1.
|
|
2
|
+
* @remix-run/router v1.11.0-pre.1
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -768,20 +768,29 @@ function matchPath(pattern, pathname) {
|
|
|
768
768
|
end: true
|
|
769
769
|
};
|
|
770
770
|
}
|
|
771
|
-
let [matcher,
|
|
771
|
+
let [matcher, compiledParams] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
|
|
772
772
|
let match = pathname.match(matcher);
|
|
773
773
|
if (!match) return null;
|
|
774
774
|
let matchedPathname = match[0];
|
|
775
775
|
let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
|
|
776
776
|
let captureGroups = match.slice(1);
|
|
777
|
-
let params =
|
|
777
|
+
let params = compiledParams.reduce((memo, _ref, index) => {
|
|
778
|
+
let {
|
|
779
|
+
paramName,
|
|
780
|
+
isOptional
|
|
781
|
+
} = _ref;
|
|
778
782
|
// We need to compute the pathnameBase here using the raw splat value
|
|
779
783
|
// instead of using params["*"] later because it will be decoded then
|
|
780
784
|
if (paramName === "*") {
|
|
781
785
|
let splatValue = captureGroups[index] || "";
|
|
782
786
|
pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
|
|
783
787
|
}
|
|
784
|
-
|
|
788
|
+
const value = captureGroups[index];
|
|
789
|
+
if (isOptional && !value) {
|
|
790
|
+
memo[paramName] = undefined;
|
|
791
|
+
} else {
|
|
792
|
+
memo[paramName] = safelyDecodeURIComponent(value || "", paramName);
|
|
793
|
+
}
|
|
785
794
|
return memo;
|
|
786
795
|
}, {});
|
|
787
796
|
return {
|
|
@@ -799,16 +808,21 @@ function compilePath(path, caseSensitive, end) {
|
|
|
799
808
|
end = true;
|
|
800
809
|
}
|
|
801
810
|
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(/\*$/, "/*") + "\"."));
|
|
802
|
-
let
|
|
811
|
+
let params = [];
|
|
803
812
|
let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
|
|
804
813
|
.replace(/^\/*/, "/") // Make sure it has a leading /
|
|
805
|
-
.replace(/[
|
|
806
|
-
.replace(/\/:(\w+)
|
|
807
|
-
|
|
808
|
-
|
|
814
|
+
.replace(/[\\.*+^${}|()[\]]/g, "\\$&") // Escape special regex chars
|
|
815
|
+
.replace(/\/:(\w+)(\?)?/g, (_, paramName, isOptional) => {
|
|
816
|
+
params.push({
|
|
817
|
+
paramName,
|
|
818
|
+
isOptional: isOptional != null
|
|
819
|
+
});
|
|
820
|
+
return isOptional ? "/?([^\\/]+)?" : "/([^\\/]+)";
|
|
809
821
|
});
|
|
810
822
|
if (path.endsWith("*")) {
|
|
811
|
-
|
|
823
|
+
params.push({
|
|
824
|
+
paramName: "*"
|
|
825
|
+
});
|
|
812
826
|
regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
|
|
813
827
|
: "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
|
|
814
828
|
} else if (end) {
|
|
@@ -825,7 +839,7 @@ function compilePath(path, caseSensitive, end) {
|
|
|
825
839
|
regexpSource += "(?:(?=\\/|$))";
|
|
826
840
|
} else ;
|
|
827
841
|
let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
|
|
828
|
-
return [matcher,
|
|
842
|
+
return [matcher, params];
|
|
829
843
|
}
|
|
830
844
|
function safelyDecodeURI(value) {
|
|
831
845
|
try {
|
|
@@ -1038,8 +1052,8 @@ class DeferredData {
|
|
|
1038
1052
|
let onAbort = () => reject(new AbortedDeferredError("Deferred data aborted"));
|
|
1039
1053
|
this.unlistenAbortSignal = () => this.controller.signal.removeEventListener("abort", onAbort);
|
|
1040
1054
|
this.controller.signal.addEventListener("abort", onAbort);
|
|
1041
|
-
this.data = Object.entries(data).reduce((acc,
|
|
1042
|
-
let [key, value] =
|
|
1055
|
+
this.data = Object.entries(data).reduce((acc, _ref2) => {
|
|
1056
|
+
let [key, value] = _ref2;
|
|
1043
1057
|
return Object.assign(acc, {
|
|
1044
1058
|
[key]: this.trackPromise(key, value)
|
|
1045
1059
|
});
|
|
@@ -1136,8 +1150,8 @@ class DeferredData {
|
|
|
1136
1150
|
}
|
|
1137
1151
|
get unwrappedData() {
|
|
1138
1152
|
invariant(this.data !== null && this.done, "Can only unwrap data on initialized and settled deferreds");
|
|
1139
|
-
return Object.entries(this.data).reduce((acc,
|
|
1140
|
-
let [key, value] =
|
|
1153
|
+
return Object.entries(this.data).reduce((acc, _ref3) => {
|
|
1154
|
+
let [key, value] = _ref3;
|
|
1141
1155
|
return Object.assign(acc, {
|
|
1142
1156
|
[key]: unwrapTrackedPromise(value)
|
|
1143
1157
|
});
|
|
@@ -1301,6 +1315,7 @@ function createRouter(init) {
|
|
|
1301
1315
|
let basename = init.basename || "/";
|
|
1302
1316
|
// Config driven behavior flags
|
|
1303
1317
|
let future = _extends({
|
|
1318
|
+
v7_fetcherPersist: false,
|
|
1304
1319
|
v7_normalizeFormMethod: false,
|
|
1305
1320
|
v7_prependBasename: false
|
|
1306
1321
|
}, init.future);
|
|
@@ -1403,6 +1418,11 @@ function createRouter(init) {
|
|
|
1403
1418
|
let fetchRedirectIds = new Set();
|
|
1404
1419
|
// Most recent href/match for fetcher.load calls for fetchers
|
|
1405
1420
|
let fetchLoadMatches = new Map();
|
|
1421
|
+
// Ref-count mounted fetchers so we know when it's ok to clean them up
|
|
1422
|
+
let activeFetchers = new Map();
|
|
1423
|
+
// Fetchers that have requested a delete when using v7_fetcherPersist,
|
|
1424
|
+
// they'll be officially removed after they return to idle
|
|
1425
|
+
let deletedFetchers = new Set();
|
|
1406
1426
|
// Store DeferredData instances for active route matches. When a
|
|
1407
1427
|
// route loader returns defer() we stick one in here. Then, when a nested
|
|
1408
1428
|
// promise resolves we update loaderData. If a new navigation starts we
|
|
@@ -1507,9 +1527,33 @@ function createRouter(init) {
|
|
|
1507
1527
|
// Update our state and notify the calling context of the change
|
|
1508
1528
|
function updateState(newState, viewTransitionOpts) {
|
|
1509
1529
|
state = _extends({}, state, newState);
|
|
1530
|
+
// Prep fetcher cleanup so we can tell the UI which fetcher data entries
|
|
1531
|
+
// can be removed
|
|
1532
|
+
let completedFetchers = [];
|
|
1533
|
+
let deletedFetchersKeys = [];
|
|
1534
|
+
if (future.v7_fetcherPersist) {
|
|
1535
|
+
state.fetchers.forEach((fetcher, key) => {
|
|
1536
|
+
if (fetcher.state === "idle") {
|
|
1537
|
+
if (deletedFetchers.has(key)) {
|
|
1538
|
+
// Unmounted from the UI and can be totally removed
|
|
1539
|
+
deletedFetchersKeys.push(key);
|
|
1540
|
+
} else {
|
|
1541
|
+
// Returned to idle but still mounted in the UI, so semi-remains for
|
|
1542
|
+
// revalidations and such
|
|
1543
|
+
completedFetchers.push(key);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1510
1548
|
subscribers.forEach(subscriber => subscriber(state, {
|
|
1549
|
+
deletedFetchers: deletedFetchersKeys,
|
|
1511
1550
|
unstable_viewTransitionOpts: viewTransitionOpts
|
|
1512
1551
|
}));
|
|
1552
|
+
// Remove idle fetchers from state since we only care about in-flight fetchers.
|
|
1553
|
+
if (future.v7_fetcherPersist) {
|
|
1554
|
+
completedFetchers.forEach(key => state.fetchers.delete(key));
|
|
1555
|
+
deletedFetchersKeys.forEach(key => deleteFetcher(key));
|
|
1556
|
+
}
|
|
1513
1557
|
}
|
|
1514
1558
|
// Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
|
|
1515
1559
|
// and setting state.[historyAction/location/matches] to the new route.
|
|
@@ -2027,6 +2071,14 @@ function createRouter(init) {
|
|
|
2027
2071
|
} : {});
|
|
2028
2072
|
}
|
|
2029
2073
|
function getFetcher(key) {
|
|
2074
|
+
if (future.v7_fetcherPersist) {
|
|
2075
|
+
activeFetchers.set(key, (activeFetchers.get(key) || 0) + 1);
|
|
2076
|
+
// If this fetcher was previously marked for deletion, unmark it since we
|
|
2077
|
+
// have a new instance
|
|
2078
|
+
if (deletedFetchers.has(key)) {
|
|
2079
|
+
deletedFetchers.delete(key);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2030
2082
|
return state.fetchers.get(key) || IDLE_FETCHER;
|
|
2031
2083
|
}
|
|
2032
2084
|
// Trigger a fetcher load/submit for the given fetcher key
|
|
@@ -2095,13 +2147,20 @@ function createRouter(init) {
|
|
|
2095
2147
|
let originatingLoadId = incrementingLoadId;
|
|
2096
2148
|
let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename);
|
|
2097
2149
|
if (fetchRequest.signal.aborted) {
|
|
2098
|
-
// We can delete this so long as we weren't aborted by
|
|
2150
|
+
// We can delete this so long as we weren't aborted by our own fetcher
|
|
2099
2151
|
// re-submit which would have put _new_ controller is in fetchControllers
|
|
2100
2152
|
if (fetchControllers.get(key) === abortController) {
|
|
2101
2153
|
fetchControllers.delete(key);
|
|
2102
2154
|
}
|
|
2103
2155
|
return;
|
|
2104
2156
|
}
|
|
2157
|
+
if (deletedFetchers.has(key)) {
|
|
2158
|
+
state.fetchers.set(key, getDoneFetcher(undefined));
|
|
2159
|
+
updateState({
|
|
2160
|
+
fetchers: new Map(state.fetchers)
|
|
2161
|
+
});
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2105
2164
|
if (isRedirectResult(actionResult)) {
|
|
2106
2165
|
fetchControllers.delete(key);
|
|
2107
2166
|
if (pendingNavigationLoadId > originatingLoadId) {
|
|
@@ -2206,7 +2265,7 @@ function createRouter(init) {
|
|
|
2206
2265
|
let doneFetcher = getDoneFetcher(actionResult.data);
|
|
2207
2266
|
state.fetchers.set(key, doneFetcher);
|
|
2208
2267
|
}
|
|
2209
|
-
|
|
2268
|
+
abortStaleFetchLoads(loadId);
|
|
2210
2269
|
// If we are currently in a navigation loading state and this fetcher is
|
|
2211
2270
|
// more recent than the navigation, we want the newer data so abort the
|
|
2212
2271
|
// navigation and complete it with the fetcher data
|
|
@@ -2223,12 +2282,11 @@ function createRouter(init) {
|
|
|
2223
2282
|
// otherwise just update with the fetcher data, preserving any existing
|
|
2224
2283
|
// loaderData for loaders that did not need to reload. We have to
|
|
2225
2284
|
// manually merge here since we aren't going through completeNavigation
|
|
2226
|
-
updateState(
|
|
2285
|
+
updateState({
|
|
2227
2286
|
errors,
|
|
2228
|
-
loaderData: mergeLoaderData(state.loaderData, loaderData, matches, errors)
|
|
2229
|
-
}, didAbortFetchLoads || revalidatingFetchers.length > 0 ? {
|
|
2287
|
+
loaderData: mergeLoaderData(state.loaderData, loaderData, matches, errors),
|
|
2230
2288
|
fetchers: new Map(state.fetchers)
|
|
2231
|
-
}
|
|
2289
|
+
});
|
|
2232
2290
|
isRevalidationRequired = false;
|
|
2233
2291
|
}
|
|
2234
2292
|
}
|
|
@@ -2262,6 +2320,13 @@ function createRouter(init) {
|
|
|
2262
2320
|
if (fetchRequest.signal.aborted) {
|
|
2263
2321
|
return;
|
|
2264
2322
|
}
|
|
2323
|
+
if (deletedFetchers.has(key)) {
|
|
2324
|
+
state.fetchers.set(key, getDoneFetcher(undefined));
|
|
2325
|
+
updateState({
|
|
2326
|
+
fetchers: new Map(state.fetchers)
|
|
2327
|
+
});
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2265
2330
|
// If the loader threw a redirect Response, start a new REPLACE navigation
|
|
2266
2331
|
if (isRedirectResult(result)) {
|
|
2267
2332
|
if (pendingNavigationLoadId > originatingLoadId) {
|
|
@@ -2281,17 +2346,7 @@ function createRouter(init) {
|
|
|
2281
2346
|
}
|
|
2282
2347
|
// Process any non-redirect errors thrown
|
|
2283
2348
|
if (isErrorResult(result)) {
|
|
2284
|
-
|
|
2285
|
-
state.fetchers.delete(key);
|
|
2286
|
-
// TODO: In remix, this would reset to IDLE_NAVIGATION if it was a catch -
|
|
2287
|
-
// do we need to behave any differently with our non-redirect errors?
|
|
2288
|
-
// What if it was a non-redirect Response?
|
|
2289
|
-
updateState({
|
|
2290
|
-
fetchers: new Map(state.fetchers),
|
|
2291
|
-
errors: {
|
|
2292
|
-
[boundaryMatch.route.id]: result.error
|
|
2293
|
-
}
|
|
2294
|
-
});
|
|
2349
|
+
setFetcherError(key, routeId, result.error);
|
|
2295
2350
|
return;
|
|
2296
2351
|
}
|
|
2297
2352
|
invariant(!isDeferredResult(result), "Unhandled fetcher deferred data");
|
|
@@ -2456,8 +2511,25 @@ function createRouter(init) {
|
|
|
2456
2511
|
fetchLoadMatches.delete(key);
|
|
2457
2512
|
fetchReloadIds.delete(key);
|
|
2458
2513
|
fetchRedirectIds.delete(key);
|
|
2514
|
+
deletedFetchers.delete(key);
|
|
2459
2515
|
state.fetchers.delete(key);
|
|
2460
2516
|
}
|
|
2517
|
+
function deleteFetcherAndUpdateState(key) {
|
|
2518
|
+
if (future.v7_fetcherPersist) {
|
|
2519
|
+
let count = (activeFetchers.get(key) || 0) - 1;
|
|
2520
|
+
if (count <= 0) {
|
|
2521
|
+
activeFetchers.delete(key);
|
|
2522
|
+
deletedFetchers.add(key);
|
|
2523
|
+
} else {
|
|
2524
|
+
activeFetchers.set(key, count);
|
|
2525
|
+
}
|
|
2526
|
+
} else {
|
|
2527
|
+
deleteFetcher(key);
|
|
2528
|
+
}
|
|
2529
|
+
updateState({
|
|
2530
|
+
fetchers: new Map(state.fetchers)
|
|
2531
|
+
});
|
|
2532
|
+
}
|
|
2461
2533
|
function abortFetcher(key) {
|
|
2462
2534
|
let controller = fetchControllers.get(key);
|
|
2463
2535
|
invariant(controller, "Expected fetch controller: " + key);
|
|
@@ -2646,7 +2718,7 @@ function createRouter(init) {
|
|
|
2646
2718
|
createHref: to => init.history.createHref(to),
|
|
2647
2719
|
encodeLocation: to => init.history.encodeLocation(to),
|
|
2648
2720
|
getFetcher,
|
|
2649
|
-
deleteFetcher,
|
|
2721
|
+
deleteFetcher: deleteFetcherAndUpdateState,
|
|
2650
2722
|
dispose,
|
|
2651
2723
|
getBlocker,
|
|
2652
2724
|
deleteBlocker,
|