@remix-run/router 1.16.1 → 1.17.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/index.d.ts +1 -1
- package/dist/router.cjs.js +467 -80
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +11 -1
- package/dist/router.js +457 -76
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +467 -80
- 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 +9 -1
- package/index.ts +1 -0
- package/package.json +1 -1
- package/router.ts +586 -73
- package/utils.ts +51 -8
package/router.ts
CHANGED
|
@@ -35,6 +35,7 @@ import type {
|
|
|
35
35
|
UIMatch,
|
|
36
36
|
V7_FormMethod,
|
|
37
37
|
V7_MutationFormMethod,
|
|
38
|
+
AgnosticPatchRoutesOnMissFunction,
|
|
38
39
|
} from "./utils";
|
|
39
40
|
import {
|
|
40
41
|
ErrorResponseImpl,
|
|
@@ -47,6 +48,7 @@ import {
|
|
|
47
48
|
isRouteErrorResponse,
|
|
48
49
|
joinPaths,
|
|
49
50
|
matchRoutes,
|
|
51
|
+
matchRoutesImpl,
|
|
50
52
|
resolveTo,
|
|
51
53
|
stripBasename,
|
|
52
54
|
} from "./utils";
|
|
@@ -242,6 +244,16 @@ export interface Router {
|
|
|
242
244
|
*/
|
|
243
245
|
deleteBlocker(key: string): void;
|
|
244
246
|
|
|
247
|
+
/**
|
|
248
|
+
* @internal
|
|
249
|
+
* PRIVATE DO NOT USE
|
|
250
|
+
*
|
|
251
|
+
* Patch additional children routes into an existing parent route
|
|
252
|
+
* @param routeId The parent route id
|
|
253
|
+
* @param children The additional children routes
|
|
254
|
+
*/
|
|
255
|
+
patchRoutes(routeId: string | null, children: AgnosticRouteObject[]): void;
|
|
256
|
+
|
|
245
257
|
/**
|
|
246
258
|
* @internal
|
|
247
259
|
* PRIVATE - DO NOT USE
|
|
@@ -377,6 +389,7 @@ export interface RouterInit {
|
|
|
377
389
|
future?: Partial<FutureConfig>;
|
|
378
390
|
hydrationData?: HydrationState;
|
|
379
391
|
window?: Window;
|
|
392
|
+
unstable_patchRoutesOnMiss?: AgnosticPatchRoutesOnMissFunction;
|
|
380
393
|
unstable_dataStrategy?: DataStrategyFunction;
|
|
381
394
|
}
|
|
382
395
|
|
|
@@ -631,6 +644,10 @@ interface ShortCircuitable {
|
|
|
631
644
|
type PendingActionResult = [string, SuccessResult | ErrorResult];
|
|
632
645
|
|
|
633
646
|
interface HandleActionResult extends ShortCircuitable {
|
|
647
|
+
/**
|
|
648
|
+
* Route matches which may have been updated from fog of war discovery
|
|
649
|
+
*/
|
|
650
|
+
matches?: RouterState["matches"];
|
|
634
651
|
/**
|
|
635
652
|
* Tuple for the returned or thrown value from the current action. The routeId
|
|
636
653
|
* is the action route for success and the bubbled boundary route for errors.
|
|
@@ -639,6 +656,10 @@ interface HandleActionResult extends ShortCircuitable {
|
|
|
639
656
|
}
|
|
640
657
|
|
|
641
658
|
interface HandleLoadersResult extends ShortCircuitable {
|
|
659
|
+
/**
|
|
660
|
+
* Route matches which may have been updated from fog of war discovery
|
|
661
|
+
*/
|
|
662
|
+
matches?: RouterState["matches"];
|
|
642
663
|
/**
|
|
643
664
|
* loaderData returned from the current set of loaders
|
|
644
665
|
*/
|
|
@@ -775,6 +796,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
775
796
|
let inFlightDataRoutes: AgnosticDataRouteObject[] | undefined;
|
|
776
797
|
let basename = init.basename || "/";
|
|
777
798
|
let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
|
|
799
|
+
let patchRoutesOnMissImpl = init.unstable_patchRoutesOnMiss;
|
|
800
|
+
|
|
778
801
|
// Config driven behavior flags
|
|
779
802
|
let future: FutureConfig = {
|
|
780
803
|
v7_fetcherPersist: false,
|
|
@@ -806,7 +829,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
806
829
|
let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);
|
|
807
830
|
let initialErrors: RouteData | null = null;
|
|
808
831
|
|
|
809
|
-
if (initialMatches == null) {
|
|
832
|
+
if (initialMatches == null && !patchRoutesOnMissImpl) {
|
|
810
833
|
// If we do not match a user-provided-route, fall back to the root
|
|
811
834
|
// to allow the error boundary to take over
|
|
812
835
|
let error = getInternalRouterError(404, {
|
|
@@ -818,13 +841,15 @@ export function createRouter(init: RouterInit): Router {
|
|
|
818
841
|
}
|
|
819
842
|
|
|
820
843
|
let initialized: boolean;
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
844
|
+
if (!initialMatches) {
|
|
845
|
+
// We need to run patchRoutesOnMiss in initialize()
|
|
846
|
+
initialized = false;
|
|
847
|
+
initialMatches = [];
|
|
848
|
+
} else if (initialMatches.some((m) => m.route.lazy)) {
|
|
824
849
|
// All initialMatches need to be loaded before we're ready. If we have lazy
|
|
825
850
|
// functions around still then we'll need to run them in initialize()
|
|
826
851
|
initialized = false;
|
|
827
|
-
} else if (!
|
|
852
|
+
} else if (!initialMatches.some((m) => m.route.loader)) {
|
|
828
853
|
// If we've got no loaders to run, then we're good to go
|
|
829
854
|
initialized = true;
|
|
830
855
|
} else if (future.v7_partialHydration) {
|
|
@@ -963,6 +988,13 @@ export function createRouter(init: RouterInit): Router {
|
|
|
963
988
|
// we don't need to update UI state if they change
|
|
964
989
|
let blockerFunctions = new Map<string, BlockerFunction>();
|
|
965
990
|
|
|
991
|
+
// Map of pending patchRoutesOnMiss() promises (keyed by path/matches) so
|
|
992
|
+
// that we only kick them off once for a given combo
|
|
993
|
+
let pendingPatchRoutes = new Map<
|
|
994
|
+
string,
|
|
995
|
+
ReturnType<AgnosticPatchRoutesOnMissFunction>
|
|
996
|
+
>();
|
|
997
|
+
|
|
966
998
|
// Flag to ignore the next history update, so we can revert the URL change on
|
|
967
999
|
// a POP navigation that was blocked by the user without touching router state
|
|
968
1000
|
let ignoreNextHistoryUpdate = false;
|
|
@@ -1455,13 +1487,16 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1455
1487
|
let matches = matchRoutes(routesToUse, location, basename);
|
|
1456
1488
|
let flushSync = (opts && opts.flushSync) === true;
|
|
1457
1489
|
|
|
1490
|
+
let fogOfWar = checkFogOfWar(matches, routesToUse, location.pathname);
|
|
1491
|
+
if (fogOfWar.active && fogOfWar.matches) {
|
|
1492
|
+
matches = fogOfWar.matches;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1458
1495
|
// Short circuit with a 404 on the root error boundary if we match nothing
|
|
1459
1496
|
if (!matches) {
|
|
1460
|
-
let error
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
// Cancel all pending deferred on 404s since we don't keep any routes
|
|
1464
|
-
cancelActiveDeferreds();
|
|
1497
|
+
let { error, notFoundMatches, route } = handleNavigational404(
|
|
1498
|
+
location.pathname
|
|
1499
|
+
);
|
|
1465
1500
|
completeNavigation(
|
|
1466
1501
|
location,
|
|
1467
1502
|
{
|
|
@@ -1522,6 +1557,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1522
1557
|
location,
|
|
1523
1558
|
opts.submission,
|
|
1524
1559
|
matches,
|
|
1560
|
+
fogOfWar.active,
|
|
1525
1561
|
{ replace: opts.replace, flushSync }
|
|
1526
1562
|
);
|
|
1527
1563
|
|
|
@@ -1529,9 +1565,34 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1529
1565
|
return;
|
|
1530
1566
|
}
|
|
1531
1567
|
|
|
1568
|
+
// If we received a 404 from handleAction, it's because we couldn't lazily
|
|
1569
|
+
// discover the destination route so we don't want to call loaders
|
|
1570
|
+
if (actionResult.pendingActionResult) {
|
|
1571
|
+
let [routeId, result] = actionResult.pendingActionResult;
|
|
1572
|
+
if (
|
|
1573
|
+
isErrorResult(result) &&
|
|
1574
|
+
isRouteErrorResponse(result.error) &&
|
|
1575
|
+
result.error.status === 404
|
|
1576
|
+
) {
|
|
1577
|
+
pendingNavigationController = null;
|
|
1578
|
+
|
|
1579
|
+
completeNavigation(location, {
|
|
1580
|
+
matches: actionResult.matches,
|
|
1581
|
+
loaderData: {},
|
|
1582
|
+
errors: {
|
|
1583
|
+
[routeId]: result.error,
|
|
1584
|
+
},
|
|
1585
|
+
});
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
matches = actionResult.matches || matches;
|
|
1532
1591
|
pendingActionResult = actionResult.pendingActionResult;
|
|
1533
1592
|
loadingNavigation = getLoadingNavigation(location, opts.submission);
|
|
1534
1593
|
flushSync = false;
|
|
1594
|
+
// No need to do fog of war matching again on loader execution
|
|
1595
|
+
fogOfWar.active = false;
|
|
1535
1596
|
|
|
1536
1597
|
// Create a GET request for the loaders
|
|
1537
1598
|
request = createClientSideRequest(
|
|
@@ -1542,10 +1603,16 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1542
1603
|
}
|
|
1543
1604
|
|
|
1544
1605
|
// Call loaders
|
|
1545
|
-
let {
|
|
1606
|
+
let {
|
|
1607
|
+
shortCircuited,
|
|
1608
|
+
matches: updatedMatches,
|
|
1609
|
+
loaderData,
|
|
1610
|
+
errors,
|
|
1611
|
+
} = await handleLoaders(
|
|
1546
1612
|
request,
|
|
1547
1613
|
location,
|
|
1548
1614
|
matches,
|
|
1615
|
+
fogOfWar.active,
|
|
1549
1616
|
loadingNavigation,
|
|
1550
1617
|
opts && opts.submission,
|
|
1551
1618
|
opts && opts.fetcherSubmission,
|
|
@@ -1565,7 +1632,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1565
1632
|
pendingNavigationController = null;
|
|
1566
1633
|
|
|
1567
1634
|
completeNavigation(location, {
|
|
1568
|
-
matches,
|
|
1635
|
+
matches: updatedMatches || matches,
|
|
1569
1636
|
...getActionDataForCommit(pendingActionResult),
|
|
1570
1637
|
loaderData,
|
|
1571
1638
|
errors,
|
|
@@ -1579,6 +1646,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1579
1646
|
location: Location,
|
|
1580
1647
|
submission: Submission,
|
|
1581
1648
|
matches: AgnosticDataRouteMatch[],
|
|
1649
|
+
isFogOfWar: boolean,
|
|
1582
1650
|
opts: { replace?: boolean; flushSync?: boolean } = {}
|
|
1583
1651
|
): Promise<HandleActionResult> {
|
|
1584
1652
|
interruptActiveLoads();
|
|
@@ -1587,6 +1655,48 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1587
1655
|
let navigation = getSubmittingNavigation(location, submission);
|
|
1588
1656
|
updateState({ navigation }, { flushSync: opts.flushSync === true });
|
|
1589
1657
|
|
|
1658
|
+
if (isFogOfWar) {
|
|
1659
|
+
let discoverResult = await discoverRoutes(
|
|
1660
|
+
matches,
|
|
1661
|
+
location.pathname,
|
|
1662
|
+
request.signal
|
|
1663
|
+
);
|
|
1664
|
+
if (discoverResult.type === "aborted") {
|
|
1665
|
+
return { shortCircuited: true };
|
|
1666
|
+
} else if (discoverResult.type === "error") {
|
|
1667
|
+
let { error, notFoundMatches, route } = handleDiscoverRouteError(
|
|
1668
|
+
location.pathname,
|
|
1669
|
+
discoverResult
|
|
1670
|
+
);
|
|
1671
|
+
return {
|
|
1672
|
+
matches: notFoundMatches,
|
|
1673
|
+
pendingActionResult: [
|
|
1674
|
+
route.id,
|
|
1675
|
+
{
|
|
1676
|
+
type: ResultType.error,
|
|
1677
|
+
error,
|
|
1678
|
+
},
|
|
1679
|
+
],
|
|
1680
|
+
};
|
|
1681
|
+
} else if (!discoverResult.matches) {
|
|
1682
|
+
let { notFoundMatches, error, route } = handleNavigational404(
|
|
1683
|
+
location.pathname
|
|
1684
|
+
);
|
|
1685
|
+
return {
|
|
1686
|
+
matches: notFoundMatches,
|
|
1687
|
+
pendingActionResult: [
|
|
1688
|
+
route.id,
|
|
1689
|
+
{
|
|
1690
|
+
type: ResultType.error,
|
|
1691
|
+
error,
|
|
1692
|
+
},
|
|
1693
|
+
],
|
|
1694
|
+
};
|
|
1695
|
+
} else {
|
|
1696
|
+
matches = discoverResult.matches;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1590
1700
|
// Call our action and get the result
|
|
1591
1701
|
let result: DataResult;
|
|
1592
1702
|
let actionMatch = getTargetMatch(matches, location);
|
|
@@ -1645,20 +1755,23 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1645
1755
|
// to call and will commit it when we complete the navigation
|
|
1646
1756
|
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
1647
1757
|
|
|
1648
|
-
// By default, all submissions
|
|
1649
|
-
// action threw an error that'll be rendered in
|
|
1650
|
-
// back to PUSH so that the user can use the
|
|
1651
|
-
// the pre-submission form location to try
|
|
1758
|
+
// By default, all submissions to the current location are REPLACE
|
|
1759
|
+
// navigations, but if the action threw an error that'll be rendered in
|
|
1760
|
+
// an errorElement, we fall back to PUSH so that the user can use the
|
|
1761
|
+
// back button to get back to the pre-submission form location to try
|
|
1762
|
+
// again
|
|
1652
1763
|
if ((opts && opts.replace) !== true) {
|
|
1653
1764
|
pendingAction = HistoryAction.Push;
|
|
1654
1765
|
}
|
|
1655
1766
|
|
|
1656
1767
|
return {
|
|
1768
|
+
matches,
|
|
1657
1769
|
pendingActionResult: [boundaryMatch.route.id, result],
|
|
1658
1770
|
};
|
|
1659
1771
|
}
|
|
1660
1772
|
|
|
1661
1773
|
return {
|
|
1774
|
+
matches,
|
|
1662
1775
|
pendingActionResult: [actionMatch.route.id, result],
|
|
1663
1776
|
};
|
|
1664
1777
|
}
|
|
@@ -1669,6 +1782,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1669
1782
|
request: Request,
|
|
1670
1783
|
location: Location,
|
|
1671
1784
|
matches: AgnosticDataRouteMatch[],
|
|
1785
|
+
isFogOfWar: boolean,
|
|
1672
1786
|
overrideNavigation?: Navigation,
|
|
1673
1787
|
submission?: Submission,
|
|
1674
1788
|
fetcherSubmission?: Submission,
|
|
@@ -1688,6 +1802,71 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1688
1802
|
fetcherSubmission ||
|
|
1689
1803
|
getSubmissionFromNavigation(loadingNavigation);
|
|
1690
1804
|
|
|
1805
|
+
// If this is an uninterrupted revalidation, we remain in our current idle
|
|
1806
|
+
// state. If not, we need to switch to our loading state and load data,
|
|
1807
|
+
// preserving any new action data or existing action data (in the case of
|
|
1808
|
+
// a revalidation interrupting an actionReload)
|
|
1809
|
+
// If we have partialHydration enabled, then don't update the state for the
|
|
1810
|
+
// initial data load since it's not a "navigation"
|
|
1811
|
+
let shouldUpdateNavigationState =
|
|
1812
|
+
!isUninterruptedRevalidation &&
|
|
1813
|
+
(!future.v7_partialHydration || !initialHydration);
|
|
1814
|
+
|
|
1815
|
+
// When fog of war is enabled, we enter our `loading` state earlier so we
|
|
1816
|
+
// can discover new routes during the `loading` state. We skip this if
|
|
1817
|
+
// we've already run actions since we would have done our matching already.
|
|
1818
|
+
// If the children() function threw then, we want to proceed with the
|
|
1819
|
+
// partial matches it discovered.
|
|
1820
|
+
if (isFogOfWar) {
|
|
1821
|
+
if (shouldUpdateNavigationState) {
|
|
1822
|
+
let actionData = getUpdatedActionData(pendingActionResult);
|
|
1823
|
+
updateState(
|
|
1824
|
+
{
|
|
1825
|
+
navigation: loadingNavigation,
|
|
1826
|
+
...(actionData !== undefined ? { actionData } : {}),
|
|
1827
|
+
},
|
|
1828
|
+
{
|
|
1829
|
+
flushSync,
|
|
1830
|
+
}
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
let discoverResult = await discoverRoutes(
|
|
1835
|
+
matches,
|
|
1836
|
+
location.pathname,
|
|
1837
|
+
request.signal
|
|
1838
|
+
);
|
|
1839
|
+
|
|
1840
|
+
if (discoverResult.type === "aborted") {
|
|
1841
|
+
return { shortCircuited: true };
|
|
1842
|
+
} else if (discoverResult.type === "error") {
|
|
1843
|
+
let { error, notFoundMatches, route } = handleDiscoverRouteError(
|
|
1844
|
+
location.pathname,
|
|
1845
|
+
discoverResult
|
|
1846
|
+
);
|
|
1847
|
+
return {
|
|
1848
|
+
matches: notFoundMatches,
|
|
1849
|
+
loaderData: {},
|
|
1850
|
+
errors: {
|
|
1851
|
+
[route.id]: error,
|
|
1852
|
+
},
|
|
1853
|
+
};
|
|
1854
|
+
} else if (!discoverResult.matches) {
|
|
1855
|
+
let { error, notFoundMatches, route } = handleNavigational404(
|
|
1856
|
+
location.pathname
|
|
1857
|
+
);
|
|
1858
|
+
return {
|
|
1859
|
+
matches: notFoundMatches,
|
|
1860
|
+
loaderData: {},
|
|
1861
|
+
errors: {
|
|
1862
|
+
[route.id]: error,
|
|
1863
|
+
},
|
|
1864
|
+
};
|
|
1865
|
+
} else {
|
|
1866
|
+
matches = discoverResult.matches;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1691
1870
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
1692
1871
|
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(
|
|
1693
1872
|
init.history,
|
|
@@ -1740,53 +1919,20 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1740
1919
|
return { shortCircuited: true };
|
|
1741
1920
|
}
|
|
1742
1921
|
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
(!future.v7_partialHydration || !initialHydration)
|
|
1752
|
-
) {
|
|
1753
|
-
revalidatingFetchers.forEach((rf) => {
|
|
1754
|
-
let fetcher = state.fetchers.get(rf.key);
|
|
1755
|
-
let revalidatingFetcher = getLoadingFetcher(
|
|
1756
|
-
undefined,
|
|
1757
|
-
fetcher ? fetcher.data : undefined
|
|
1758
|
-
);
|
|
1759
|
-
state.fetchers.set(rf.key, revalidatingFetcher);
|
|
1760
|
-
});
|
|
1761
|
-
|
|
1762
|
-
let actionData: Record<string, RouteData> | null | undefined;
|
|
1763
|
-
if (pendingActionResult && !isErrorResult(pendingActionResult[1])) {
|
|
1764
|
-
// This is cast to `any` currently because `RouteData`uses any and it
|
|
1765
|
-
// would be a breaking change to use any.
|
|
1766
|
-
// TODO: v7 - change `RouteData` to use `unknown` instead of `any`
|
|
1767
|
-
actionData = {
|
|
1768
|
-
[pendingActionResult[0]]: pendingActionResult[1].data as any,
|
|
1769
|
-
};
|
|
1770
|
-
} else if (state.actionData) {
|
|
1771
|
-
if (Object.keys(state.actionData).length === 0) {
|
|
1772
|
-
actionData = null;
|
|
1773
|
-
} else {
|
|
1774
|
-
actionData = state.actionData;
|
|
1922
|
+
if (shouldUpdateNavigationState) {
|
|
1923
|
+
let updates: Partial<RouterState> = {};
|
|
1924
|
+
if (!isFogOfWar) {
|
|
1925
|
+
// Only update navigation/actionNData if we didn't already do it above
|
|
1926
|
+
updates.navigation = loadingNavigation;
|
|
1927
|
+
let actionData = getUpdatedActionData(pendingActionResult);
|
|
1928
|
+
if (actionData !== undefined) {
|
|
1929
|
+
updates.actionData = actionData;
|
|
1775
1930
|
}
|
|
1776
1931
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
...(actionData !== undefined ? { actionData } : {}),
|
|
1782
|
-
...(revalidatingFetchers.length > 0
|
|
1783
|
-
? { fetchers: new Map(state.fetchers) }
|
|
1784
|
-
: {}),
|
|
1785
|
-
},
|
|
1786
|
-
{
|
|
1787
|
-
flushSync,
|
|
1788
|
-
}
|
|
1789
|
-
);
|
|
1932
|
+
if (revalidatingFetchers.length > 0) {
|
|
1933
|
+
updates.fetchers = getUpdatedRevalidatingFetchers(revalidatingFetchers);
|
|
1934
|
+
}
|
|
1935
|
+
updateState(updates, { flushSync });
|
|
1790
1936
|
}
|
|
1791
1937
|
|
|
1792
1938
|
revalidatingFetchers.forEach((rf) => {
|
|
@@ -1891,12 +2037,46 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1891
2037
|
updatedFetchers || didAbortFetchLoads || revalidatingFetchers.length > 0;
|
|
1892
2038
|
|
|
1893
2039
|
return {
|
|
2040
|
+
matches,
|
|
1894
2041
|
loaderData,
|
|
1895
2042
|
errors,
|
|
1896
2043
|
...(shouldUpdateFetchers ? { fetchers: new Map(state.fetchers) } : {}),
|
|
1897
2044
|
};
|
|
1898
2045
|
}
|
|
1899
2046
|
|
|
2047
|
+
function getUpdatedActionData(
|
|
2048
|
+
pendingActionResult: PendingActionResult | undefined
|
|
2049
|
+
): Record<string, RouteData> | null | undefined {
|
|
2050
|
+
if (pendingActionResult && !isErrorResult(pendingActionResult[1])) {
|
|
2051
|
+
// This is cast to `any` currently because `RouteData`uses any and it
|
|
2052
|
+
// would be a breaking change to use any.
|
|
2053
|
+
// TODO: v7 - change `RouteData` to use `unknown` instead of `any`
|
|
2054
|
+
return {
|
|
2055
|
+
[pendingActionResult[0]]: pendingActionResult[1].data as any,
|
|
2056
|
+
};
|
|
2057
|
+
} else if (state.actionData) {
|
|
2058
|
+
if (Object.keys(state.actionData).length === 0) {
|
|
2059
|
+
return null;
|
|
2060
|
+
} else {
|
|
2061
|
+
return state.actionData;
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
function getUpdatedRevalidatingFetchers(
|
|
2067
|
+
revalidatingFetchers: RevalidatingFetcher[]
|
|
2068
|
+
) {
|
|
2069
|
+
revalidatingFetchers.forEach((rf) => {
|
|
2070
|
+
let fetcher = state.fetchers.get(rf.key);
|
|
2071
|
+
let revalidatingFetcher = getLoadingFetcher(
|
|
2072
|
+
undefined,
|
|
2073
|
+
fetcher ? fetcher.data : undefined
|
|
2074
|
+
);
|
|
2075
|
+
state.fetchers.set(rf.key, revalidatingFetcher);
|
|
2076
|
+
});
|
|
2077
|
+
return new Map(state.fetchers);
|
|
2078
|
+
}
|
|
2079
|
+
|
|
1900
2080
|
// Trigger a fetcher load/submit for the given fetcher key
|
|
1901
2081
|
function fetch(
|
|
1902
2082
|
key: string,
|
|
@@ -1928,6 +2108,11 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1928
2108
|
);
|
|
1929
2109
|
let matches = matchRoutes(routesToUse, normalizedPath, basename);
|
|
1930
2110
|
|
|
2111
|
+
let fogOfWar = checkFogOfWar(matches, routesToUse, normalizedPath);
|
|
2112
|
+
if (fogOfWar.active && fogOfWar.matches) {
|
|
2113
|
+
matches = fogOfWar.matches;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
1931
2116
|
if (!matches) {
|
|
1932
2117
|
setFetcherError(
|
|
1933
2118
|
key,
|
|
@@ -1961,6 +2146,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1961
2146
|
path,
|
|
1962
2147
|
match,
|
|
1963
2148
|
matches,
|
|
2149
|
+
fogOfWar.active,
|
|
1964
2150
|
flushSync,
|
|
1965
2151
|
submission
|
|
1966
2152
|
);
|
|
@@ -1976,6 +2162,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1976
2162
|
path,
|
|
1977
2163
|
match,
|
|
1978
2164
|
matches,
|
|
2165
|
+
fogOfWar.active,
|
|
1979
2166
|
flushSync,
|
|
1980
2167
|
submission
|
|
1981
2168
|
);
|
|
@@ -1989,19 +2176,27 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1989
2176
|
path: string,
|
|
1990
2177
|
match: AgnosticDataRouteMatch,
|
|
1991
2178
|
requestMatches: AgnosticDataRouteMatch[],
|
|
2179
|
+
isFogOfWar: boolean,
|
|
1992
2180
|
flushSync: boolean,
|
|
1993
2181
|
submission: Submission
|
|
1994
2182
|
) {
|
|
1995
2183
|
interruptActiveLoads();
|
|
1996
2184
|
fetchLoadMatches.delete(key);
|
|
1997
2185
|
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2186
|
+
function detectAndHandle405Error(m: AgnosticDataRouteMatch) {
|
|
2187
|
+
if (!m.route.action && !m.route.lazy) {
|
|
2188
|
+
let error = getInternalRouterError(405, {
|
|
2189
|
+
method: submission.formMethod,
|
|
2190
|
+
pathname: path,
|
|
2191
|
+
routeId: routeId,
|
|
2192
|
+
});
|
|
2193
|
+
setFetcherError(key, routeId, error, { flushSync });
|
|
2194
|
+
return true;
|
|
2195
|
+
}
|
|
2196
|
+
return false;
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
if (!isFogOfWar && detectAndHandle405Error(match)) {
|
|
2005
2200
|
return;
|
|
2006
2201
|
}
|
|
2007
2202
|
|
|
@@ -2011,7 +2206,6 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2011
2206
|
flushSync,
|
|
2012
2207
|
});
|
|
2013
2208
|
|
|
2014
|
-
// Call the action for the fetcher
|
|
2015
2209
|
let abortController = new AbortController();
|
|
2016
2210
|
let fetchRequest = createClientSideRequest(
|
|
2017
2211
|
init.history,
|
|
@@ -2019,6 +2213,39 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2019
2213
|
abortController.signal,
|
|
2020
2214
|
submission
|
|
2021
2215
|
);
|
|
2216
|
+
|
|
2217
|
+
if (isFogOfWar) {
|
|
2218
|
+
let discoverResult = await discoverRoutes(
|
|
2219
|
+
requestMatches,
|
|
2220
|
+
path,
|
|
2221
|
+
fetchRequest.signal
|
|
2222
|
+
);
|
|
2223
|
+
|
|
2224
|
+
if (discoverResult.type === "aborted") {
|
|
2225
|
+
return;
|
|
2226
|
+
} else if (discoverResult.type === "error") {
|
|
2227
|
+
let { error } = handleDiscoverRouteError(path, discoverResult);
|
|
2228
|
+
setFetcherError(key, routeId, error, { flushSync });
|
|
2229
|
+
return;
|
|
2230
|
+
} else if (!discoverResult.matches) {
|
|
2231
|
+
setFetcherError(
|
|
2232
|
+
key,
|
|
2233
|
+
routeId,
|
|
2234
|
+
getInternalRouterError(404, { pathname: path }),
|
|
2235
|
+
{ flushSync }
|
|
2236
|
+
);
|
|
2237
|
+
return;
|
|
2238
|
+
} else {
|
|
2239
|
+
requestMatches = discoverResult.matches;
|
|
2240
|
+
match = getTargetMatch(requestMatches, path);
|
|
2241
|
+
|
|
2242
|
+
if (detectAndHandle405Error(match)) {
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
// Call the action for the fetcher
|
|
2022
2249
|
fetchControllers.set(key, abortController);
|
|
2023
2250
|
|
|
2024
2251
|
let originatingLoadId = incrementingLoadId;
|
|
@@ -2247,6 +2474,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2247
2474
|
path: string,
|
|
2248
2475
|
match: AgnosticDataRouteMatch,
|
|
2249
2476
|
matches: AgnosticDataRouteMatch[],
|
|
2477
|
+
isFogOfWar: boolean,
|
|
2250
2478
|
flushSync: boolean,
|
|
2251
2479
|
submission?: Submission
|
|
2252
2480
|
) {
|
|
@@ -2260,13 +2488,41 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2260
2488
|
{ flushSync }
|
|
2261
2489
|
);
|
|
2262
2490
|
|
|
2263
|
-
// Call the loader for this fetcher route match
|
|
2264
2491
|
let abortController = new AbortController();
|
|
2265
2492
|
let fetchRequest = createClientSideRequest(
|
|
2266
2493
|
init.history,
|
|
2267
2494
|
path,
|
|
2268
2495
|
abortController.signal
|
|
2269
2496
|
);
|
|
2497
|
+
|
|
2498
|
+
if (isFogOfWar) {
|
|
2499
|
+
let discoverResult = await discoverRoutes(
|
|
2500
|
+
matches,
|
|
2501
|
+
path,
|
|
2502
|
+
fetchRequest.signal
|
|
2503
|
+
);
|
|
2504
|
+
|
|
2505
|
+
if (discoverResult.type === "aborted") {
|
|
2506
|
+
return;
|
|
2507
|
+
} else if (discoverResult.type === "error") {
|
|
2508
|
+
let { error } = handleDiscoverRouteError(path, discoverResult);
|
|
2509
|
+
setFetcherError(key, routeId, error, { flushSync });
|
|
2510
|
+
return;
|
|
2511
|
+
} else if (!discoverResult.matches) {
|
|
2512
|
+
setFetcherError(
|
|
2513
|
+
key,
|
|
2514
|
+
routeId,
|
|
2515
|
+
getInternalRouterError(404, { pathname: path }),
|
|
2516
|
+
{ flushSync }
|
|
2517
|
+
);
|
|
2518
|
+
return;
|
|
2519
|
+
} else {
|
|
2520
|
+
matches = discoverResult.matches;
|
|
2521
|
+
match = getTargetMatch(matches, path);
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
// Call the loader for this fetcher route match
|
|
2270
2526
|
fetchControllers.set(key, abortController);
|
|
2271
2527
|
|
|
2272
2528
|
let originatingLoadId = incrementingLoadId;
|
|
@@ -2777,6 +3033,35 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2777
3033
|
}
|
|
2778
3034
|
}
|
|
2779
3035
|
|
|
3036
|
+
function handleNavigational404(pathname: string) {
|
|
3037
|
+
let error = getInternalRouterError(404, { pathname });
|
|
3038
|
+
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
3039
|
+
let { matches, route } = getShortCircuitMatches(routesToUse);
|
|
3040
|
+
|
|
3041
|
+
// Cancel all pending deferred on 404s since we don't keep any routes
|
|
3042
|
+
cancelActiveDeferreds();
|
|
3043
|
+
|
|
3044
|
+
return { notFoundMatches: matches, route, error };
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
function handleDiscoverRouteError(
|
|
3048
|
+
pathname: string,
|
|
3049
|
+
discoverResult: DiscoverRoutesErrorResult
|
|
3050
|
+
) {
|
|
3051
|
+
let matches = discoverResult.partialMatches;
|
|
3052
|
+
let route = matches[matches.length - 1].route;
|
|
3053
|
+
let error = getInternalRouterError(400, {
|
|
3054
|
+
type: "route-discovery",
|
|
3055
|
+
routeId: route.id,
|
|
3056
|
+
pathname,
|
|
3057
|
+
message:
|
|
3058
|
+
discoverResult.error != null && "message" in discoverResult.error
|
|
3059
|
+
? discoverResult.error
|
|
3060
|
+
: String(discoverResult.error),
|
|
3061
|
+
});
|
|
3062
|
+
return { notFoundMatches: matches, route, error };
|
|
3063
|
+
}
|
|
3064
|
+
|
|
2780
3065
|
function cancelActiveDeferreds(
|
|
2781
3066
|
predicate?: (routeId: string) => boolean
|
|
2782
3067
|
): string[] {
|
|
@@ -2858,6 +3143,137 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2858
3143
|
return null;
|
|
2859
3144
|
}
|
|
2860
3145
|
|
|
3146
|
+
function checkFogOfWar(
|
|
3147
|
+
matches: AgnosticDataRouteMatch[] | null,
|
|
3148
|
+
routesToUse: AgnosticDataRouteObject[],
|
|
3149
|
+
pathname: string
|
|
3150
|
+
): { active: boolean; matches: AgnosticDataRouteMatch[] | null } {
|
|
3151
|
+
if (patchRoutesOnMissImpl) {
|
|
3152
|
+
if (!matches) {
|
|
3153
|
+
let fogMatches = matchRoutesImpl<AgnosticDataRouteObject>(
|
|
3154
|
+
routesToUse,
|
|
3155
|
+
pathname,
|
|
3156
|
+
basename,
|
|
3157
|
+
true
|
|
3158
|
+
);
|
|
3159
|
+
|
|
3160
|
+
return { active: true, matches: fogMatches || [] };
|
|
3161
|
+
} else {
|
|
3162
|
+
let leafRoute = matches[matches.length - 1].route;
|
|
3163
|
+
if (leafRoute.path === "*") {
|
|
3164
|
+
// If we matched a splat, it might only be because we haven't yet fetched
|
|
3165
|
+
// the children that would match with a higher score, so let's fetch
|
|
3166
|
+
// around and find out
|
|
3167
|
+
let partialMatches = matchRoutesImpl<AgnosticDataRouteObject>(
|
|
3168
|
+
routesToUse,
|
|
3169
|
+
pathname,
|
|
3170
|
+
basename,
|
|
3171
|
+
true
|
|
3172
|
+
);
|
|
3173
|
+
return { active: true, matches: partialMatches };
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
return { active: false, matches: null };
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
type DiscoverRoutesSuccessResult = {
|
|
3182
|
+
type: "success";
|
|
3183
|
+
matches: AgnosticDataRouteMatch[] | null;
|
|
3184
|
+
};
|
|
3185
|
+
type DiscoverRoutesErrorResult = {
|
|
3186
|
+
type: "error";
|
|
3187
|
+
error: any;
|
|
3188
|
+
partialMatches: AgnosticDataRouteMatch[];
|
|
3189
|
+
};
|
|
3190
|
+
type DiscoverRoutesAbortedResult = { type: "aborted" };
|
|
3191
|
+
type DiscoverRoutesResult =
|
|
3192
|
+
| DiscoverRoutesSuccessResult
|
|
3193
|
+
| DiscoverRoutesErrorResult
|
|
3194
|
+
| DiscoverRoutesAbortedResult;
|
|
3195
|
+
|
|
3196
|
+
async function discoverRoutes(
|
|
3197
|
+
matches: AgnosticDataRouteMatch[],
|
|
3198
|
+
pathname: string,
|
|
3199
|
+
signal: AbortSignal
|
|
3200
|
+
): Promise<DiscoverRoutesResult> {
|
|
3201
|
+
let partialMatches: AgnosticDataRouteMatch[] | null = matches;
|
|
3202
|
+
let route =
|
|
3203
|
+
partialMatches.length > 0
|
|
3204
|
+
? partialMatches[partialMatches.length - 1].route
|
|
3205
|
+
: null;
|
|
3206
|
+
while (true) {
|
|
3207
|
+
try {
|
|
3208
|
+
await loadLazyRouteChildren(
|
|
3209
|
+
patchRoutesOnMissImpl!,
|
|
3210
|
+
pathname,
|
|
3211
|
+
partialMatches,
|
|
3212
|
+
dataRoutes || inFlightDataRoutes,
|
|
3213
|
+
manifest,
|
|
3214
|
+
mapRouteProperties,
|
|
3215
|
+
pendingPatchRoutes,
|
|
3216
|
+
signal
|
|
3217
|
+
);
|
|
3218
|
+
} catch (e) {
|
|
3219
|
+
return { type: "error", error: e, partialMatches };
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
if (signal.aborted) {
|
|
3223
|
+
return { type: "aborted" };
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
3227
|
+
let newMatches = matchRoutes(routesToUse, pathname, basename);
|
|
3228
|
+
let matchedSplat = false;
|
|
3229
|
+
if (newMatches) {
|
|
3230
|
+
let leafRoute = newMatches[newMatches.length - 1].route;
|
|
3231
|
+
|
|
3232
|
+
if (leafRoute.index) {
|
|
3233
|
+
// If we found an index route, we can stop
|
|
3234
|
+
return { type: "success", matches: newMatches };
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
if (leafRoute.path && leafRoute.path.length > 0) {
|
|
3238
|
+
if (leafRoute.path === "*") {
|
|
3239
|
+
// If we found a splat route, we can't be sure there's not a
|
|
3240
|
+
// higher-scoring route down some partial matches trail so we need
|
|
3241
|
+
// to check that out
|
|
3242
|
+
matchedSplat = true;
|
|
3243
|
+
} else {
|
|
3244
|
+
// If we found a non-splat route, we can stop
|
|
3245
|
+
return { type: "success", matches: newMatches };
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
let newPartialMatches = matchRoutesImpl<AgnosticDataRouteObject>(
|
|
3251
|
+
routesToUse,
|
|
3252
|
+
pathname,
|
|
3253
|
+
basename,
|
|
3254
|
+
true
|
|
3255
|
+
);
|
|
3256
|
+
|
|
3257
|
+
// If we are no longer partially matching anything, this was either a
|
|
3258
|
+
// legit splat match above, or it's a 404. Also avoid loops if the
|
|
3259
|
+
// second pass results in the same partial matches
|
|
3260
|
+
if (
|
|
3261
|
+
!newPartialMatches ||
|
|
3262
|
+
partialMatches.map((m) => m.route.id).join("-") ===
|
|
3263
|
+
newPartialMatches.map((m) => m.route.id).join("-")
|
|
3264
|
+
) {
|
|
3265
|
+
return { type: "success", matches: matchedSplat ? newMatches : null };
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
partialMatches = newPartialMatches;
|
|
3269
|
+
route = partialMatches[partialMatches.length - 1].route;
|
|
3270
|
+
if (route.path === "*") {
|
|
3271
|
+
// The splat is still our most accurate partial, so run with it
|
|
3272
|
+
return { type: "success", matches: partialMatches };
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
|
|
2861
3277
|
function _internalSetRoutes(newRoutes: AgnosticDataRouteObject[]) {
|
|
2862
3278
|
manifest = {};
|
|
2863
3279
|
inFlightDataRoutes = convertRoutesToDataRoutes(
|
|
@@ -2899,6 +3315,15 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2899
3315
|
dispose,
|
|
2900
3316
|
getBlocker,
|
|
2901
3317
|
deleteBlocker,
|
|
3318
|
+
patchRoutes(routeId, children) {
|
|
3319
|
+
return patchRoutes(
|
|
3320
|
+
routeId,
|
|
3321
|
+
children,
|
|
3322
|
+
dataRoutes || inFlightDataRoutes,
|
|
3323
|
+
manifest,
|
|
3324
|
+
mapRouteProperties
|
|
3325
|
+
);
|
|
3326
|
+
},
|
|
2902
3327
|
_internalFetchControllers: fetchControllers,
|
|
2903
3328
|
_internalActiveDeferreds: activeDeferreds,
|
|
2904
3329
|
// TODO: Remove setRoutes, it's temporary to avoid dealing with
|
|
@@ -4062,6 +4487,85 @@ function shouldRevalidateLoader(
|
|
|
4062
4487
|
return arg.defaultShouldRevalidate;
|
|
4063
4488
|
}
|
|
4064
4489
|
|
|
4490
|
+
/**
|
|
4491
|
+
* Idempotent utility to execute route.children() method to lazily load route
|
|
4492
|
+
* definitions and update the routes/routeManifest
|
|
4493
|
+
*/
|
|
4494
|
+
async function loadLazyRouteChildren(
|
|
4495
|
+
patchRoutesOnMissImpl: AgnosticPatchRoutesOnMissFunction,
|
|
4496
|
+
path: string,
|
|
4497
|
+
matches: AgnosticDataRouteMatch[],
|
|
4498
|
+
routes: AgnosticDataRouteObject[],
|
|
4499
|
+
manifest: RouteManifest,
|
|
4500
|
+
mapRouteProperties: MapRoutePropertiesFunction,
|
|
4501
|
+
pendingRouteChildren: Map<string, ReturnType<typeof patchRoutesOnMissImpl>>,
|
|
4502
|
+
signal: AbortSignal
|
|
4503
|
+
) {
|
|
4504
|
+
let key = [path, ...matches.map((m) => m.route.id)].join("-");
|
|
4505
|
+
try {
|
|
4506
|
+
let pending = pendingRouteChildren.get(key);
|
|
4507
|
+
if (!pending) {
|
|
4508
|
+
pending = patchRoutesOnMissImpl({
|
|
4509
|
+
path,
|
|
4510
|
+
matches,
|
|
4511
|
+
patch: (routeId, children) => {
|
|
4512
|
+
if (!signal.aborted) {
|
|
4513
|
+
patchRoutes(
|
|
4514
|
+
routeId,
|
|
4515
|
+
children,
|
|
4516
|
+
routes,
|
|
4517
|
+
manifest,
|
|
4518
|
+
mapRouteProperties
|
|
4519
|
+
);
|
|
4520
|
+
}
|
|
4521
|
+
},
|
|
4522
|
+
});
|
|
4523
|
+
pendingRouteChildren.set(key, pending);
|
|
4524
|
+
}
|
|
4525
|
+
|
|
4526
|
+
if (pending && isPromise<AgnosticRouteObject[]>(pending)) {
|
|
4527
|
+
await pending;
|
|
4528
|
+
}
|
|
4529
|
+
} finally {
|
|
4530
|
+
pendingRouteChildren.delete(key);
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
|
|
4534
|
+
function patchRoutes(
|
|
4535
|
+
routeId: string | null,
|
|
4536
|
+
children: AgnosticRouteObject[],
|
|
4537
|
+
routes: AgnosticDataRouteObject[],
|
|
4538
|
+
manifest: RouteManifest,
|
|
4539
|
+
mapRouteProperties: MapRoutePropertiesFunction
|
|
4540
|
+
) {
|
|
4541
|
+
if (routeId) {
|
|
4542
|
+
let route = manifest[routeId];
|
|
4543
|
+
invariant(
|
|
4544
|
+
route,
|
|
4545
|
+
`No route found to patch children into: routeId = ${routeId}`
|
|
4546
|
+
);
|
|
4547
|
+
let dataChildren = convertRoutesToDataRoutes(
|
|
4548
|
+
children,
|
|
4549
|
+
mapRouteProperties,
|
|
4550
|
+
[routeId, "patch", String(route.children?.length || "0")],
|
|
4551
|
+
manifest
|
|
4552
|
+
);
|
|
4553
|
+
if (route.children) {
|
|
4554
|
+
route.children.push(...dataChildren);
|
|
4555
|
+
} else {
|
|
4556
|
+
route.children = dataChildren;
|
|
4557
|
+
}
|
|
4558
|
+
} else {
|
|
4559
|
+
let dataChildren = convertRoutesToDataRoutes(
|
|
4560
|
+
children,
|
|
4561
|
+
mapRouteProperties,
|
|
4562
|
+
["patch", String(routes.length || "0")],
|
|
4563
|
+
manifest
|
|
4564
|
+
);
|
|
4565
|
+
routes.push(...dataChildren);
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
|
|
4065
4569
|
/**
|
|
4066
4570
|
* Execute route.lazy() methods to lazily load route modules (loader, action,
|
|
4067
4571
|
* shouldRevalidate) and update the routeManifest in place which shares objects
|
|
@@ -4797,11 +5301,13 @@ function getInternalRouterError(
|
|
|
4797
5301
|
routeId,
|
|
4798
5302
|
method,
|
|
4799
5303
|
type,
|
|
5304
|
+
message,
|
|
4800
5305
|
}: {
|
|
4801
5306
|
pathname?: string;
|
|
4802
5307
|
routeId?: string;
|
|
4803
5308
|
method?: string;
|
|
4804
|
-
type?: "defer-action" | "invalid-body";
|
|
5309
|
+
type?: "defer-action" | "invalid-body" | "route-discovery";
|
|
5310
|
+
message?: string;
|
|
4805
5311
|
} = {}
|
|
4806
5312
|
) {
|
|
4807
5313
|
let statusText = "Unknown Server Error";
|
|
@@ -4809,7 +5315,11 @@ function getInternalRouterError(
|
|
|
4809
5315
|
|
|
4810
5316
|
if (status === 400) {
|
|
4811
5317
|
statusText = "Bad Request";
|
|
4812
|
-
if (
|
|
5318
|
+
if (type === "route-discovery") {
|
|
5319
|
+
errorMessage =
|
|
5320
|
+
`Unable to match URL "${pathname}" - the \`children()\` function for ` +
|
|
5321
|
+
`route \`${routeId}\` threw the following error:\n${message}`;
|
|
5322
|
+
} else if (method && pathname && routeId) {
|
|
4813
5323
|
errorMessage =
|
|
4814
5324
|
`You made a ${method} request to "${pathname}" but ` +
|
|
4815
5325
|
`did not provide a \`loader\` for route "${routeId}", ` +
|
|
@@ -4883,6 +5393,10 @@ function isHashChangeOnly(a: Location, b: Location): boolean {
|
|
|
4883
5393
|
return false;
|
|
4884
5394
|
}
|
|
4885
5395
|
|
|
5396
|
+
function isPromise<T = unknown>(val: unknown): val is Promise<T> {
|
|
5397
|
+
return typeof val === "object" && val != null && "then" in val;
|
|
5398
|
+
}
|
|
5399
|
+
|
|
4886
5400
|
function isHandlerResult(result: unknown): result is HandlerResult {
|
|
4887
5401
|
return (
|
|
4888
5402
|
result != null &&
|
|
@@ -5243,5 +5757,4 @@ function persistAppliedTransitions(
|
|
|
5243
5757
|
}
|
|
5244
5758
|
}
|
|
5245
5759
|
}
|
|
5246
|
-
|
|
5247
5760
|
//#endregion
|