@remix-run/router 1.7.1 → 1.7.2-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 +15 -0
- package/dist/router.cjs.js +104 -43
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.js +103 -43
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +104 -43
- 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 +4 -1
- package/package.json +1 -1
- package/router.ts +84 -37
- package/utils.ts +22 -3
package/dist/utils.d.ts
CHANGED
|
@@ -191,12 +191,15 @@ export interface MapRoutePropertiesFunction {
|
|
|
191
191
|
*/
|
|
192
192
|
export type ImmutableRouteKey = "lazy" | "caseSensitive" | "path" | "id" | "index" | "children";
|
|
193
193
|
export declare const immutableRouteKeys: Set<ImmutableRouteKey>;
|
|
194
|
+
type RequireOne<T, Key = keyof T> = Exclude<{
|
|
195
|
+
[K in keyof T]: K extends Key ? Omit<T, K> & Required<Pick<T, K>> : never;
|
|
196
|
+
}[keyof T], undefined>;
|
|
194
197
|
/**
|
|
195
198
|
* lazy() function to load a route definition, which can add non-matching
|
|
196
199
|
* related properties to a route
|
|
197
200
|
*/
|
|
198
201
|
export interface LazyRouteFunction<R extends AgnosticRouteObject> {
|
|
199
|
-
(): Promise<Omit<R, ImmutableRouteKey
|
|
202
|
+
(): Promise<RequireOne<Omit<R, ImmutableRouteKey>>>;
|
|
200
203
|
}
|
|
201
204
|
/**
|
|
202
205
|
* Base RouteObject with common props shared by all types of routes
|
package/package.json
CHANGED
package/router.ts
CHANGED
|
@@ -1500,6 +1500,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1500
1500
|
(matchesToLoad && matchesToLoad.some((m) => m.route.id === routeId))
|
|
1501
1501
|
);
|
|
1502
1502
|
|
|
1503
|
+
pendingNavigationLoadId = ++incrementingLoadId;
|
|
1504
|
+
|
|
1503
1505
|
// Short circuit if we have no loaders to run
|
|
1504
1506
|
if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {
|
|
1505
1507
|
let updatedFetchers = markFetchRedirectsDone();
|
|
@@ -1541,7 +1543,6 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1541
1543
|
});
|
|
1542
1544
|
}
|
|
1543
1545
|
|
|
1544
|
-
pendingNavigationLoadId = ++incrementingLoadId;
|
|
1545
1546
|
revalidatingFetchers.forEach((rf) => {
|
|
1546
1547
|
if (fetchControllers.has(rf.key)) {
|
|
1547
1548
|
abortFetcher(rf.key);
|
|
@@ -1591,7 +1592,15 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1591
1592
|
// If any loaders returned a redirect Response, start a new REPLACE navigation
|
|
1592
1593
|
let redirect = findRedirect(results);
|
|
1593
1594
|
if (redirect) {
|
|
1594
|
-
|
|
1595
|
+
if (redirect.idx >= matchesToLoad.length) {
|
|
1596
|
+
// If this redirect came from a fetcher make sure we mark it in
|
|
1597
|
+
// fetchRedirectIds so it doesn't get revalidated on the next set of
|
|
1598
|
+
// loader executions
|
|
1599
|
+
let fetcherKey =
|
|
1600
|
+
revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
1601
|
+
fetchRedirectIds.add(fetcherKey);
|
|
1602
|
+
}
|
|
1603
|
+
await startRedirectNavigation(state, redirect.result, { replace });
|
|
1595
1604
|
return { shortCircuited: true };
|
|
1596
1605
|
}
|
|
1597
1606
|
|
|
@@ -1739,6 +1748,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1739
1748
|
);
|
|
1740
1749
|
fetchControllers.set(key, abortController);
|
|
1741
1750
|
|
|
1751
|
+
let originatingLoadId = incrementingLoadId;
|
|
1742
1752
|
let actionResult = await callLoaderOrAction(
|
|
1743
1753
|
"action",
|
|
1744
1754
|
fetchRequest,
|
|
@@ -1760,15 +1770,26 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1760
1770
|
|
|
1761
1771
|
if (isRedirectResult(actionResult)) {
|
|
1762
1772
|
fetchControllers.delete(key);
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1773
|
+
if (pendingNavigationLoadId > originatingLoadId) {
|
|
1774
|
+
// A new navigation was kicked off after our action started, so that
|
|
1775
|
+
// should take precedence over this redirect navigation. We already
|
|
1776
|
+
// set isRevalidationRequired so all loaders for the new route should
|
|
1777
|
+
// fire unless opted out via shouldRevalidate
|
|
1778
|
+
let doneFetcher = getDoneFetcher(undefined);
|
|
1779
|
+
state.fetchers.set(key, doneFetcher);
|
|
1780
|
+
updateState({ fetchers: new Map(state.fetchers) });
|
|
1781
|
+
return;
|
|
1782
|
+
} else {
|
|
1783
|
+
fetchRedirectIds.add(key);
|
|
1784
|
+
let loadingFetcher = getLoadingFetcher(submission);
|
|
1785
|
+
state.fetchers.set(key, loadingFetcher);
|
|
1786
|
+
updateState({ fetchers: new Map(state.fetchers) });
|
|
1787
|
+
|
|
1788
|
+
return startRedirectNavigation(state, actionResult, {
|
|
1789
|
+
submission,
|
|
1790
|
+
isFetchActionRedirect: true,
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1772
1793
|
}
|
|
1773
1794
|
|
|
1774
1795
|
// Process any non-redirect errors thrown
|
|
@@ -1875,7 +1896,15 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1875
1896
|
|
|
1876
1897
|
let redirect = findRedirect(results);
|
|
1877
1898
|
if (redirect) {
|
|
1878
|
-
|
|
1899
|
+
if (redirect.idx >= matchesToLoad.length) {
|
|
1900
|
+
// If this redirect came from a fetcher make sure we mark it in
|
|
1901
|
+
// fetchRedirectIds so it doesn't get revalidated on the next set of
|
|
1902
|
+
// loader executions
|
|
1903
|
+
let fetcherKey =
|
|
1904
|
+
revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
1905
|
+
fetchRedirectIds.add(fetcherKey);
|
|
1906
|
+
}
|
|
1907
|
+
return startRedirectNavigation(state, redirect.result);
|
|
1879
1908
|
}
|
|
1880
1909
|
|
|
1881
1910
|
// Process and commit output from loaders
|
|
@@ -1962,6 +1991,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1962
1991
|
);
|
|
1963
1992
|
fetchControllers.set(key, abortController);
|
|
1964
1993
|
|
|
1994
|
+
let originatingLoadId = incrementingLoadId;
|
|
1965
1995
|
let result: DataResult = await callLoaderOrAction(
|
|
1966
1996
|
"loader",
|
|
1967
1997
|
fetchRequest,
|
|
@@ -1994,9 +2024,18 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1994
2024
|
|
|
1995
2025
|
// If the loader threw a redirect Response, start a new REPLACE navigation
|
|
1996
2026
|
if (isRedirectResult(result)) {
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2027
|
+
if (pendingNavigationLoadId > originatingLoadId) {
|
|
2028
|
+
// A new navigation was kicked off after our loader started, so that
|
|
2029
|
+
// should take precedence over this redirect navigation
|
|
2030
|
+
let doneFetcher = getDoneFetcher(undefined);
|
|
2031
|
+
state.fetchers.set(key, doneFetcher);
|
|
2032
|
+
updateState({ fetchers: new Map(state.fetchers) });
|
|
2033
|
+
return;
|
|
2034
|
+
} else {
|
|
2035
|
+
fetchRedirectIds.add(key);
|
|
2036
|
+
await startRedirectNavigation(state, result);
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2000
2039
|
}
|
|
2001
2040
|
|
|
2002
2041
|
// Process any non-redirect errors thrown
|
|
@@ -3360,7 +3399,9 @@ function getMatchesToLoad(
|
|
|
3360
3399
|
let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
|
|
3361
3400
|
|
|
3362
3401
|
// If the fetcher path no longer matches, push it in with null matches so
|
|
3363
|
-
// we can trigger a 404 in callLoadersAndMaybeResolveData
|
|
3402
|
+
// we can trigger a 404 in callLoadersAndMaybeResolveData. Note this is
|
|
3403
|
+
// currently only a use-case for Remix HMR where the route tree can change
|
|
3404
|
+
// at runtime and remove a route previously loaded via a fetcher
|
|
3364
3405
|
if (!fetcherMatches) {
|
|
3365
3406
|
revalidatingFetchers.push({
|
|
3366
3407
|
key,
|
|
@@ -3374,28 +3415,31 @@ function getMatchesToLoad(
|
|
|
3374
3415
|
}
|
|
3375
3416
|
|
|
3376
3417
|
// Revalidating fetchers are decoupled from the route matches since they
|
|
3377
|
-
// load from a static href. They
|
|
3378
|
-
//
|
|
3379
|
-
//
|
|
3380
|
-
// They automatically revalidate without even calling shouldRevalidate if:
|
|
3381
|
-
// - They were cancelled
|
|
3382
|
-
// - They're in the middle of their first load and therefore this is still
|
|
3383
|
-
// an initial load and not a revalidation
|
|
3384
|
-
//
|
|
3385
|
-
// If neither of those is true, then they _always_ check shouldRevalidate
|
|
3418
|
+
// load from a static href. They revalidate based on explicit revalidation
|
|
3419
|
+
// (submission, useRevalidator, or X-Remix-Revalidate)
|
|
3386
3420
|
let fetcher = state.fetchers.get(key);
|
|
3387
|
-
let
|
|
3421
|
+
let fetcherMatch = getTargetMatch(fetcherMatches, f.path);
|
|
3422
|
+
|
|
3423
|
+
let shouldRevalidate = false;
|
|
3424
|
+
if (fetchRedirectIds.has(key)) {
|
|
3425
|
+
// Never trigger a revalidation of an actively redirecting fetcher
|
|
3426
|
+
shouldRevalidate = false;
|
|
3427
|
+
} else if (cancelledFetcherLoads.includes(key)) {
|
|
3428
|
+
// Always revalidate if the fetcher was cancelled
|
|
3429
|
+
shouldRevalidate = true;
|
|
3430
|
+
} else if (
|
|
3388
3431
|
fetcher &&
|
|
3389
3432
|
fetcher.state !== "idle" &&
|
|
3390
|
-
fetcher.data === undefined
|
|
3391
|
-
|
|
3392
|
-
//
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3433
|
+
fetcher.data === undefined
|
|
3434
|
+
) {
|
|
3435
|
+
// If the fetcher hasn't ever completed loading yet, then this isn't a
|
|
3436
|
+
// revalidation, it would just be a brand new load if an explicit
|
|
3437
|
+
// revalidation is required
|
|
3438
|
+
shouldRevalidate = isRevalidationRequired;
|
|
3439
|
+
} else {
|
|
3440
|
+
// Otherwise fall back on any user-defined shouldRevalidate, defaulting
|
|
3441
|
+
// to explicit revalidations only
|
|
3442
|
+
shouldRevalidate = shouldRevalidateLoader(fetcherMatch, {
|
|
3399
3443
|
currentUrl,
|
|
3400
3444
|
currentParams: state.matches[state.matches.length - 1].params,
|
|
3401
3445
|
nextUrl,
|
|
@@ -3404,6 +3448,7 @@ function getMatchesToLoad(
|
|
|
3404
3448
|
actionResult,
|
|
3405
3449
|
defaultShouldRevalidate: isRevalidationRequired,
|
|
3406
3450
|
});
|
|
3451
|
+
}
|
|
3407
3452
|
|
|
3408
3453
|
if (shouldRevalidate) {
|
|
3409
3454
|
revalidatingFetchers.push({
|
|
@@ -4090,11 +4135,13 @@ function getInternalRouterError(
|
|
|
4090
4135
|
}
|
|
4091
4136
|
|
|
4092
4137
|
// Find any returned redirect errors, starting from the lowest match
|
|
4093
|
-
function findRedirect(
|
|
4138
|
+
function findRedirect(
|
|
4139
|
+
results: DataResult[]
|
|
4140
|
+
): { result: RedirectResult; idx: number } | undefined {
|
|
4094
4141
|
for (let i = results.length - 1; i >= 0; i--) {
|
|
4095
4142
|
let result = results[i];
|
|
4096
4143
|
if (isRedirectResult(result)) {
|
|
4097
|
-
return result;
|
|
4144
|
+
return { result, idx: i };
|
|
4098
4145
|
}
|
|
4099
4146
|
}
|
|
4100
4147
|
}
|
package/utils.ts
CHANGED
|
@@ -239,12 +239,19 @@ export const immutableRouteKeys = new Set<ImmutableRouteKey>([
|
|
|
239
239
|
"children",
|
|
240
240
|
]);
|
|
241
241
|
|
|
242
|
+
type RequireOne<T, Key = keyof T> = Exclude<
|
|
243
|
+
{
|
|
244
|
+
[K in keyof T]: K extends Key ? Omit<T, K> & Required<Pick<T, K>> : never;
|
|
245
|
+
}[keyof T],
|
|
246
|
+
undefined
|
|
247
|
+
>;
|
|
248
|
+
|
|
242
249
|
/**
|
|
243
250
|
* lazy() function to load a route definition, which can add non-matching
|
|
244
251
|
* related properties to a route
|
|
245
252
|
*/
|
|
246
253
|
export interface LazyRouteFunction<R extends AgnosticRouteObject> {
|
|
247
|
-
(): Promise<Omit<R, ImmutableRouteKey
|
|
254
|
+
(): Promise<RequireOne<Omit<R, ImmutableRouteKey>>>;
|
|
248
255
|
}
|
|
249
256
|
|
|
250
257
|
/**
|
|
@@ -1310,7 +1317,7 @@ export class DeferredData {
|
|
|
1310
1317
|
// We store a little wrapper promise that will be extended with
|
|
1311
1318
|
// _data/_error props upon resolve/reject
|
|
1312
1319
|
let promise: TrackedPromise = Promise.race([value, this.abortPromise]).then(
|
|
1313
|
-
(data) => this.onSettle(promise, key,
|
|
1320
|
+
(data) => this.onSettle(promise, key, undefined, data as unknown),
|
|
1314
1321
|
(error) => this.onSettle(promise, key, error as unknown)
|
|
1315
1322
|
);
|
|
1316
1323
|
|
|
@@ -1344,7 +1351,19 @@ export class DeferredData {
|
|
|
1344
1351
|
this.unlistenAbortSignal();
|
|
1345
1352
|
}
|
|
1346
1353
|
|
|
1347
|
-
|
|
1354
|
+
// If the promise was resolved/rejected with undefined, we'll throw an error as you
|
|
1355
|
+
// should always resolve with a value or null
|
|
1356
|
+
if (error === undefined && data === undefined) {
|
|
1357
|
+
let undefinedError = new Error(
|
|
1358
|
+
`Deferred data for key "${key}" resolved/rejected with \`undefined\`, ` +
|
|
1359
|
+
`you must resolve/reject with a value or \`null\`.`
|
|
1360
|
+
);
|
|
1361
|
+
Object.defineProperty(promise, "_error", { get: () => undefinedError });
|
|
1362
|
+
this.emit(false, key);
|
|
1363
|
+
return Promise.reject(undefinedError);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
if (data === undefined) {
|
|
1348
1367
|
Object.defineProperty(promise, "_error", { get: () => error });
|
|
1349
1368
|
this.emit(false, key);
|
|
1350
1369
|
return Promise.reject(error);
|