@remix-run/router 1.7.1 → 1.7.2-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/CHANGELOG.md +9 -0
- package/dist/router.cjs.js +103 -42
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.js +102 -42
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +103 -42
- 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 +82 -36
- 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
|
@@ -1591,7 +1591,15 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1591
1591
|
// If any loaders returned a redirect Response, start a new REPLACE navigation
|
|
1592
1592
|
let redirect = findRedirect(results);
|
|
1593
1593
|
if (redirect) {
|
|
1594
|
-
|
|
1594
|
+
if (redirect.idx >= matchesToLoad.length) {
|
|
1595
|
+
// If this redirect came from a fetcher make sure we mark it in
|
|
1596
|
+
// fetchRedirectIds so it doesn't get revalidated on the next set of
|
|
1597
|
+
// loader executions
|
|
1598
|
+
let fetcherKey =
|
|
1599
|
+
revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
1600
|
+
fetchRedirectIds.add(fetcherKey);
|
|
1601
|
+
}
|
|
1602
|
+
await startRedirectNavigation(state, redirect.result, { replace });
|
|
1595
1603
|
return { shortCircuited: true };
|
|
1596
1604
|
}
|
|
1597
1605
|
|
|
@@ -1739,6 +1747,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1739
1747
|
);
|
|
1740
1748
|
fetchControllers.set(key, abortController);
|
|
1741
1749
|
|
|
1750
|
+
let originatingLoadId = incrementingLoadId;
|
|
1742
1751
|
let actionResult = await callLoaderOrAction(
|
|
1743
1752
|
"action",
|
|
1744
1753
|
fetchRequest,
|
|
@@ -1760,15 +1769,26 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1760
1769
|
|
|
1761
1770
|
if (isRedirectResult(actionResult)) {
|
|
1762
1771
|
fetchControllers.delete(key);
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
+
if (pendingNavigationLoadId > originatingLoadId) {
|
|
1773
|
+
// A new navigation was kicked off after our action started, so that
|
|
1774
|
+
// should take precedence over this redirect navigation. We already
|
|
1775
|
+
// set isRevalidationRequired so all loaders for the new route should
|
|
1776
|
+
// fire unless opted out via shouldRevalidate
|
|
1777
|
+
let doneFetcher = getDoneFetcher(undefined);
|
|
1778
|
+
state.fetchers.set(key, doneFetcher);
|
|
1779
|
+
updateState({ fetchers: new Map(state.fetchers) });
|
|
1780
|
+
return;
|
|
1781
|
+
} else {
|
|
1782
|
+
fetchRedirectIds.add(key);
|
|
1783
|
+
let loadingFetcher = getLoadingFetcher(submission);
|
|
1784
|
+
state.fetchers.set(key, loadingFetcher);
|
|
1785
|
+
updateState({ fetchers: new Map(state.fetchers) });
|
|
1786
|
+
|
|
1787
|
+
return startRedirectNavigation(state, actionResult, {
|
|
1788
|
+
submission,
|
|
1789
|
+
isFetchActionRedirect: true,
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1772
1792
|
}
|
|
1773
1793
|
|
|
1774
1794
|
// Process any non-redirect errors thrown
|
|
@@ -1875,7 +1895,15 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1875
1895
|
|
|
1876
1896
|
let redirect = findRedirect(results);
|
|
1877
1897
|
if (redirect) {
|
|
1878
|
-
|
|
1898
|
+
if (redirect.idx >= matchesToLoad.length) {
|
|
1899
|
+
// If this redirect came from a fetcher make sure we mark it in
|
|
1900
|
+
// fetchRedirectIds so it doesn't get revalidated on the next set of
|
|
1901
|
+
// loader executions
|
|
1902
|
+
let fetcherKey =
|
|
1903
|
+
revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
1904
|
+
fetchRedirectIds.add(fetcherKey);
|
|
1905
|
+
}
|
|
1906
|
+
return startRedirectNavigation(state, redirect.result);
|
|
1879
1907
|
}
|
|
1880
1908
|
|
|
1881
1909
|
// Process and commit output from loaders
|
|
@@ -1962,6 +1990,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1962
1990
|
);
|
|
1963
1991
|
fetchControllers.set(key, abortController);
|
|
1964
1992
|
|
|
1993
|
+
let originatingLoadId = incrementingLoadId;
|
|
1965
1994
|
let result: DataResult = await callLoaderOrAction(
|
|
1966
1995
|
"loader",
|
|
1967
1996
|
fetchRequest,
|
|
@@ -1994,9 +2023,18 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1994
2023
|
|
|
1995
2024
|
// If the loader threw a redirect Response, start a new REPLACE navigation
|
|
1996
2025
|
if (isRedirectResult(result)) {
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2026
|
+
if (pendingNavigationLoadId > originatingLoadId) {
|
|
2027
|
+
// A new navigation was kicked off after our loader started, so that
|
|
2028
|
+
// should take precedence over this redirect navigation
|
|
2029
|
+
let doneFetcher = getDoneFetcher(undefined);
|
|
2030
|
+
state.fetchers.set(key, doneFetcher);
|
|
2031
|
+
updateState({ fetchers: new Map(state.fetchers) });
|
|
2032
|
+
return;
|
|
2033
|
+
} else {
|
|
2034
|
+
fetchRedirectIds.add(key);
|
|
2035
|
+
await startRedirectNavigation(state, result);
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2000
2038
|
}
|
|
2001
2039
|
|
|
2002
2040
|
// Process any non-redirect errors thrown
|
|
@@ -3360,7 +3398,9 @@ function getMatchesToLoad(
|
|
|
3360
3398
|
let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
|
|
3361
3399
|
|
|
3362
3400
|
// If the fetcher path no longer matches, push it in with null matches so
|
|
3363
|
-
// we can trigger a 404 in callLoadersAndMaybeResolveData
|
|
3401
|
+
// we can trigger a 404 in callLoadersAndMaybeResolveData. Note this is
|
|
3402
|
+
// currently only a use-case for Remix HMR where the route tree can change
|
|
3403
|
+
// at runtime and remove a route previously loaded via a fetcher
|
|
3364
3404
|
if (!fetcherMatches) {
|
|
3365
3405
|
revalidatingFetchers.push({
|
|
3366
3406
|
key,
|
|
@@ -3374,28 +3414,31 @@ function getMatchesToLoad(
|
|
|
3374
3414
|
}
|
|
3375
3415
|
|
|
3376
3416
|
// 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
|
|
3417
|
+
// load from a static href. They revalidate based on explicit revalidation
|
|
3418
|
+
// (submission, useRevalidator, or X-Remix-Revalidate)
|
|
3386
3419
|
let fetcher = state.fetchers.get(key);
|
|
3387
|
-
let
|
|
3420
|
+
let fetcherMatch = getTargetMatch(fetcherMatches, f.path);
|
|
3421
|
+
|
|
3422
|
+
let shouldRevalidate = false;
|
|
3423
|
+
if (fetchRedirectIds.has(key)) {
|
|
3424
|
+
// Never trigger a revalidation of an actively redirecting fetcher
|
|
3425
|
+
shouldRevalidate = false;
|
|
3426
|
+
} else if (cancelledFetcherLoads.includes(key)) {
|
|
3427
|
+
// Always revalidate if the fetcher was cancelled
|
|
3428
|
+
shouldRevalidate = true;
|
|
3429
|
+
} else if (
|
|
3388
3430
|
fetcher &&
|
|
3389
3431
|
fetcher.state !== "idle" &&
|
|
3390
|
-
fetcher.data === undefined
|
|
3391
|
-
|
|
3392
|
-
//
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3432
|
+
fetcher.data === undefined
|
|
3433
|
+
) {
|
|
3434
|
+
// If the fetcher hasn't ever completed loading yet, then this isn't a
|
|
3435
|
+
// revalidation, it would just be a brand new load if an explicit
|
|
3436
|
+
// revalidation is required
|
|
3437
|
+
shouldRevalidate = isRevalidationRequired;
|
|
3438
|
+
} else {
|
|
3439
|
+
// Otherwise fall back on any user-defined shouldRevalidate, defaulting
|
|
3440
|
+
// to explicit revalidations only
|
|
3441
|
+
shouldRevalidate = shouldRevalidateLoader(fetcherMatch, {
|
|
3399
3442
|
currentUrl,
|
|
3400
3443
|
currentParams: state.matches[state.matches.length - 1].params,
|
|
3401
3444
|
nextUrl,
|
|
@@ -3404,6 +3447,7 @@ function getMatchesToLoad(
|
|
|
3404
3447
|
actionResult,
|
|
3405
3448
|
defaultShouldRevalidate: isRevalidationRequired,
|
|
3406
3449
|
});
|
|
3450
|
+
}
|
|
3407
3451
|
|
|
3408
3452
|
if (shouldRevalidate) {
|
|
3409
3453
|
revalidatingFetchers.push({
|
|
@@ -4090,11 +4134,13 @@ function getInternalRouterError(
|
|
|
4090
4134
|
}
|
|
4091
4135
|
|
|
4092
4136
|
// Find any returned redirect errors, starting from the lowest match
|
|
4093
|
-
function findRedirect(
|
|
4137
|
+
function findRedirect(
|
|
4138
|
+
results: DataResult[]
|
|
4139
|
+
): { result: RedirectResult; idx: number } | undefined {
|
|
4094
4140
|
for (let i = results.length - 1; i >= 0; i--) {
|
|
4095
4141
|
let result = results[i];
|
|
4096
4142
|
if (isRedirectResult(result)) {
|
|
4097
|
-
return result;
|
|
4143
|
+
return { result, idx: i };
|
|
4098
4144
|
}
|
|
4099
4145
|
}
|
|
4100
4146
|
}
|
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);
|