@remix-run/router 1.5.0 → 1.6.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 +22 -0
- package/LICENSE.md +3 -2
- package/README.md +34 -6
- package/dist/router.cjs.js +214 -100
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +22 -12
- package/dist/router.js +214 -100
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +214 -100
- 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 +20 -3
- package/package.json +1 -1
- package/router.ts +301 -132
- package/utils.ts +25 -6
package/router.ts
CHANGED
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
V7_FormMethod,
|
|
33
33
|
HTMLFormMethod,
|
|
34
34
|
MutationFormMethod,
|
|
35
|
+
MapRoutePropertiesFunction,
|
|
35
36
|
} from "./utils";
|
|
36
37
|
import {
|
|
37
38
|
ErrorResponse,
|
|
@@ -129,7 +130,7 @@ export interface Router {
|
|
|
129
130
|
* @param to Path to navigate to
|
|
130
131
|
* @param opts Navigation options (method, submission, etc.)
|
|
131
132
|
*/
|
|
132
|
-
navigate(to: To, opts?: RouterNavigateOptions): Promise<void>;
|
|
133
|
+
navigate(to: To | null, opts?: RouterNavigateOptions): Promise<void>;
|
|
133
134
|
|
|
134
135
|
/**
|
|
135
136
|
* @internal
|
|
@@ -145,7 +146,7 @@ export interface Router {
|
|
|
145
146
|
fetch(
|
|
146
147
|
key: string,
|
|
147
148
|
routeId: string,
|
|
148
|
-
href: string,
|
|
149
|
+
href: string | null,
|
|
149
150
|
opts?: RouterNavigateOptions
|
|
150
151
|
): void;
|
|
151
152
|
|
|
@@ -334,6 +335,7 @@ export type HydrationState = Partial<
|
|
|
334
335
|
*/
|
|
335
336
|
export interface FutureConfig {
|
|
336
337
|
v7_normalizeFormMethod: boolean;
|
|
338
|
+
v7_prependBasename: boolean;
|
|
337
339
|
}
|
|
338
340
|
|
|
339
341
|
/**
|
|
@@ -343,8 +345,12 @@ export interface RouterInit {
|
|
|
343
345
|
routes: AgnosticRouteObject[];
|
|
344
346
|
history: History;
|
|
345
347
|
basename?: string;
|
|
348
|
+
/**
|
|
349
|
+
* @deprecated Use `mapRouteProperties` instead
|
|
350
|
+
*/
|
|
346
351
|
detectErrorBoundary?: DetectErrorBoundaryFunction;
|
|
347
|
-
|
|
352
|
+
mapRouteProperties?: MapRoutePropertiesFunction;
|
|
353
|
+
future?: Partial<FutureConfig>;
|
|
348
354
|
hydrationData?: HydrationState;
|
|
349
355
|
}
|
|
350
356
|
|
|
@@ -410,22 +416,25 @@ export interface GetScrollPositionFunction {
|
|
|
410
416
|
(): number;
|
|
411
417
|
}
|
|
412
418
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
type LinkNavigateOptions = {
|
|
419
|
+
export type RelativeRoutingType = "route" | "path";
|
|
420
|
+
|
|
421
|
+
type BaseNavigateOptions = {
|
|
417
422
|
replace?: boolean;
|
|
418
423
|
state?: any;
|
|
419
424
|
preventScrollReset?: boolean;
|
|
425
|
+
relative?: RelativeRoutingType;
|
|
426
|
+
fromRouteId?: string;
|
|
420
427
|
};
|
|
421
428
|
|
|
429
|
+
/**
|
|
430
|
+
* Options for a navigate() call for a Link navigation
|
|
431
|
+
*/
|
|
432
|
+
type LinkNavigateOptions = BaseNavigateOptions;
|
|
433
|
+
|
|
422
434
|
/**
|
|
423
435
|
* Options for a navigate() call for a Form navigation
|
|
424
436
|
*/
|
|
425
|
-
type SubmissionNavigateOptions = {
|
|
426
|
-
replace?: boolean;
|
|
427
|
-
state?: any;
|
|
428
|
-
preventScrollReset?: boolean;
|
|
437
|
+
type SubmissionNavigateOptions = BaseNavigateOptions & {
|
|
429
438
|
formMethod?: HTMLFormMethod;
|
|
430
439
|
formEncType?: FormEncType;
|
|
431
440
|
formData: FormData;
|
|
@@ -593,6 +602,7 @@ interface RevalidatingFetcher extends FetchLoadMatch {
|
|
|
593
602
|
key: string;
|
|
594
603
|
match: AgnosticDataRouteMatch | null;
|
|
595
604
|
matches: AgnosticDataRouteMatch[] | null;
|
|
605
|
+
controller: AbortController | null;
|
|
596
606
|
}
|
|
597
607
|
|
|
598
608
|
/**
|
|
@@ -657,8 +667,10 @@ const isBrowser =
|
|
|
657
667
|
typeof window.document.createElement !== "undefined";
|
|
658
668
|
const isServer = !isBrowser;
|
|
659
669
|
|
|
660
|
-
const
|
|
661
|
-
Boolean(route.hasErrorBoundary)
|
|
670
|
+
const defaultMapRouteProperties: MapRoutePropertiesFunction = (route) => ({
|
|
671
|
+
hasErrorBoundary: Boolean(route.hasErrorBoundary),
|
|
672
|
+
});
|
|
673
|
+
|
|
662
674
|
//#endregion
|
|
663
675
|
|
|
664
676
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -674,22 +686,34 @@ export function createRouter(init: RouterInit): Router {
|
|
|
674
686
|
"You must provide a non-empty routes array to createRouter"
|
|
675
687
|
);
|
|
676
688
|
|
|
677
|
-
let
|
|
678
|
-
|
|
689
|
+
let mapRouteProperties: MapRoutePropertiesFunction;
|
|
690
|
+
if (init.mapRouteProperties) {
|
|
691
|
+
mapRouteProperties = init.mapRouteProperties;
|
|
692
|
+
} else if (init.detectErrorBoundary) {
|
|
693
|
+
// If they are still using the deprecated version, wrap it with the new API
|
|
694
|
+
let detectErrorBoundary = init.detectErrorBoundary;
|
|
695
|
+
mapRouteProperties = (route) => ({
|
|
696
|
+
hasErrorBoundary: detectErrorBoundary(route),
|
|
697
|
+
});
|
|
698
|
+
} else {
|
|
699
|
+
mapRouteProperties = defaultMapRouteProperties;
|
|
700
|
+
}
|
|
679
701
|
|
|
680
702
|
// Routes keyed by ID
|
|
681
703
|
let manifest: RouteManifest = {};
|
|
682
704
|
// Routes in tree format for matching
|
|
683
705
|
let dataRoutes = convertRoutesToDataRoutes(
|
|
684
706
|
init.routes,
|
|
685
|
-
|
|
707
|
+
mapRouteProperties,
|
|
686
708
|
undefined,
|
|
687
709
|
manifest
|
|
688
710
|
);
|
|
689
711
|
let inFlightDataRoutes: AgnosticDataRouteObject[] | undefined;
|
|
712
|
+
let basename = init.basename || "/";
|
|
690
713
|
// Config driven behavior flags
|
|
691
714
|
let future: FutureConfig = {
|
|
692
715
|
v7_normalizeFormMethod: false,
|
|
716
|
+
v7_prependBasename: false,
|
|
693
717
|
...init.future,
|
|
694
718
|
};
|
|
695
719
|
// Cleanup function for history
|
|
@@ -710,11 +734,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
710
734
|
// SSR did the initial scroll restoration.
|
|
711
735
|
let initialScrollRestored = init.hydrationData != null;
|
|
712
736
|
|
|
713
|
-
let initialMatches = matchRoutes(
|
|
714
|
-
dataRoutes,
|
|
715
|
-
init.history.location,
|
|
716
|
-
init.basename
|
|
717
|
-
);
|
|
737
|
+
let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);
|
|
718
738
|
let initialErrors: RouteData | null = null;
|
|
719
739
|
|
|
720
740
|
if (initialMatches == null) {
|
|
@@ -770,7 +790,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
770
790
|
|
|
771
791
|
// Use this internal flag to force revalidation of all loaders:
|
|
772
792
|
// - submissions (completed or interrupted)
|
|
773
|
-
// -
|
|
793
|
+
// - useRevalidator()
|
|
774
794
|
// - X-Remix-Revalidate (from redirect)
|
|
775
795
|
let isRevalidationRequired = false;
|
|
776
796
|
|
|
@@ -796,7 +816,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
796
816
|
// Fetchers that triggered data reloads as a result of their actions
|
|
797
817
|
let fetchReloadIds = new Map<string, number>();
|
|
798
818
|
|
|
799
|
-
// Fetchers that triggered redirect navigations
|
|
819
|
+
// Fetchers that triggered redirect navigations
|
|
800
820
|
let fetchRedirectIds = new Set<string>();
|
|
801
821
|
|
|
802
822
|
// Most recent href/match for fetcher.load calls for fetchers
|
|
@@ -1021,7 +1041,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1021
1041
|
// Trigger a navigation event, which can either be a numerical POP or a PUSH
|
|
1022
1042
|
// replace with an optional submission
|
|
1023
1043
|
async function navigate(
|
|
1024
|
-
to: number | To,
|
|
1044
|
+
to: number | To | null,
|
|
1025
1045
|
opts?: RouterNavigateOptions
|
|
1026
1046
|
): Promise<void> {
|
|
1027
1047
|
if (typeof to === "number") {
|
|
@@ -1029,9 +1049,19 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1029
1049
|
return;
|
|
1030
1050
|
}
|
|
1031
1051
|
|
|
1032
|
-
let
|
|
1052
|
+
let normalizedPath = normalizeTo(
|
|
1053
|
+
state.location,
|
|
1054
|
+
state.matches,
|
|
1055
|
+
basename,
|
|
1056
|
+
future.v7_prependBasename,
|
|
1033
1057
|
to,
|
|
1034
|
-
|
|
1058
|
+
opts?.fromRouteId,
|
|
1059
|
+
opts?.relative
|
|
1060
|
+
);
|
|
1061
|
+
let { path, submission, error } = normalizeNavigateOptions(
|
|
1062
|
+
future.v7_normalizeFormMethod,
|
|
1063
|
+
false,
|
|
1064
|
+
normalizedPath,
|
|
1035
1065
|
opts
|
|
1036
1066
|
);
|
|
1037
1067
|
|
|
@@ -1176,7 +1206,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1176
1206
|
|
|
1177
1207
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
1178
1208
|
let loadingNavigation = opts && opts.overrideNavigation;
|
|
1179
|
-
let matches = matchRoutes(routesToUse, location,
|
|
1209
|
+
let matches = matchRoutes(routesToUse, location, basename);
|
|
1180
1210
|
|
|
1181
1211
|
// Short circuit with a 404 on the root error boundary if we match nothing
|
|
1182
1212
|
if (!matches) {
|
|
@@ -1326,8 +1356,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1326
1356
|
actionMatch,
|
|
1327
1357
|
matches,
|
|
1328
1358
|
manifest,
|
|
1329
|
-
|
|
1330
|
-
|
|
1359
|
+
mapRouteProperties,
|
|
1360
|
+
basename
|
|
1331
1361
|
);
|
|
1332
1362
|
|
|
1333
1363
|
if (request.signal.aborted) {
|
|
@@ -1436,7 +1466,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1436
1466
|
cancelledFetcherLoads,
|
|
1437
1467
|
fetchLoadMatches,
|
|
1438
1468
|
routesToUse,
|
|
1439
|
-
|
|
1469
|
+
basename,
|
|
1440
1470
|
pendingActionData,
|
|
1441
1471
|
pendingError
|
|
1442
1472
|
);
|
|
@@ -1452,12 +1482,14 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1452
1482
|
|
|
1453
1483
|
// Short circuit if we have no loaders to run
|
|
1454
1484
|
if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {
|
|
1485
|
+
let updatedFetchers = markFetchRedirectsDone();
|
|
1455
1486
|
completeNavigation(location, {
|
|
1456
1487
|
matches,
|
|
1457
1488
|
loaderData: {},
|
|
1458
1489
|
// Commit pending error if we're short circuiting
|
|
1459
1490
|
errors: pendingError || null,
|
|
1460
1491
|
...(pendingActionData ? { actionData: pendingActionData } : {}),
|
|
1492
|
+
...(updatedFetchers ? { fetchers: new Map(state.fetchers) } : {}),
|
|
1461
1493
|
});
|
|
1462
1494
|
return { shortCircuited: true };
|
|
1463
1495
|
}
|
|
@@ -1495,9 +1527,24 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1495
1527
|
}
|
|
1496
1528
|
|
|
1497
1529
|
pendingNavigationLoadId = ++incrementingLoadId;
|
|
1498
|
-
revalidatingFetchers.forEach((rf) =>
|
|
1499
|
-
|
|
1500
|
-
|
|
1530
|
+
revalidatingFetchers.forEach((rf) => {
|
|
1531
|
+
if (rf.controller) {
|
|
1532
|
+
// Fetchers use an independent AbortController so that aborting a fetcher
|
|
1533
|
+
// (via deleteFetcher) does not abort the triggering navigation that
|
|
1534
|
+
// triggered the revalidation
|
|
1535
|
+
fetchControllers.set(rf.key, rf.controller);
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
|
|
1539
|
+
// Proxy navigation abort through to revalidation fetchers
|
|
1540
|
+
let abortPendingFetchRevalidations = () =>
|
|
1541
|
+
revalidatingFetchers.forEach((f) => abortFetcher(f.key));
|
|
1542
|
+
if (pendingNavigationController) {
|
|
1543
|
+
pendingNavigationController.signal.addEventListener(
|
|
1544
|
+
"abort",
|
|
1545
|
+
abortPendingFetchRevalidations
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1501
1548
|
|
|
1502
1549
|
let { results, loaderResults, fetcherResults } =
|
|
1503
1550
|
await callLoadersAndMaybeResolveData(
|
|
@@ -1515,6 +1562,12 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1515
1562
|
// Clean up _after_ loaders have completed. Don't clean up if we short
|
|
1516
1563
|
// circuited because fetchControllers would have been aborted and
|
|
1517
1564
|
// reassigned to new controllers for the next navigation
|
|
1565
|
+
if (pendingNavigationController) {
|
|
1566
|
+
pendingNavigationController.signal.removeEventListener(
|
|
1567
|
+
"abort",
|
|
1568
|
+
abortPendingFetchRevalidations
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1518
1571
|
revalidatingFetchers.forEach((rf) => fetchControllers.delete(rf.key));
|
|
1519
1572
|
|
|
1520
1573
|
// If any loaders returned a redirect Response, start a new REPLACE navigation
|
|
@@ -1548,15 +1601,15 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1548
1601
|
});
|
|
1549
1602
|
});
|
|
1550
1603
|
|
|
1551
|
-
markFetchRedirectsDone();
|
|
1604
|
+
let updatedFetchers = markFetchRedirectsDone();
|
|
1552
1605
|
let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
|
|
1606
|
+
let shouldUpdateFetchers =
|
|
1607
|
+
updatedFetchers || didAbortFetchLoads || revalidatingFetchers.length > 0;
|
|
1553
1608
|
|
|
1554
1609
|
return {
|
|
1555
1610
|
loaderData,
|
|
1556
1611
|
errors,
|
|
1557
|
-
...(
|
|
1558
|
-
? { fetchers: new Map(state.fetchers) }
|
|
1559
|
-
: {}),
|
|
1612
|
+
...(shouldUpdateFetchers ? { fetchers: new Map(state.fetchers) } : {}),
|
|
1560
1613
|
};
|
|
1561
1614
|
}
|
|
1562
1615
|
|
|
@@ -1568,7 +1621,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1568
1621
|
function fetch(
|
|
1569
1622
|
key: string,
|
|
1570
1623
|
routeId: string,
|
|
1571
|
-
href: string,
|
|
1624
|
+
href: string | null,
|
|
1572
1625
|
opts?: RouterFetchOptions
|
|
1573
1626
|
) {
|
|
1574
1627
|
if (isServer) {
|
|
@@ -1582,21 +1635,31 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1582
1635
|
if (fetchControllers.has(key)) abortFetcher(key);
|
|
1583
1636
|
|
|
1584
1637
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
1585
|
-
let
|
|
1638
|
+
let normalizedPath = normalizeTo(
|
|
1639
|
+
state.location,
|
|
1640
|
+
state.matches,
|
|
1641
|
+
basename,
|
|
1642
|
+
future.v7_prependBasename,
|
|
1643
|
+
href,
|
|
1644
|
+
routeId,
|
|
1645
|
+
opts?.relative
|
|
1646
|
+
);
|
|
1647
|
+
let matches = matchRoutes(routesToUse, normalizedPath, basename);
|
|
1648
|
+
|
|
1586
1649
|
if (!matches) {
|
|
1587
1650
|
setFetcherError(
|
|
1588
1651
|
key,
|
|
1589
1652
|
routeId,
|
|
1590
|
-
getInternalRouterError(404, { pathname:
|
|
1653
|
+
getInternalRouterError(404, { pathname: normalizedPath })
|
|
1591
1654
|
);
|
|
1592
1655
|
return;
|
|
1593
1656
|
}
|
|
1594
1657
|
|
|
1595
1658
|
let { path, submission } = normalizeNavigateOptions(
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1659
|
+
future.v7_normalizeFormMethod,
|
|
1660
|
+
true,
|
|
1661
|
+
normalizedPath,
|
|
1662
|
+
opts
|
|
1600
1663
|
);
|
|
1601
1664
|
let match = getTargetMatch(matches, path);
|
|
1602
1665
|
|
|
@@ -1663,8 +1726,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1663
1726
|
match,
|
|
1664
1727
|
requestMatches,
|
|
1665
1728
|
manifest,
|
|
1666
|
-
|
|
1667
|
-
|
|
1729
|
+
mapRouteProperties,
|
|
1730
|
+
basename
|
|
1668
1731
|
);
|
|
1669
1732
|
|
|
1670
1733
|
if (fetchRequest.signal.aborted) {
|
|
@@ -1716,7 +1779,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1716
1779
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
1717
1780
|
let matches =
|
|
1718
1781
|
state.navigation.state !== "idle"
|
|
1719
|
-
? matchRoutes(routesToUse, state.navigation.location,
|
|
1782
|
+
? matchRoutes(routesToUse, state.navigation.location, basename)
|
|
1720
1783
|
: state.matches;
|
|
1721
1784
|
|
|
1722
1785
|
invariant(matches, "Didn't find any matches after fetcher action");
|
|
@@ -1743,7 +1806,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1743
1806
|
cancelledFetcherLoads,
|
|
1744
1807
|
fetchLoadMatches,
|
|
1745
1808
|
routesToUse,
|
|
1746
|
-
|
|
1809
|
+
basename,
|
|
1747
1810
|
{ [match.route.id]: actionResult.data },
|
|
1748
1811
|
undefined // No need to send through errors since we short circuit above
|
|
1749
1812
|
);
|
|
@@ -1766,11 +1829,21 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1766
1829
|
" _hasFetcherDoneAnything ": true,
|
|
1767
1830
|
};
|
|
1768
1831
|
state.fetchers.set(staleKey, revalidatingFetcher);
|
|
1769
|
-
|
|
1832
|
+
if (rf.controller) {
|
|
1833
|
+
fetchControllers.set(staleKey, rf.controller);
|
|
1834
|
+
}
|
|
1770
1835
|
});
|
|
1771
1836
|
|
|
1772
1837
|
updateState({ fetchers: new Map(state.fetchers) });
|
|
1773
1838
|
|
|
1839
|
+
let abortPendingFetchRevalidations = () =>
|
|
1840
|
+
revalidatingFetchers.forEach((rf) => abortFetcher(rf.key));
|
|
1841
|
+
|
|
1842
|
+
abortController.signal.addEventListener(
|
|
1843
|
+
"abort",
|
|
1844
|
+
abortPendingFetchRevalidations
|
|
1845
|
+
);
|
|
1846
|
+
|
|
1774
1847
|
let { results, loaderResults, fetcherResults } =
|
|
1775
1848
|
await callLoadersAndMaybeResolveData(
|
|
1776
1849
|
state.matches,
|
|
@@ -1784,6 +1857,11 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1784
1857
|
return;
|
|
1785
1858
|
}
|
|
1786
1859
|
|
|
1860
|
+
abortController.signal.removeEventListener(
|
|
1861
|
+
"abort",
|
|
1862
|
+
abortPendingFetchRevalidations
|
|
1863
|
+
);
|
|
1864
|
+
|
|
1787
1865
|
fetchReloadIds.delete(key);
|
|
1788
1866
|
fetchControllers.delete(key);
|
|
1789
1867
|
revalidatingFetchers.forEach((r) => fetchControllers.delete(r.key));
|
|
@@ -1891,8 +1969,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1891
1969
|
match,
|
|
1892
1970
|
matches,
|
|
1893
1971
|
manifest,
|
|
1894
|
-
|
|
1895
|
-
|
|
1972
|
+
mapRouteProperties,
|
|
1973
|
+
basename
|
|
1896
1974
|
);
|
|
1897
1975
|
|
|
1898
1976
|
// Deferred isn't supported for fetcher loads, await everything and treat it
|
|
@@ -1905,7 +1983,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1905
1983
|
result;
|
|
1906
1984
|
}
|
|
1907
1985
|
|
|
1908
|
-
// We can delete this so long as we weren't aborted by
|
|
1986
|
+
// We can delete this so long as we weren't aborted by our our own fetcher
|
|
1909
1987
|
// re-load which would have put _new_ controller is in fetchControllers
|
|
1910
1988
|
if (fetchControllers.get(key) === abortController) {
|
|
1911
1989
|
fetchControllers.delete(key);
|
|
@@ -1917,6 +1995,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1917
1995
|
|
|
1918
1996
|
// If the loader threw a redirect Response, start a new REPLACE navigation
|
|
1919
1997
|
if (isRedirectResult(result)) {
|
|
1998
|
+
fetchRedirectIds.add(key);
|
|
1920
1999
|
await startRedirectNavigation(state, result);
|
|
1921
2000
|
return;
|
|
1922
2001
|
}
|
|
@@ -2009,8 +2088,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2009
2088
|
typeof window?.location !== "undefined"
|
|
2010
2089
|
) {
|
|
2011
2090
|
let url = init.history.createURL(redirect.location);
|
|
2012
|
-
let isDifferentBasename =
|
|
2013
|
-
stripBasename(url.pathname, init.basename || "/") == null;
|
|
2091
|
+
let isDifferentBasename = stripBasename(url.pathname, basename) == null;
|
|
2014
2092
|
|
|
2015
2093
|
if (window.location.origin !== url.origin || isDifferentBasename) {
|
|
2016
2094
|
if (replace) {
|
|
@@ -2109,20 +2187,20 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2109
2187
|
match,
|
|
2110
2188
|
matches,
|
|
2111
2189
|
manifest,
|
|
2112
|
-
|
|
2113
|
-
|
|
2190
|
+
mapRouteProperties,
|
|
2191
|
+
basename
|
|
2114
2192
|
)
|
|
2115
2193
|
),
|
|
2116
2194
|
...fetchersToLoad.map((f) => {
|
|
2117
|
-
if (f.matches && f.match) {
|
|
2195
|
+
if (f.matches && f.match && f.controller) {
|
|
2118
2196
|
return callLoaderOrAction(
|
|
2119
2197
|
"loader",
|
|
2120
|
-
createClientSideRequest(init.history, f.path,
|
|
2198
|
+
createClientSideRequest(init.history, f.path, f.controller.signal),
|
|
2121
2199
|
f.match,
|
|
2122
2200
|
f.matches,
|
|
2123
2201
|
manifest,
|
|
2124
|
-
|
|
2125
|
-
|
|
2202
|
+
mapRouteProperties,
|
|
2203
|
+
basename
|
|
2126
2204
|
);
|
|
2127
2205
|
} else {
|
|
2128
2206
|
let error: ErrorResult = {
|
|
@@ -2141,7 +2219,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2141
2219
|
currentMatches,
|
|
2142
2220
|
matchesToLoad,
|
|
2143
2221
|
loaderResults,
|
|
2144
|
-
request.signal,
|
|
2222
|
+
loaderResults.map(() => request.signal),
|
|
2145
2223
|
false,
|
|
2146
2224
|
state.loaderData
|
|
2147
2225
|
),
|
|
@@ -2149,7 +2227,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2149
2227
|
currentMatches,
|
|
2150
2228
|
fetchersToLoad.map((f) => f.match),
|
|
2151
2229
|
fetcherResults,
|
|
2152
|
-
|
|
2230
|
+
fetchersToLoad.map((f) => (f.controller ? f.controller.signal : null)),
|
|
2153
2231
|
true
|
|
2154
2232
|
),
|
|
2155
2233
|
]);
|
|
@@ -2216,17 +2294,20 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2216
2294
|
}
|
|
2217
2295
|
}
|
|
2218
2296
|
|
|
2219
|
-
function markFetchRedirectsDone():
|
|
2297
|
+
function markFetchRedirectsDone(): boolean {
|
|
2220
2298
|
let doneKeys = [];
|
|
2299
|
+
let updatedFetchers = false;
|
|
2221
2300
|
for (let key of fetchRedirectIds) {
|
|
2222
2301
|
let fetcher = state.fetchers.get(key);
|
|
2223
2302
|
invariant(fetcher, `Expected fetcher: ${key}`);
|
|
2224
2303
|
if (fetcher.state === "loading") {
|
|
2225
2304
|
fetchRedirectIds.delete(key);
|
|
2226
2305
|
doneKeys.push(key);
|
|
2306
|
+
updatedFetchers = true;
|
|
2227
2307
|
}
|
|
2228
2308
|
}
|
|
2229
2309
|
markFetchersDone(doneKeys);
|
|
2310
|
+
return updatedFetchers;
|
|
2230
2311
|
}
|
|
2231
2312
|
|
|
2232
2313
|
function abortStaleFetchLoads(landedId: number): boolean {
|
|
@@ -2398,7 +2479,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2398
2479
|
|
|
2399
2480
|
router = {
|
|
2400
2481
|
get basename() {
|
|
2401
|
-
return
|
|
2482
|
+
return basename;
|
|
2402
2483
|
},
|
|
2403
2484
|
get state() {
|
|
2404
2485
|
return state;
|
|
@@ -2440,7 +2521,11 @@ export const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
|
|
|
2440
2521
|
|
|
2441
2522
|
export interface CreateStaticHandlerOptions {
|
|
2442
2523
|
basename?: string;
|
|
2524
|
+
/**
|
|
2525
|
+
* @deprecated Use `mapRouteProperties` instead
|
|
2526
|
+
*/
|
|
2443
2527
|
detectErrorBoundary?: DetectErrorBoundaryFunction;
|
|
2528
|
+
mapRouteProperties?: MapRoutePropertiesFunction;
|
|
2444
2529
|
}
|
|
2445
2530
|
|
|
2446
2531
|
export function createStaticHandler(
|
|
@@ -2453,15 +2538,26 @@ export function createStaticHandler(
|
|
|
2453
2538
|
);
|
|
2454
2539
|
|
|
2455
2540
|
let manifest: RouteManifest = {};
|
|
2456
|
-
let
|
|
2457
|
-
|
|
2541
|
+
let basename = (opts ? opts.basename : null) || "/";
|
|
2542
|
+
let mapRouteProperties: MapRoutePropertiesFunction;
|
|
2543
|
+
if (opts?.mapRouteProperties) {
|
|
2544
|
+
mapRouteProperties = opts.mapRouteProperties;
|
|
2545
|
+
} else if (opts?.detectErrorBoundary) {
|
|
2546
|
+
// If they are still using the deprecated version, wrap it with the new API
|
|
2547
|
+
let detectErrorBoundary = opts.detectErrorBoundary;
|
|
2548
|
+
mapRouteProperties = (route) => ({
|
|
2549
|
+
hasErrorBoundary: detectErrorBoundary(route),
|
|
2550
|
+
});
|
|
2551
|
+
} else {
|
|
2552
|
+
mapRouteProperties = defaultMapRouteProperties;
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2458
2555
|
let dataRoutes = convertRoutesToDataRoutes(
|
|
2459
2556
|
routes,
|
|
2460
|
-
|
|
2557
|
+
mapRouteProperties,
|
|
2461
2558
|
undefined,
|
|
2462
2559
|
manifest
|
|
2463
2560
|
);
|
|
2464
|
-
let basename = (opts ? opts.basename : null) || "/";
|
|
2465
2561
|
|
|
2466
2562
|
/**
|
|
2467
2563
|
* The query() method is intended for document requests, in which we want to
|
|
@@ -2715,7 +2811,7 @@ export function createStaticHandler(
|
|
|
2715
2811
|
actionMatch,
|
|
2716
2812
|
matches,
|
|
2717
2813
|
manifest,
|
|
2718
|
-
|
|
2814
|
+
mapRouteProperties,
|
|
2719
2815
|
basename,
|
|
2720
2816
|
true,
|
|
2721
2817
|
isRouteRequest,
|
|
@@ -2883,7 +2979,7 @@ export function createStaticHandler(
|
|
|
2883
2979
|
match,
|
|
2884
2980
|
matches,
|
|
2885
2981
|
manifest,
|
|
2886
|
-
|
|
2982
|
+
mapRouteProperties,
|
|
2887
2983
|
basename,
|
|
2888
2984
|
true,
|
|
2889
2985
|
isRouteRequest,
|
|
@@ -2965,20 +3061,87 @@ function isSubmissionNavigation(
|
|
|
2965
3061
|
return opts != null && "formData" in opts;
|
|
2966
3062
|
}
|
|
2967
3063
|
|
|
3064
|
+
function normalizeTo(
|
|
3065
|
+
location: Path,
|
|
3066
|
+
matches: AgnosticDataRouteMatch[],
|
|
3067
|
+
basename: string,
|
|
3068
|
+
prependBasename: boolean,
|
|
3069
|
+
to: To | null,
|
|
3070
|
+
fromRouteId?: string,
|
|
3071
|
+
relative?: RelativeRoutingType
|
|
3072
|
+
) {
|
|
3073
|
+
let contextualMatches: AgnosticDataRouteMatch[];
|
|
3074
|
+
let activeRouteMatch: AgnosticDataRouteMatch | undefined;
|
|
3075
|
+
if (fromRouteId != null && relative !== "path") {
|
|
3076
|
+
// Grab matches up to the calling route so our route-relative logic is
|
|
3077
|
+
// relative to the correct source route. When using relative:path,
|
|
3078
|
+
// fromRouteId is ignored since that is always relative to the current
|
|
3079
|
+
// location path
|
|
3080
|
+
contextualMatches = [];
|
|
3081
|
+
for (let match of matches) {
|
|
3082
|
+
contextualMatches.push(match);
|
|
3083
|
+
if (match.route.id === fromRouteId) {
|
|
3084
|
+
activeRouteMatch = match;
|
|
3085
|
+
break;
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
} else {
|
|
3089
|
+
contextualMatches = matches;
|
|
3090
|
+
activeRouteMatch = matches[matches.length - 1];
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
// Resolve the relative path
|
|
3094
|
+
let path = resolveTo(
|
|
3095
|
+
to ? to : ".",
|
|
3096
|
+
getPathContributingMatches(contextualMatches).map((m) => m.pathnameBase),
|
|
3097
|
+
location.pathname,
|
|
3098
|
+
relative === "path"
|
|
3099
|
+
);
|
|
3100
|
+
|
|
3101
|
+
// When `to` is not specified we inherit search/hash from the current
|
|
3102
|
+
// location, unlike when to="." and we just inherit the path.
|
|
3103
|
+
// See https://github.com/remix-run/remix/issues/927
|
|
3104
|
+
if (to == null) {
|
|
3105
|
+
path.search = location.search;
|
|
3106
|
+
path.hash = location.hash;
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
// Add an ?index param for matched index routes if we don't already have one
|
|
3110
|
+
if (
|
|
3111
|
+
(to == null || to === "" || to === ".") &&
|
|
3112
|
+
activeRouteMatch &&
|
|
3113
|
+
activeRouteMatch.route.index &&
|
|
3114
|
+
!hasNakedIndexQuery(path.search)
|
|
3115
|
+
) {
|
|
3116
|
+
path.search = path.search
|
|
3117
|
+
? path.search.replace(/^\?/, "?index&")
|
|
3118
|
+
: "?index";
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
// If we're operating within a basename, prepend it to the pathname. If
|
|
3122
|
+
// this is a root navigation, then just use the raw basename which allows
|
|
3123
|
+
// the basename to have full control over the presence of a trailing slash
|
|
3124
|
+
// on root actions
|
|
3125
|
+
if (prependBasename && basename !== "/") {
|
|
3126
|
+
path.pathname =
|
|
3127
|
+
path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
return createPath(path);
|
|
3131
|
+
}
|
|
3132
|
+
|
|
2968
3133
|
// Normalize navigation options by converting formMethod=GET formData objects to
|
|
2969
3134
|
// URLSearchParams so they behave identically to links with query params
|
|
2970
3135
|
function normalizeNavigateOptions(
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
3136
|
+
normalizeFormMethod: boolean,
|
|
3137
|
+
isFetcher: boolean,
|
|
3138
|
+
path: string,
|
|
3139
|
+
opts?: RouterNavigateOptions
|
|
2975
3140
|
): {
|
|
2976
3141
|
path: string;
|
|
2977
3142
|
submission?: Submission;
|
|
2978
3143
|
error?: ErrorResponse;
|
|
2979
3144
|
} {
|
|
2980
|
-
let path = typeof to === "string" ? to : createPath(to);
|
|
2981
|
-
|
|
2982
3145
|
// Return location verbatim on non-submission navigations
|
|
2983
3146
|
if (!opts || !isSubmissionNavigation(opts)) {
|
|
2984
3147
|
return { path };
|
|
@@ -2996,7 +3159,7 @@ function normalizeNavigateOptions(
|
|
|
2996
3159
|
if (opts.formData) {
|
|
2997
3160
|
let formMethod = opts.formMethod || "get";
|
|
2998
3161
|
submission = {
|
|
2999
|
-
formMethod:
|
|
3162
|
+
formMethod: normalizeFormMethod
|
|
3000
3163
|
? (formMethod.toUpperCase() as V7_FormMethod)
|
|
3001
3164
|
: (formMethod.toLowerCase() as FormMethod),
|
|
3002
3165
|
formAction: stripHashFromPath(path),
|
|
@@ -3013,9 +3176,9 @@ function normalizeNavigateOptions(
|
|
|
3013
3176
|
// Flatten submission onto URLSearchParams for GET submissions
|
|
3014
3177
|
let parsedPath = parsePath(path);
|
|
3015
3178
|
let searchParams = convertFormDataToSearchParams(opts.formData);
|
|
3016
|
-
//
|
|
3017
|
-
//
|
|
3018
|
-
// any incoming ?index params
|
|
3179
|
+
// On GET navigation submissions we can drop the ?index param from the
|
|
3180
|
+
// resulting location since all loaders will run. But fetcher GET submissions
|
|
3181
|
+
// only run a single loader so we need to preserve any incoming ?index params
|
|
3019
3182
|
if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {
|
|
3020
3183
|
searchParams.append("index", "");
|
|
3021
3184
|
}
|
|
@@ -3064,14 +3227,6 @@ function getMatchesToLoad(
|
|
|
3064
3227
|
let currentUrl = history.createURL(state.location);
|
|
3065
3228
|
let nextUrl = history.createURL(location);
|
|
3066
3229
|
|
|
3067
|
-
let defaultShouldRevalidate =
|
|
3068
|
-
// Forced revalidation due to submission, useRevalidate, or X-Remix-Revalidate
|
|
3069
|
-
isRevalidationRequired ||
|
|
3070
|
-
// Clicked the same link, resubmitted a GET form
|
|
3071
|
-
currentUrl.toString() === nextUrl.toString() ||
|
|
3072
|
-
// Search params affect all loaders
|
|
3073
|
-
currentUrl.search !== nextUrl.search;
|
|
3074
|
-
|
|
3075
3230
|
// Pick navigation matches that are net-new or qualify for revalidation
|
|
3076
3231
|
let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
|
|
3077
3232
|
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
@@ -3108,7 +3263,12 @@ function getMatchesToLoad(
|
|
|
3108
3263
|
...submission,
|
|
3109
3264
|
actionResult,
|
|
3110
3265
|
defaultShouldRevalidate:
|
|
3111
|
-
|
|
3266
|
+
// Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
|
|
3267
|
+
isRevalidationRequired ||
|
|
3268
|
+
// Clicked the same link, resubmitted a GET form
|
|
3269
|
+
currentUrl.toString() === nextUrl.toString() ||
|
|
3270
|
+
// Search params affect all loaders
|
|
3271
|
+
currentUrl.search !== nextUrl.search ||
|
|
3112
3272
|
isNewRouteInstance(currentRouteMatch, nextRouteMatch),
|
|
3113
3273
|
});
|
|
3114
3274
|
});
|
|
@@ -3126,7 +3286,14 @@ function getMatchesToLoad(
|
|
|
3126
3286
|
// If the fetcher path no longer matches, push it in with null matches so
|
|
3127
3287
|
// we can trigger a 404 in callLoadersAndMaybeResolveData
|
|
3128
3288
|
if (!fetcherMatches) {
|
|
3129
|
-
revalidatingFetchers.push({
|
|
3289
|
+
revalidatingFetchers.push({
|
|
3290
|
+
key,
|
|
3291
|
+
routeId: f.routeId,
|
|
3292
|
+
path: f.path,
|
|
3293
|
+
matches: null,
|
|
3294
|
+
match: null,
|
|
3295
|
+
controller: null,
|
|
3296
|
+
});
|
|
3130
3297
|
return;
|
|
3131
3298
|
}
|
|
3132
3299
|
|
|
@@ -3135,9 +3302,11 @@ function getMatchesToLoad(
|
|
|
3135
3302
|
if (cancelledFetcherLoads.includes(key)) {
|
|
3136
3303
|
revalidatingFetchers.push({
|
|
3137
3304
|
key,
|
|
3305
|
+
routeId: f.routeId,
|
|
3306
|
+
path: f.path,
|
|
3138
3307
|
matches: fetcherMatches,
|
|
3139
3308
|
match: fetcherMatch,
|
|
3140
|
-
|
|
3309
|
+
controller: new AbortController(),
|
|
3141
3310
|
});
|
|
3142
3311
|
return;
|
|
3143
3312
|
}
|
|
@@ -3153,14 +3322,17 @@ function getMatchesToLoad(
|
|
|
3153
3322
|
nextParams: matches[matches.length - 1].params,
|
|
3154
3323
|
...submission,
|
|
3155
3324
|
actionResult,
|
|
3156
|
-
|
|
3325
|
+
// Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
|
|
3326
|
+
defaultShouldRevalidate: isRevalidationRequired,
|
|
3157
3327
|
});
|
|
3158
3328
|
if (shouldRevalidate) {
|
|
3159
3329
|
revalidatingFetchers.push({
|
|
3160
3330
|
key,
|
|
3331
|
+
routeId: f.routeId,
|
|
3332
|
+
path: f.path,
|
|
3161
3333
|
matches: fetcherMatches,
|
|
3162
3334
|
match: fetcherMatch,
|
|
3163
|
-
|
|
3335
|
+
controller: new AbortController(),
|
|
3164
3336
|
});
|
|
3165
3337
|
}
|
|
3166
3338
|
});
|
|
@@ -3224,7 +3396,7 @@ function shouldRevalidateLoader(
|
|
|
3224
3396
|
*/
|
|
3225
3397
|
async function loadLazyRouteModule(
|
|
3226
3398
|
route: AgnosticDataRouteObject,
|
|
3227
|
-
|
|
3399
|
+
mapRouteProperties: MapRoutePropertiesFunction,
|
|
3228
3400
|
manifest: RouteManifest
|
|
3229
3401
|
) {
|
|
3230
3402
|
if (!route.lazy) {
|
|
@@ -3279,7 +3451,7 @@ async function loadLazyRouteModule(
|
|
|
3279
3451
|
}
|
|
3280
3452
|
|
|
3281
3453
|
// Mutate the route with the provided updates. Do this first so we pass
|
|
3282
|
-
// the updated version to
|
|
3454
|
+
// the updated version to mapRouteProperties
|
|
3283
3455
|
Object.assign(routeToUpdate, routeUpdates);
|
|
3284
3456
|
|
|
3285
3457
|
// Mutate the `hasErrorBoundary` property on the route based on the route
|
|
@@ -3287,9 +3459,10 @@ async function loadLazyRouteModule(
|
|
|
3287
3459
|
// route again.
|
|
3288
3460
|
Object.assign(routeToUpdate, {
|
|
3289
3461
|
// To keep things framework agnostic, we use the provided
|
|
3290
|
-
// `
|
|
3291
|
-
//
|
|
3292
|
-
|
|
3462
|
+
// `mapRouteProperties` (or wrapped `detectErrorBoundary`) function to
|
|
3463
|
+
// set the framework-aware properties (`element`/`hasErrorBoundary`) since
|
|
3464
|
+
// the logic will differ between frameworks.
|
|
3465
|
+
...mapRouteProperties(routeToUpdate),
|
|
3293
3466
|
lazy: undefined,
|
|
3294
3467
|
});
|
|
3295
3468
|
}
|
|
@@ -3300,8 +3473,8 @@ async function callLoaderOrAction(
|
|
|
3300
3473
|
match: AgnosticDataRouteMatch,
|
|
3301
3474
|
matches: AgnosticDataRouteMatch[],
|
|
3302
3475
|
manifest: RouteManifest,
|
|
3303
|
-
|
|
3304
|
-
basename
|
|
3476
|
+
mapRouteProperties: MapRoutePropertiesFunction,
|
|
3477
|
+
basename: string,
|
|
3305
3478
|
isStaticRequest: boolean = false,
|
|
3306
3479
|
isRouteRequest: boolean = false,
|
|
3307
3480
|
requestContext?: unknown
|
|
@@ -3330,12 +3503,12 @@ async function callLoaderOrAction(
|
|
|
3330
3503
|
// Run statically defined handler in parallel with lazy()
|
|
3331
3504
|
let values = await Promise.all([
|
|
3332
3505
|
runHandler(handler),
|
|
3333
|
-
loadLazyRouteModule(match.route,
|
|
3506
|
+
loadLazyRouteModule(match.route, mapRouteProperties, manifest),
|
|
3334
3507
|
]);
|
|
3335
3508
|
result = values[0];
|
|
3336
3509
|
} else {
|
|
3337
3510
|
// Load lazy route module, then run any returned handler
|
|
3338
|
-
await loadLazyRouteModule(match.route,
|
|
3511
|
+
await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
|
|
3339
3512
|
|
|
3340
3513
|
handler = match.route[type];
|
|
3341
3514
|
if (handler) {
|
|
@@ -3344,9 +3517,11 @@ async function callLoaderOrAction(
|
|
|
3344
3517
|
// previously-lazy-loaded routes
|
|
3345
3518
|
result = await runHandler(handler);
|
|
3346
3519
|
} else if (type === "action") {
|
|
3520
|
+
let url = new URL(request.url);
|
|
3521
|
+
let pathname = url.pathname + url.search;
|
|
3347
3522
|
throw getInternalRouterError(405, {
|
|
3348
3523
|
method: request.method,
|
|
3349
|
-
pathname
|
|
3524
|
+
pathname,
|
|
3350
3525
|
routeId: match.route.id,
|
|
3351
3526
|
});
|
|
3352
3527
|
} else {
|
|
@@ -3355,12 +3530,13 @@ async function callLoaderOrAction(
|
|
|
3355
3530
|
return { type: ResultType.data, data: undefined };
|
|
3356
3531
|
}
|
|
3357
3532
|
}
|
|
3533
|
+
} else if (!handler) {
|
|
3534
|
+
let url = new URL(request.url);
|
|
3535
|
+
let pathname = url.pathname + url.search;
|
|
3536
|
+
throw getInternalRouterError(404, {
|
|
3537
|
+
pathname,
|
|
3538
|
+
});
|
|
3358
3539
|
} else {
|
|
3359
|
-
invariant<Function>(
|
|
3360
|
-
handler,
|
|
3361
|
-
`Could not find the ${type} to run on the "${match.route.id}" route`
|
|
3362
|
-
);
|
|
3363
|
-
|
|
3364
3540
|
result = await runHandler(handler);
|
|
3365
3541
|
}
|
|
3366
3542
|
|
|
@@ -3392,28 +3568,13 @@ async function callLoaderOrAction(
|
|
|
3392
3568
|
|
|
3393
3569
|
// Support relative routing in internal redirects
|
|
3394
3570
|
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
(match)
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
location
|
|
3401
|
-
routePathnames,
|
|
3402
|
-
new URL(request.url).pathname
|
|
3403
|
-
);
|
|
3404
|
-
invariant(
|
|
3405
|
-
createPath(resolvedLocation),
|
|
3406
|
-
`Unable to resolve redirect location: ${location}`
|
|
3571
|
+
location = normalizeTo(
|
|
3572
|
+
new URL(request.url),
|
|
3573
|
+
matches.slice(0, matches.indexOf(match) + 1),
|
|
3574
|
+
basename,
|
|
3575
|
+
true,
|
|
3576
|
+
location
|
|
3407
3577
|
);
|
|
3408
|
-
|
|
3409
|
-
// Prepend the basename to the redirect location if we have one
|
|
3410
|
-
if (basename) {
|
|
3411
|
-
let path = resolvedLocation.pathname;
|
|
3412
|
-
resolvedLocation.pathname =
|
|
3413
|
-
path === "/" ? basename : joinPaths([basename, path]);
|
|
3414
|
-
}
|
|
3415
|
-
|
|
3416
|
-
location = createPath(resolvedLocation);
|
|
3417
3578
|
} else if (!isStaticRequest) {
|
|
3418
3579
|
// Strip off the protocol+origin for same-origin + same-basename absolute
|
|
3419
3580
|
// redirects. If this is a static request, we can let it go back to the
|
|
@@ -3659,7 +3820,7 @@ function processLoaderData(
|
|
|
3659
3820
|
|
|
3660
3821
|
// Process results from our revalidating fetchers
|
|
3661
3822
|
for (let index = 0; index < revalidatingFetchers.length; index++) {
|
|
3662
|
-
let { key, match } = revalidatingFetchers[index];
|
|
3823
|
+
let { key, match, controller } = revalidatingFetchers[index];
|
|
3663
3824
|
invariant(
|
|
3664
3825
|
fetcherResults !== undefined && fetcherResults[index] !== undefined,
|
|
3665
3826
|
"Did not find corresponding fetcher result"
|
|
@@ -3667,7 +3828,10 @@ function processLoaderData(
|
|
|
3667
3828
|
let result = fetcherResults[index];
|
|
3668
3829
|
|
|
3669
3830
|
// Process fetcher non-redirect errors
|
|
3670
|
-
if (
|
|
3831
|
+
if (controller && controller.signal.aborted) {
|
|
3832
|
+
// Nothing to do for aborted fetchers
|
|
3833
|
+
continue;
|
|
3834
|
+
} else if (isErrorResult(result)) {
|
|
3671
3835
|
let boundaryMatch = findNearestBoundary(state.matches, match?.route.id);
|
|
3672
3836
|
if (!(errors && errors[boundaryMatch.route.id])) {
|
|
3673
3837
|
errors = {
|
|
@@ -3910,7 +4074,7 @@ async function resolveDeferredResults(
|
|
|
3910
4074
|
currentMatches: AgnosticDataRouteMatch[],
|
|
3911
4075
|
matchesToLoad: (AgnosticDataRouteMatch | null)[],
|
|
3912
4076
|
results: DataResult[],
|
|
3913
|
-
|
|
4077
|
+
signals: (AbortSignal | null)[],
|
|
3914
4078
|
isFetcher: boolean,
|
|
3915
4079
|
currentLoaderData?: RouteData
|
|
3916
4080
|
) {
|
|
@@ -3936,6 +4100,11 @@ async function resolveDeferredResults(
|
|
|
3936
4100
|
// Note: we do not have to touch activeDeferreds here since we race them
|
|
3937
4101
|
// against the signal in resolveDeferredData and they'll get aborted
|
|
3938
4102
|
// there if needed
|
|
4103
|
+
let signal = signals[index];
|
|
4104
|
+
invariant(
|
|
4105
|
+
signal,
|
|
4106
|
+
"Expected an AbortSignal for revalidating fetcher deferred result"
|
|
4107
|
+
);
|
|
3939
4108
|
await resolveDeferredData(result, signal, isFetcher).then((result) => {
|
|
3940
4109
|
if (result) {
|
|
3941
4110
|
results[index] = result || results[index];
|