@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/dist/router.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @remix-run/router v1.
|
|
2
|
+
* @remix-run/router v1.17.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -486,7 +486,7 @@ function convertRoutesToDataRoutes(routes, mapRouteProperties, parentPath, manif
|
|
|
486
486
|
manifest = {};
|
|
487
487
|
}
|
|
488
488
|
return routes.map((route, index) => {
|
|
489
|
-
let treePath = [...parentPath, index];
|
|
489
|
+
let treePath = [...parentPath, String(index)];
|
|
490
490
|
let id = typeof route.id === "string" ? route.id : treePath.join("-");
|
|
491
491
|
invariant(route.index !== true || !route.children, "Cannot specify children on an index route");
|
|
492
492
|
invariant(!manifest[id], "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
|
|
@@ -518,6 +518,9 @@ function matchRoutes(routes, locationArg, basename) {
|
|
|
518
518
|
if (basename === void 0) {
|
|
519
519
|
basename = "/";
|
|
520
520
|
}
|
|
521
|
+
return matchRoutesImpl(routes, locationArg, basename, false);
|
|
522
|
+
}
|
|
523
|
+
function matchRoutesImpl(routes, locationArg, basename, allowPartial) {
|
|
521
524
|
let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
|
|
522
525
|
let pathname = stripBasename(location.pathname || "/", basename);
|
|
523
526
|
if (pathname == null) {
|
|
@@ -534,7 +537,7 @@ function matchRoutes(routes, locationArg, basename) {
|
|
|
534
537
|
// should be a safe operation. This avoids needing matchRoutes to be
|
|
535
538
|
// history-aware.
|
|
536
539
|
let decoded = decodePath(pathname);
|
|
537
|
-
matches = matchRouteBranch(branches[i], decoded);
|
|
540
|
+
matches = matchRouteBranch(branches[i], decoded, allowPartial);
|
|
538
541
|
}
|
|
539
542
|
return matches;
|
|
540
543
|
}
|
|
@@ -687,7 +690,10 @@ function compareIndexes(a, b) {
|
|
|
687
690
|
// so they sort equally.
|
|
688
691
|
0;
|
|
689
692
|
}
|
|
690
|
-
function matchRouteBranch(branch, pathname) {
|
|
693
|
+
function matchRouteBranch(branch, pathname, allowPartial) {
|
|
694
|
+
if (allowPartial === void 0) {
|
|
695
|
+
allowPartial = false;
|
|
696
|
+
}
|
|
691
697
|
let {
|
|
692
698
|
routesMeta
|
|
693
699
|
} = branch;
|
|
@@ -703,9 +709,18 @@ function matchRouteBranch(branch, pathname) {
|
|
|
703
709
|
caseSensitive: meta.caseSensitive,
|
|
704
710
|
end
|
|
705
711
|
}, remainingPathname);
|
|
706
|
-
if (!match) return null;
|
|
707
|
-
Object.assign(matchedParams, match.params);
|
|
708
712
|
let route = meta.route;
|
|
713
|
+
if (!match && end && allowPartial && !routesMeta[routesMeta.length - 1].route.index) {
|
|
714
|
+
match = matchPath({
|
|
715
|
+
path: meta.relativePath,
|
|
716
|
+
caseSensitive: meta.caseSensitive,
|
|
717
|
+
end: false
|
|
718
|
+
}, remainingPathname);
|
|
719
|
+
}
|
|
720
|
+
if (!match) {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
Object.assign(matchedParams, match.params);
|
|
709
724
|
matches.push({
|
|
710
725
|
// TODO: Can this as be avoided?
|
|
711
726
|
params: matchedParams,
|
|
@@ -1321,6 +1336,7 @@ function createRouter(init) {
|
|
|
1321
1336
|
let inFlightDataRoutes;
|
|
1322
1337
|
let basename = init.basename || "/";
|
|
1323
1338
|
let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
|
|
1339
|
+
let patchRoutesOnMissImpl = init.unstable_patchRoutesOnMiss;
|
|
1324
1340
|
// Config driven behavior flags
|
|
1325
1341
|
let future = _extends({
|
|
1326
1342
|
v7_fetcherPersist: false,
|
|
@@ -1349,7 +1365,7 @@ function createRouter(init) {
|
|
|
1349
1365
|
let initialScrollRestored = init.hydrationData != null;
|
|
1350
1366
|
let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);
|
|
1351
1367
|
let initialErrors = null;
|
|
1352
|
-
if (initialMatches == null) {
|
|
1368
|
+
if (initialMatches == null && !patchRoutesOnMissImpl) {
|
|
1353
1369
|
// If we do not match a user-provided-route, fall back to the root
|
|
1354
1370
|
// to allow the error boundary to take over
|
|
1355
1371
|
let error = getInternalRouterError(404, {
|
|
@@ -1365,13 +1381,15 @@ function createRouter(init) {
|
|
|
1365
1381
|
};
|
|
1366
1382
|
}
|
|
1367
1383
|
let initialized;
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1384
|
+
if (!initialMatches) {
|
|
1385
|
+
// We need to run patchRoutesOnMiss in initialize()
|
|
1386
|
+
initialized = false;
|
|
1387
|
+
initialMatches = [];
|
|
1388
|
+
} else if (initialMatches.some(m => m.route.lazy)) {
|
|
1371
1389
|
// All initialMatches need to be loaded before we're ready. If we have lazy
|
|
1372
1390
|
// functions around still then we'll need to run them in initialize()
|
|
1373
1391
|
initialized = false;
|
|
1374
|
-
} else if (!
|
|
1392
|
+
} else if (!initialMatches.some(m => m.route.loader)) {
|
|
1375
1393
|
// If we've got no loaders to run, then we're good to go
|
|
1376
1394
|
initialized = true;
|
|
1377
1395
|
} else if (future.v7_partialHydration) {
|
|
@@ -1476,6 +1494,9 @@ function createRouter(init) {
|
|
|
1476
1494
|
// Store blocker functions in a separate Map outside of router state since
|
|
1477
1495
|
// we don't need to update UI state if they change
|
|
1478
1496
|
let blockerFunctions = new Map();
|
|
1497
|
+
// Map of pending patchRoutesOnMiss() promises (keyed by path/matches) so
|
|
1498
|
+
// that we only kick them off once for a given combo
|
|
1499
|
+
let pendingPatchRoutes = new Map();
|
|
1479
1500
|
// Flag to ignore the next history update, so we can revert the URL change on
|
|
1480
1501
|
// a POP navigation that was blocked by the user without touching router state
|
|
1481
1502
|
let ignoreNextHistoryUpdate = false;
|
|
@@ -1841,17 +1862,17 @@ function createRouter(init) {
|
|
|
1841
1862
|
let loadingNavigation = opts && opts.overrideNavigation;
|
|
1842
1863
|
let matches = matchRoutes(routesToUse, location, basename);
|
|
1843
1864
|
let flushSync = (opts && opts.flushSync) === true;
|
|
1865
|
+
let fogOfWar = checkFogOfWar(matches, routesToUse, location.pathname);
|
|
1866
|
+
if (fogOfWar.active && fogOfWar.matches) {
|
|
1867
|
+
matches = fogOfWar.matches;
|
|
1868
|
+
}
|
|
1844
1869
|
// Short circuit with a 404 on the root error boundary if we match nothing
|
|
1845
1870
|
if (!matches) {
|
|
1846
|
-
let error = getInternalRouterError(404, {
|
|
1847
|
-
pathname: location.pathname
|
|
1848
|
-
});
|
|
1849
1871
|
let {
|
|
1850
|
-
|
|
1872
|
+
error,
|
|
1873
|
+
notFoundMatches,
|
|
1851
1874
|
route
|
|
1852
|
-
} =
|
|
1853
|
-
// Cancel all pending deferred on 404s since we don't keep any routes
|
|
1854
|
-
cancelActiveDeferreds();
|
|
1875
|
+
} = handleNavigational404(location.pathname);
|
|
1855
1876
|
completeNavigation(location, {
|
|
1856
1877
|
matches: notFoundMatches,
|
|
1857
1878
|
loaderData: {},
|
|
@@ -1892,25 +1913,45 @@ function createRouter(init) {
|
|
|
1892
1913
|
}];
|
|
1893
1914
|
} else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
|
|
1894
1915
|
// Call action if we received an action submission
|
|
1895
|
-
let actionResult = await handleAction(request, location, opts.submission, matches, {
|
|
1916
|
+
let actionResult = await handleAction(request, location, opts.submission, matches, fogOfWar.active, {
|
|
1896
1917
|
replace: opts.replace,
|
|
1897
1918
|
flushSync
|
|
1898
1919
|
});
|
|
1899
1920
|
if (actionResult.shortCircuited) {
|
|
1900
1921
|
return;
|
|
1901
1922
|
}
|
|
1923
|
+
// If we received a 404 from handleAction, it's because we couldn't lazily
|
|
1924
|
+
// discover the destination route so we don't want to call loaders
|
|
1925
|
+
if (actionResult.pendingActionResult) {
|
|
1926
|
+
let [routeId, result] = actionResult.pendingActionResult;
|
|
1927
|
+
if (isErrorResult(result) && isRouteErrorResponse(result.error) && result.error.status === 404) {
|
|
1928
|
+
pendingNavigationController = null;
|
|
1929
|
+
completeNavigation(location, {
|
|
1930
|
+
matches: actionResult.matches,
|
|
1931
|
+
loaderData: {},
|
|
1932
|
+
errors: {
|
|
1933
|
+
[routeId]: result.error
|
|
1934
|
+
}
|
|
1935
|
+
});
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
matches = actionResult.matches || matches;
|
|
1902
1940
|
pendingActionResult = actionResult.pendingActionResult;
|
|
1903
1941
|
loadingNavigation = getLoadingNavigation(location, opts.submission);
|
|
1904
1942
|
flushSync = false;
|
|
1943
|
+
// No need to do fog of war matching again on loader execution
|
|
1944
|
+
fogOfWar.active = false;
|
|
1905
1945
|
// Create a GET request for the loaders
|
|
1906
1946
|
request = createClientSideRequest(init.history, request.url, request.signal);
|
|
1907
1947
|
}
|
|
1908
1948
|
// Call loaders
|
|
1909
1949
|
let {
|
|
1910
1950
|
shortCircuited,
|
|
1951
|
+
matches: updatedMatches,
|
|
1911
1952
|
loaderData,
|
|
1912
1953
|
errors
|
|
1913
|
-
} = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync, pendingActionResult);
|
|
1954
|
+
} = await handleLoaders(request, location, matches, fogOfWar.active, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync, pendingActionResult);
|
|
1914
1955
|
if (shortCircuited) {
|
|
1915
1956
|
return;
|
|
1916
1957
|
}
|
|
@@ -1919,7 +1960,7 @@ function createRouter(init) {
|
|
|
1919
1960
|
// been assigned to a new controller for the next navigation
|
|
1920
1961
|
pendingNavigationController = null;
|
|
1921
1962
|
completeNavigation(location, _extends({
|
|
1922
|
-
matches
|
|
1963
|
+
matches: updatedMatches || matches
|
|
1923
1964
|
}, getActionDataForCommit(pendingActionResult), {
|
|
1924
1965
|
loaderData,
|
|
1925
1966
|
errors
|
|
@@ -1927,7 +1968,7 @@ function createRouter(init) {
|
|
|
1927
1968
|
}
|
|
1928
1969
|
// Call the action matched by the leaf route for this navigation and handle
|
|
1929
1970
|
// redirects/errors
|
|
1930
|
-
async function handleAction(request, location, submission, matches, opts) {
|
|
1971
|
+
async function handleAction(request, location, submission, matches, isFogOfWar, opts) {
|
|
1931
1972
|
if (opts === void 0) {
|
|
1932
1973
|
opts = {};
|
|
1933
1974
|
}
|
|
@@ -1939,6 +1980,42 @@ function createRouter(init) {
|
|
|
1939
1980
|
}, {
|
|
1940
1981
|
flushSync: opts.flushSync === true
|
|
1941
1982
|
});
|
|
1983
|
+
if (isFogOfWar) {
|
|
1984
|
+
let discoverResult = await discoverRoutes(matches, location.pathname, request.signal);
|
|
1985
|
+
if (discoverResult.type === "aborted") {
|
|
1986
|
+
return {
|
|
1987
|
+
shortCircuited: true
|
|
1988
|
+
};
|
|
1989
|
+
} else if (discoverResult.type === "error") {
|
|
1990
|
+
let {
|
|
1991
|
+
error,
|
|
1992
|
+
notFoundMatches,
|
|
1993
|
+
route
|
|
1994
|
+
} = handleDiscoverRouteError(location.pathname, discoverResult);
|
|
1995
|
+
return {
|
|
1996
|
+
matches: notFoundMatches,
|
|
1997
|
+
pendingActionResult: [route.id, {
|
|
1998
|
+
type: ResultType.error,
|
|
1999
|
+
error
|
|
2000
|
+
}]
|
|
2001
|
+
};
|
|
2002
|
+
} else if (!discoverResult.matches) {
|
|
2003
|
+
let {
|
|
2004
|
+
notFoundMatches,
|
|
2005
|
+
error,
|
|
2006
|
+
route
|
|
2007
|
+
} = handleNavigational404(location.pathname);
|
|
2008
|
+
return {
|
|
2009
|
+
matches: notFoundMatches,
|
|
2010
|
+
pendingActionResult: [route.id, {
|
|
2011
|
+
type: ResultType.error,
|
|
2012
|
+
error
|
|
2013
|
+
}]
|
|
2014
|
+
};
|
|
2015
|
+
} else {
|
|
2016
|
+
matches = discoverResult.matches;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
1942
2019
|
// Call our action and get the result
|
|
1943
2020
|
let result;
|
|
1944
2021
|
let actionMatch = getTargetMatch(matches, location);
|
|
@@ -1988,29 +2065,90 @@ function createRouter(init) {
|
|
|
1988
2065
|
// Store off the pending error - we use it to determine which loaders
|
|
1989
2066
|
// to call and will commit it when we complete the navigation
|
|
1990
2067
|
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
1991
|
-
// By default, all submissions
|
|
1992
|
-
// action threw an error that'll be rendered in
|
|
1993
|
-
// back to PUSH so that the user can use the
|
|
1994
|
-
// the pre-submission form location to try
|
|
2068
|
+
// By default, all submissions to the current location are REPLACE
|
|
2069
|
+
// navigations, but if the action threw an error that'll be rendered in
|
|
2070
|
+
// an errorElement, we fall back to PUSH so that the user can use the
|
|
2071
|
+
// back button to get back to the pre-submission form location to try
|
|
2072
|
+
// again
|
|
1995
2073
|
if ((opts && opts.replace) !== true) {
|
|
1996
2074
|
pendingAction = Action.Push;
|
|
1997
2075
|
}
|
|
1998
2076
|
return {
|
|
2077
|
+
matches,
|
|
1999
2078
|
pendingActionResult: [boundaryMatch.route.id, result]
|
|
2000
2079
|
};
|
|
2001
2080
|
}
|
|
2002
2081
|
return {
|
|
2082
|
+
matches,
|
|
2003
2083
|
pendingActionResult: [actionMatch.route.id, result]
|
|
2004
2084
|
};
|
|
2005
2085
|
}
|
|
2006
2086
|
// Call all applicable loaders for the given matches, handling redirects,
|
|
2007
2087
|
// errors, etc.
|
|
2008
|
-
async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync, pendingActionResult) {
|
|
2088
|
+
async function handleLoaders(request, location, matches, isFogOfWar, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync, pendingActionResult) {
|
|
2009
2089
|
// Figure out the right navigation we want to use for data loading
|
|
2010
2090
|
let loadingNavigation = overrideNavigation || getLoadingNavigation(location, submission);
|
|
2011
2091
|
// If this was a redirect from an action we don't have a "submission" but
|
|
2012
2092
|
// we have it on the loading navigation so use that if available
|
|
2013
2093
|
let activeSubmission = submission || fetcherSubmission || getSubmissionFromNavigation(loadingNavigation);
|
|
2094
|
+
// If this is an uninterrupted revalidation, we remain in our current idle
|
|
2095
|
+
// state. If not, we need to switch to our loading state and load data,
|
|
2096
|
+
// preserving any new action data or existing action data (in the case of
|
|
2097
|
+
// a revalidation interrupting an actionReload)
|
|
2098
|
+
// If we have partialHydration enabled, then don't update the state for the
|
|
2099
|
+
// initial data load since it's not a "navigation"
|
|
2100
|
+
let shouldUpdateNavigationState = !isUninterruptedRevalidation && (!future.v7_partialHydration || !initialHydration);
|
|
2101
|
+
// When fog of war is enabled, we enter our `loading` state earlier so we
|
|
2102
|
+
// can discover new routes during the `loading` state. We skip this if
|
|
2103
|
+
// we've already run actions since we would have done our matching already.
|
|
2104
|
+
// If the children() function threw then, we want to proceed with the
|
|
2105
|
+
// partial matches it discovered.
|
|
2106
|
+
if (isFogOfWar) {
|
|
2107
|
+
if (shouldUpdateNavigationState) {
|
|
2108
|
+
let actionData = getUpdatedActionData(pendingActionResult);
|
|
2109
|
+
updateState(_extends({
|
|
2110
|
+
navigation: loadingNavigation
|
|
2111
|
+
}, actionData !== undefined ? {
|
|
2112
|
+
actionData
|
|
2113
|
+
} : {}), {
|
|
2114
|
+
flushSync
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
let discoverResult = await discoverRoutes(matches, location.pathname, request.signal);
|
|
2118
|
+
if (discoverResult.type === "aborted") {
|
|
2119
|
+
return {
|
|
2120
|
+
shortCircuited: true
|
|
2121
|
+
};
|
|
2122
|
+
} else if (discoverResult.type === "error") {
|
|
2123
|
+
let {
|
|
2124
|
+
error,
|
|
2125
|
+
notFoundMatches,
|
|
2126
|
+
route
|
|
2127
|
+
} = handleDiscoverRouteError(location.pathname, discoverResult);
|
|
2128
|
+
return {
|
|
2129
|
+
matches: notFoundMatches,
|
|
2130
|
+
loaderData: {},
|
|
2131
|
+
errors: {
|
|
2132
|
+
[route.id]: error
|
|
2133
|
+
}
|
|
2134
|
+
};
|
|
2135
|
+
} else if (!discoverResult.matches) {
|
|
2136
|
+
let {
|
|
2137
|
+
error,
|
|
2138
|
+
notFoundMatches,
|
|
2139
|
+
route
|
|
2140
|
+
} = handleNavigational404(location.pathname);
|
|
2141
|
+
return {
|
|
2142
|
+
matches: notFoundMatches,
|
|
2143
|
+
loaderData: {},
|
|
2144
|
+
errors: {
|
|
2145
|
+
[route.id]: error
|
|
2146
|
+
}
|
|
2147
|
+
};
|
|
2148
|
+
} else {
|
|
2149
|
+
matches = discoverResult.matches;
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2014
2152
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
2015
2153
|
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, future.v7_partialHydration && initialHydration === true, future.unstable_skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult);
|
|
2016
2154
|
// Cancel pending deferreds for no-longer-matched routes or routes we're
|
|
@@ -2037,40 +2175,20 @@ function createRouter(init) {
|
|
|
2037
2175
|
shortCircuited: true
|
|
2038
2176
|
};
|
|
2039
2177
|
}
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
let fetcher = state.fetchers.get(rf.key);
|
|
2049
|
-
let revalidatingFetcher = getLoadingFetcher(undefined, fetcher ? fetcher.data : undefined);
|
|
2050
|
-
state.fetchers.set(rf.key, revalidatingFetcher);
|
|
2051
|
-
});
|
|
2052
|
-
let actionData;
|
|
2053
|
-
if (pendingActionResult && !isErrorResult(pendingActionResult[1])) {
|
|
2054
|
-
// This is cast to `any` currently because `RouteData`uses any and it
|
|
2055
|
-
// would be a breaking change to use any.
|
|
2056
|
-
// TODO: v7 - change `RouteData` to use `unknown` instead of `any`
|
|
2057
|
-
actionData = {
|
|
2058
|
-
[pendingActionResult[0]]: pendingActionResult[1].data
|
|
2059
|
-
};
|
|
2060
|
-
} else if (state.actionData) {
|
|
2061
|
-
if (Object.keys(state.actionData).length === 0) {
|
|
2062
|
-
actionData = null;
|
|
2063
|
-
} else {
|
|
2064
|
-
actionData = state.actionData;
|
|
2178
|
+
if (shouldUpdateNavigationState) {
|
|
2179
|
+
let updates = {};
|
|
2180
|
+
if (!isFogOfWar) {
|
|
2181
|
+
// Only update navigation/actionNData if we didn't already do it above
|
|
2182
|
+
updates.navigation = loadingNavigation;
|
|
2183
|
+
let actionData = getUpdatedActionData(pendingActionResult);
|
|
2184
|
+
if (actionData !== undefined) {
|
|
2185
|
+
updates.actionData = actionData;
|
|
2065
2186
|
}
|
|
2066
2187
|
}
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
}
|
|
2070
|
-
|
|
2071
|
-
} : {}, revalidatingFetchers.length > 0 ? {
|
|
2072
|
-
fetchers: new Map(state.fetchers)
|
|
2073
|
-
} : {}), {
|
|
2188
|
+
if (revalidatingFetchers.length > 0) {
|
|
2189
|
+
updates.fetchers = getUpdatedRevalidatingFetchers(revalidatingFetchers);
|
|
2190
|
+
}
|
|
2191
|
+
updateState(updates, {
|
|
2074
2192
|
flushSync
|
|
2075
2193
|
});
|
|
2076
2194
|
}
|
|
@@ -2155,12 +2273,37 @@ function createRouter(init) {
|
|
|
2155
2273
|
let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
|
|
2156
2274
|
let shouldUpdateFetchers = updatedFetchers || didAbortFetchLoads || revalidatingFetchers.length > 0;
|
|
2157
2275
|
return _extends({
|
|
2276
|
+
matches,
|
|
2158
2277
|
loaderData,
|
|
2159
2278
|
errors
|
|
2160
2279
|
}, shouldUpdateFetchers ? {
|
|
2161
2280
|
fetchers: new Map(state.fetchers)
|
|
2162
2281
|
} : {});
|
|
2163
2282
|
}
|
|
2283
|
+
function getUpdatedActionData(pendingActionResult) {
|
|
2284
|
+
if (pendingActionResult && !isErrorResult(pendingActionResult[1])) {
|
|
2285
|
+
// This is cast to `any` currently because `RouteData`uses any and it
|
|
2286
|
+
// would be a breaking change to use any.
|
|
2287
|
+
// TODO: v7 - change `RouteData` to use `unknown` instead of `any`
|
|
2288
|
+
return {
|
|
2289
|
+
[pendingActionResult[0]]: pendingActionResult[1].data
|
|
2290
|
+
};
|
|
2291
|
+
} else if (state.actionData) {
|
|
2292
|
+
if (Object.keys(state.actionData).length === 0) {
|
|
2293
|
+
return null;
|
|
2294
|
+
} else {
|
|
2295
|
+
return state.actionData;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
function getUpdatedRevalidatingFetchers(revalidatingFetchers) {
|
|
2300
|
+
revalidatingFetchers.forEach(rf => {
|
|
2301
|
+
let fetcher = state.fetchers.get(rf.key);
|
|
2302
|
+
let revalidatingFetcher = getLoadingFetcher(undefined, fetcher ? fetcher.data : undefined);
|
|
2303
|
+
state.fetchers.set(rf.key, revalidatingFetcher);
|
|
2304
|
+
});
|
|
2305
|
+
return new Map(state.fetchers);
|
|
2306
|
+
}
|
|
2164
2307
|
// Trigger a fetcher load/submit for the given fetcher key
|
|
2165
2308
|
function fetch(key, routeId, href, opts) {
|
|
2166
2309
|
if (isServer) {
|
|
@@ -2171,6 +2314,10 @@ function createRouter(init) {
|
|
|
2171
2314
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
2172
2315
|
let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, future.v7_relativeSplatPath, routeId, opts == null ? void 0 : opts.relative);
|
|
2173
2316
|
let matches = matchRoutes(routesToUse, normalizedPath, basename);
|
|
2317
|
+
let fogOfWar = checkFogOfWar(matches, routesToUse, normalizedPath);
|
|
2318
|
+
if (fogOfWar.active && fogOfWar.matches) {
|
|
2319
|
+
matches = fogOfWar.matches;
|
|
2320
|
+
}
|
|
2174
2321
|
if (!matches) {
|
|
2175
2322
|
setFetcherError(key, routeId, getInternalRouterError(404, {
|
|
2176
2323
|
pathname: normalizedPath
|
|
@@ -2193,7 +2340,7 @@ function createRouter(init) {
|
|
|
2193
2340
|
let match = getTargetMatch(matches, path);
|
|
2194
2341
|
pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
|
|
2195
2342
|
if (submission && isMutationMethod(submission.formMethod)) {
|
|
2196
|
-
handleFetcherAction(key, routeId, path, match, matches, flushSync, submission);
|
|
2343
|
+
handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
|
|
2197
2344
|
return;
|
|
2198
2345
|
}
|
|
2199
2346
|
// Store off the match so we can call it's shouldRevalidate on subsequent
|
|
@@ -2202,22 +2349,28 @@ function createRouter(init) {
|
|
|
2202
2349
|
routeId,
|
|
2203
2350
|
path
|
|
2204
2351
|
});
|
|
2205
|
-
handleFetcherLoader(key, routeId, path, match, matches, flushSync, submission);
|
|
2352
|
+
handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
|
|
2206
2353
|
}
|
|
2207
2354
|
// Call the action for the matched fetcher.submit(), and then handle redirects,
|
|
2208
2355
|
// errors, and revalidation
|
|
2209
|
-
async function handleFetcherAction(key, routeId, path, match, requestMatches, flushSync, submission) {
|
|
2356
|
+
async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, submission) {
|
|
2210
2357
|
interruptActiveLoads();
|
|
2211
2358
|
fetchLoadMatches.delete(key);
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2359
|
+
function detectAndHandle405Error(m) {
|
|
2360
|
+
if (!m.route.action && !m.route.lazy) {
|
|
2361
|
+
let error = getInternalRouterError(405, {
|
|
2362
|
+
method: submission.formMethod,
|
|
2363
|
+
pathname: path,
|
|
2364
|
+
routeId: routeId
|
|
2365
|
+
});
|
|
2366
|
+
setFetcherError(key, routeId, error, {
|
|
2367
|
+
flushSync
|
|
2368
|
+
});
|
|
2369
|
+
return true;
|
|
2370
|
+
}
|
|
2371
|
+
return false;
|
|
2372
|
+
}
|
|
2373
|
+
if (!isFogOfWar && detectAndHandle405Error(match)) {
|
|
2221
2374
|
return;
|
|
2222
2375
|
}
|
|
2223
2376
|
// Put this fetcher into it's submitting state
|
|
@@ -2225,9 +2378,36 @@ function createRouter(init) {
|
|
|
2225
2378
|
updateFetcherState(key, getSubmittingFetcher(submission, existingFetcher), {
|
|
2226
2379
|
flushSync
|
|
2227
2380
|
});
|
|
2228
|
-
// Call the action for the fetcher
|
|
2229
2381
|
let abortController = new AbortController();
|
|
2230
2382
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
|
|
2383
|
+
if (isFogOfWar) {
|
|
2384
|
+
let discoverResult = await discoverRoutes(requestMatches, path, fetchRequest.signal);
|
|
2385
|
+
if (discoverResult.type === "aborted") {
|
|
2386
|
+
return;
|
|
2387
|
+
} else if (discoverResult.type === "error") {
|
|
2388
|
+
let {
|
|
2389
|
+
error
|
|
2390
|
+
} = handleDiscoverRouteError(path, discoverResult);
|
|
2391
|
+
setFetcherError(key, routeId, error, {
|
|
2392
|
+
flushSync
|
|
2393
|
+
});
|
|
2394
|
+
return;
|
|
2395
|
+
} else if (!discoverResult.matches) {
|
|
2396
|
+
setFetcherError(key, routeId, getInternalRouterError(404, {
|
|
2397
|
+
pathname: path
|
|
2398
|
+
}), {
|
|
2399
|
+
flushSync
|
|
2400
|
+
});
|
|
2401
|
+
return;
|
|
2402
|
+
} else {
|
|
2403
|
+
requestMatches = discoverResult.matches;
|
|
2404
|
+
match = getTargetMatch(requestMatches, path);
|
|
2405
|
+
if (detectAndHandle405Error(match)) {
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
// Call the action for the fetcher
|
|
2231
2411
|
fetchControllers.set(key, abortController);
|
|
2232
2412
|
let originatingLoadId = incrementingLoadId;
|
|
2233
2413
|
let actionResults = await callDataStrategy("action", fetchRequest, [match], requestMatches);
|
|
@@ -2369,14 +2549,38 @@ function createRouter(init) {
|
|
|
2369
2549
|
}
|
|
2370
2550
|
}
|
|
2371
2551
|
// Call the matched loader for fetcher.load(), handling redirects, errors, etc.
|
|
2372
|
-
async function handleFetcherLoader(key, routeId, path, match, matches, flushSync, submission) {
|
|
2552
|
+
async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, submission) {
|
|
2373
2553
|
let existingFetcher = state.fetchers.get(key);
|
|
2374
2554
|
updateFetcherState(key, getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined), {
|
|
2375
2555
|
flushSync
|
|
2376
2556
|
});
|
|
2377
|
-
// Call the loader for this fetcher route match
|
|
2378
2557
|
let abortController = new AbortController();
|
|
2379
2558
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
|
|
2559
|
+
if (isFogOfWar) {
|
|
2560
|
+
let discoverResult = await discoverRoutes(matches, path, fetchRequest.signal);
|
|
2561
|
+
if (discoverResult.type === "aborted") {
|
|
2562
|
+
return;
|
|
2563
|
+
} else if (discoverResult.type === "error") {
|
|
2564
|
+
let {
|
|
2565
|
+
error
|
|
2566
|
+
} = handleDiscoverRouteError(path, discoverResult);
|
|
2567
|
+
setFetcherError(key, routeId, error, {
|
|
2568
|
+
flushSync
|
|
2569
|
+
});
|
|
2570
|
+
return;
|
|
2571
|
+
} else if (!discoverResult.matches) {
|
|
2572
|
+
setFetcherError(key, routeId, getInternalRouterError(404, {
|
|
2573
|
+
pathname: path
|
|
2574
|
+
}), {
|
|
2575
|
+
flushSync
|
|
2576
|
+
});
|
|
2577
|
+
return;
|
|
2578
|
+
} else {
|
|
2579
|
+
matches = discoverResult.matches;
|
|
2580
|
+
match = getTargetMatch(matches, path);
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
// Call the loader for this fetcher route match
|
|
2380
2584
|
fetchControllers.set(key, abortController);
|
|
2381
2585
|
let originatingLoadId = incrementingLoadId;
|
|
2382
2586
|
let results = await callDataStrategy("loader", fetchRequest, [match], matches);
|
|
@@ -2743,6 +2947,38 @@ function createRouter(init) {
|
|
|
2743
2947
|
return blockerKey;
|
|
2744
2948
|
}
|
|
2745
2949
|
}
|
|
2950
|
+
function handleNavigational404(pathname) {
|
|
2951
|
+
let error = getInternalRouterError(404, {
|
|
2952
|
+
pathname
|
|
2953
|
+
});
|
|
2954
|
+
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
2955
|
+
let {
|
|
2956
|
+
matches,
|
|
2957
|
+
route
|
|
2958
|
+
} = getShortCircuitMatches(routesToUse);
|
|
2959
|
+
// Cancel all pending deferred on 404s since we don't keep any routes
|
|
2960
|
+
cancelActiveDeferreds();
|
|
2961
|
+
return {
|
|
2962
|
+
notFoundMatches: matches,
|
|
2963
|
+
route,
|
|
2964
|
+
error
|
|
2965
|
+
};
|
|
2966
|
+
}
|
|
2967
|
+
function handleDiscoverRouteError(pathname, discoverResult) {
|
|
2968
|
+
let matches = discoverResult.partialMatches;
|
|
2969
|
+
let route = matches[matches.length - 1].route;
|
|
2970
|
+
let error = getInternalRouterError(400, {
|
|
2971
|
+
type: "route-discovery",
|
|
2972
|
+
routeId: route.id,
|
|
2973
|
+
pathname,
|
|
2974
|
+
message: discoverResult.error != null && "message" in discoverResult.error ? discoverResult.error : String(discoverResult.error)
|
|
2975
|
+
});
|
|
2976
|
+
return {
|
|
2977
|
+
notFoundMatches: matches,
|
|
2978
|
+
route,
|
|
2979
|
+
error
|
|
2980
|
+
};
|
|
2981
|
+
}
|
|
2746
2982
|
function cancelActiveDeferreds(predicate) {
|
|
2747
2983
|
let cancelledRouteIds = [];
|
|
2748
2984
|
activeDeferreds.forEach((dfd, routeId) => {
|
|
@@ -2804,6 +3040,99 @@ function createRouter(init) {
|
|
|
2804
3040
|
}
|
|
2805
3041
|
return null;
|
|
2806
3042
|
}
|
|
3043
|
+
function checkFogOfWar(matches, routesToUse, pathname) {
|
|
3044
|
+
if (patchRoutesOnMissImpl) {
|
|
3045
|
+
if (!matches) {
|
|
3046
|
+
let fogMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
|
|
3047
|
+
return {
|
|
3048
|
+
active: true,
|
|
3049
|
+
matches: fogMatches || []
|
|
3050
|
+
};
|
|
3051
|
+
} else {
|
|
3052
|
+
let leafRoute = matches[matches.length - 1].route;
|
|
3053
|
+
if (leafRoute.path === "*") {
|
|
3054
|
+
// If we matched a splat, it might only be because we haven't yet fetched
|
|
3055
|
+
// the children that would match with a higher score, so let's fetch
|
|
3056
|
+
// around and find out
|
|
3057
|
+
let partialMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
|
|
3058
|
+
return {
|
|
3059
|
+
active: true,
|
|
3060
|
+
matches: partialMatches
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
return {
|
|
3066
|
+
active: false,
|
|
3067
|
+
matches: null
|
|
3068
|
+
};
|
|
3069
|
+
}
|
|
3070
|
+
async function discoverRoutes(matches, pathname, signal) {
|
|
3071
|
+
let partialMatches = matches;
|
|
3072
|
+
let route = partialMatches.length > 0 ? partialMatches[partialMatches.length - 1].route : null;
|
|
3073
|
+
while (true) {
|
|
3074
|
+
try {
|
|
3075
|
+
await loadLazyRouteChildren(patchRoutesOnMissImpl, pathname, partialMatches, dataRoutes || inFlightDataRoutes, manifest, mapRouteProperties, pendingPatchRoutes, signal);
|
|
3076
|
+
} catch (e) {
|
|
3077
|
+
return {
|
|
3078
|
+
type: "error",
|
|
3079
|
+
error: e,
|
|
3080
|
+
partialMatches
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
if (signal.aborted) {
|
|
3084
|
+
return {
|
|
3085
|
+
type: "aborted"
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
3089
|
+
let newMatches = matchRoutes(routesToUse, pathname, basename);
|
|
3090
|
+
let matchedSplat = false;
|
|
3091
|
+
if (newMatches) {
|
|
3092
|
+
let leafRoute = newMatches[newMatches.length - 1].route;
|
|
3093
|
+
if (leafRoute.index) {
|
|
3094
|
+
// If we found an index route, we can stop
|
|
3095
|
+
return {
|
|
3096
|
+
type: "success",
|
|
3097
|
+
matches: newMatches
|
|
3098
|
+
};
|
|
3099
|
+
}
|
|
3100
|
+
if (leafRoute.path && leafRoute.path.length > 0) {
|
|
3101
|
+
if (leafRoute.path === "*") {
|
|
3102
|
+
// If we found a splat route, we can't be sure there's not a
|
|
3103
|
+
// higher-scoring route down some partial matches trail so we need
|
|
3104
|
+
// to check that out
|
|
3105
|
+
matchedSplat = true;
|
|
3106
|
+
} else {
|
|
3107
|
+
// If we found a non-splat route, we can stop
|
|
3108
|
+
return {
|
|
3109
|
+
type: "success",
|
|
3110
|
+
matches: newMatches
|
|
3111
|
+
};
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
let newPartialMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
|
|
3116
|
+
// If we are no longer partially matching anything, this was either a
|
|
3117
|
+
// legit splat match above, or it's a 404. Also avoid loops if the
|
|
3118
|
+
// second pass results in the same partial matches
|
|
3119
|
+
if (!newPartialMatches || partialMatches.map(m => m.route.id).join("-") === newPartialMatches.map(m => m.route.id).join("-")) {
|
|
3120
|
+
return {
|
|
3121
|
+
type: "success",
|
|
3122
|
+
matches: matchedSplat ? newMatches : null
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
partialMatches = newPartialMatches;
|
|
3126
|
+
route = partialMatches[partialMatches.length - 1].route;
|
|
3127
|
+
if (route.path === "*") {
|
|
3128
|
+
// The splat is still our most accurate partial, so run with it
|
|
3129
|
+
return {
|
|
3130
|
+
type: "success",
|
|
3131
|
+
matches: partialMatches
|
|
3132
|
+
};
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
2807
3136
|
function _internalSetRoutes(newRoutes) {
|
|
2808
3137
|
manifest = {};
|
|
2809
3138
|
inFlightDataRoutes = convertRoutesToDataRoutes(newRoutes, mapRouteProperties, undefined, manifest);
|
|
@@ -2839,6 +3168,9 @@ function createRouter(init) {
|
|
|
2839
3168
|
dispose,
|
|
2840
3169
|
getBlocker,
|
|
2841
3170
|
deleteBlocker,
|
|
3171
|
+
patchRoutes(routeId, children) {
|
|
3172
|
+
return patchRoutes(routeId, children, dataRoutes || inFlightDataRoutes, manifest, mapRouteProperties);
|
|
3173
|
+
},
|
|
2842
3174
|
_internalFetchControllers: fetchControllers,
|
|
2843
3175
|
_internalActiveDeferreds: activeDeferreds,
|
|
2844
3176
|
// TODO: Remove setRoutes, it's temporary to avoid dealing with
|
|
@@ -3614,6 +3946,49 @@ function shouldRevalidateLoader(loaderMatch, arg) {
|
|
|
3614
3946
|
}
|
|
3615
3947
|
return arg.defaultShouldRevalidate;
|
|
3616
3948
|
}
|
|
3949
|
+
/**
|
|
3950
|
+
* Idempotent utility to execute route.children() method to lazily load route
|
|
3951
|
+
* definitions and update the routes/routeManifest
|
|
3952
|
+
*/
|
|
3953
|
+
async function loadLazyRouteChildren(patchRoutesOnMissImpl, path, matches, routes, manifest, mapRouteProperties, pendingRouteChildren, signal) {
|
|
3954
|
+
let key = [path, ...matches.map(m => m.route.id)].join("-");
|
|
3955
|
+
try {
|
|
3956
|
+
let pending = pendingRouteChildren.get(key);
|
|
3957
|
+
if (!pending) {
|
|
3958
|
+
pending = patchRoutesOnMissImpl({
|
|
3959
|
+
path,
|
|
3960
|
+
matches,
|
|
3961
|
+
patch: (routeId, children) => {
|
|
3962
|
+
if (!signal.aborted) {
|
|
3963
|
+
patchRoutes(routeId, children, routes, manifest, mapRouteProperties);
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
});
|
|
3967
|
+
pendingRouteChildren.set(key, pending);
|
|
3968
|
+
}
|
|
3969
|
+
if (pending && isPromise(pending)) {
|
|
3970
|
+
await pending;
|
|
3971
|
+
}
|
|
3972
|
+
} finally {
|
|
3973
|
+
pendingRouteChildren.delete(key);
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3976
|
+
function patchRoutes(routeId, children, routes, manifest, mapRouteProperties) {
|
|
3977
|
+
if (routeId) {
|
|
3978
|
+
var _route$children;
|
|
3979
|
+
let route = manifest[routeId];
|
|
3980
|
+
invariant(route, "No route found to patch children into: routeId = " + routeId);
|
|
3981
|
+
let dataChildren = convertRoutesToDataRoutes(children, mapRouteProperties, [routeId, "patch", String(((_route$children = route.children) == null ? void 0 : _route$children.length) || "0")], manifest);
|
|
3982
|
+
if (route.children) {
|
|
3983
|
+
route.children.push(...dataChildren);
|
|
3984
|
+
} else {
|
|
3985
|
+
route.children = dataChildren;
|
|
3986
|
+
}
|
|
3987
|
+
} else {
|
|
3988
|
+
let dataChildren = convertRoutesToDataRoutes(children, mapRouteProperties, ["patch", String(routes.length || "0")], manifest);
|
|
3989
|
+
routes.push(...dataChildren);
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3617
3992
|
/**
|
|
3618
3993
|
* Execute route.lazy() methods to lazily load route modules (loader, action,
|
|
3619
3994
|
* shouldRevalidate) and update the routeManifest in place which shares objects
|
|
@@ -4139,13 +4514,16 @@ function getInternalRouterError(status, _temp5) {
|
|
|
4139
4514
|
pathname,
|
|
4140
4515
|
routeId,
|
|
4141
4516
|
method,
|
|
4142
|
-
type
|
|
4517
|
+
type,
|
|
4518
|
+
message
|
|
4143
4519
|
} = _temp5 === void 0 ? {} : _temp5;
|
|
4144
4520
|
let statusText = "Unknown Server Error";
|
|
4145
4521
|
let errorMessage = "Unknown @remix-run/router error";
|
|
4146
4522
|
if (status === 400) {
|
|
4147
4523
|
statusText = "Bad Request";
|
|
4148
|
-
if (
|
|
4524
|
+
if (type === "route-discovery") {
|
|
4525
|
+
errorMessage = "Unable to match URL \"" + pathname + "\" - the `children()` function for " + ("route `" + routeId + "` threw the following error:\n" + message);
|
|
4526
|
+
} else if (method && pathname && routeId) {
|
|
4149
4527
|
errorMessage = "You made a " + method + " request to \"" + pathname + "\" but " + ("did not provide a `loader` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
|
|
4150
4528
|
} else if (type === "defer-action") {
|
|
4151
4529
|
errorMessage = "defer() is not supported in actions";
|
|
@@ -4204,6 +4582,9 @@ function isHashChangeOnly(a, b) {
|
|
|
4204
4582
|
// /page#hash -> /page
|
|
4205
4583
|
return false;
|
|
4206
4584
|
}
|
|
4585
|
+
function isPromise(val) {
|
|
4586
|
+
return typeof val === "object" && val != null && "then" in val;
|
|
4587
|
+
}
|
|
4207
4588
|
function isHandlerResult(result) {
|
|
4208
4589
|
return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
|
|
4209
4590
|
}
|