@remix-run/router 1.0.2 → 1.0.3-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -2
- package/dist/history.d.ts +9 -0
- package/dist/index.d.ts +2 -2
- package/dist/router.cjs.js +340 -169
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.js +340 -170
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +3425 -0
- package/dist/router.umd.js.map +1 -0
- package/dist/router.umd.min.js +12 -0
- package/dist/router.umd.min.js.map +1 -0
- package/dist/utils.d.ts +24 -0
- package/history.ts +38 -2
- package/index.ts +5 -2
- package/package.json +2 -1
- package/router.ts +372 -171
- package/utils.ts +57 -1
package/router.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { History, Location,
|
|
1
|
+
import type { History, Location, To } from "./history";
|
|
2
2
|
import {
|
|
3
3
|
Action as HistoryAction,
|
|
4
4
|
createLocation,
|
|
5
5
|
createPath,
|
|
6
|
+
createURL,
|
|
6
7
|
parsePath,
|
|
7
8
|
} from "./history";
|
|
8
9
|
import type {
|
|
@@ -25,9 +26,12 @@ import {
|
|
|
25
26
|
ErrorResponse,
|
|
26
27
|
ResultType,
|
|
27
28
|
convertRoutesToDataRoutes,
|
|
29
|
+
getPathContributingMatches,
|
|
28
30
|
invariant,
|
|
29
31
|
isRouteErrorResponse,
|
|
32
|
+
joinPaths,
|
|
30
33
|
matchRoutes,
|
|
34
|
+
resolveTo,
|
|
31
35
|
} from "./utils";
|
|
32
36
|
|
|
33
37
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -469,14 +473,35 @@ interface HandleLoadersResult extends ShortCircuitable {
|
|
|
469
473
|
}
|
|
470
474
|
|
|
471
475
|
/**
|
|
472
|
-
* Tuple of [key, href,
|
|
476
|
+
* Tuple of [key, href, DataRouteMatch, DataRouteMatch[]] for a revalidating
|
|
477
|
+
* fetcher.load()
|
|
473
478
|
*/
|
|
474
|
-
type RevalidatingFetcher = [
|
|
479
|
+
type RevalidatingFetcher = [
|
|
480
|
+
string,
|
|
481
|
+
string,
|
|
482
|
+
AgnosticDataRouteMatch,
|
|
483
|
+
AgnosticDataRouteMatch[]
|
|
484
|
+
];
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Tuple of [href, DataRouteMatch, DataRouteMatch[]] for an active
|
|
488
|
+
* fetcher.load()
|
|
489
|
+
*/
|
|
490
|
+
type FetchLoadMatch = [
|
|
491
|
+
string,
|
|
492
|
+
AgnosticDataRouteMatch,
|
|
493
|
+
AgnosticDataRouteMatch[]
|
|
494
|
+
];
|
|
475
495
|
|
|
476
496
|
/**
|
|
477
|
-
*
|
|
497
|
+
* Wrapper object to allow us to throw any response out from callLoaderOrAction
|
|
498
|
+
* for queryRouter while preserving whether or not it was thrown or returned
|
|
499
|
+
* from the loader/action
|
|
478
500
|
*/
|
|
479
|
-
|
|
501
|
+
interface QueryRouteResponse {
|
|
502
|
+
type: ResultType.data | ResultType.error;
|
|
503
|
+
response: Response;
|
|
504
|
+
}
|
|
480
505
|
|
|
481
506
|
export const IDLE_NAVIGATION: NavigationStates["Idle"] = {
|
|
482
507
|
state: "idle",
|
|
@@ -495,6 +520,12 @@ export const IDLE_FETCHER: FetcherStates["Idle"] = {
|
|
|
495
520
|
formEncType: undefined,
|
|
496
521
|
formData: undefined,
|
|
497
522
|
};
|
|
523
|
+
|
|
524
|
+
const isBrowser =
|
|
525
|
+
typeof window !== "undefined" &&
|
|
526
|
+
typeof window.document !== "undefined" &&
|
|
527
|
+
typeof window.document.createElement !== "undefined";
|
|
528
|
+
const isServer = !isBrowser;
|
|
498
529
|
//#endregion
|
|
499
530
|
|
|
500
531
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -733,6 +764,14 @@ export function createRouter(init: RouterInit): Router {
|
|
|
733
764
|
let { path, submission, error } = normalizeNavigateOptions(to, opts);
|
|
734
765
|
|
|
735
766
|
let location = createLocation(state.location, path, opts && opts.state);
|
|
767
|
+
|
|
768
|
+
// When using navigate as a PUSH/REPLACE we aren't reading an already-encoded
|
|
769
|
+
// URL from window.location, so we need to encode it here so the behavior
|
|
770
|
+
// remains the same as POP and non-data-router usages. new URL() does all
|
|
771
|
+
// the same encoding we'd get from a history.pushState/window.location read
|
|
772
|
+
// without having to touch history
|
|
773
|
+
location = init.history.encodeLocation(location);
|
|
774
|
+
|
|
736
775
|
let historyAction =
|
|
737
776
|
(opts && opts.replace) === true || submission != null
|
|
738
777
|
? HistoryAction.Replace
|
|
@@ -939,7 +978,13 @@ export function createRouter(init: RouterInit): Router {
|
|
|
939
978
|
if (!actionMatch.route.action) {
|
|
940
979
|
result = getMethodNotAllowedResult(location);
|
|
941
980
|
} else {
|
|
942
|
-
result = await callLoaderOrAction(
|
|
981
|
+
result = await callLoaderOrAction(
|
|
982
|
+
"action",
|
|
983
|
+
request,
|
|
984
|
+
actionMatch,
|
|
985
|
+
matches,
|
|
986
|
+
router.basename
|
|
987
|
+
);
|
|
943
988
|
|
|
944
989
|
if (request.signal.aborted) {
|
|
945
990
|
return { shortCircuited: true };
|
|
@@ -1053,7 +1098,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1053
1098
|
// a revalidation interrupting an actionReload)
|
|
1054
1099
|
if (!isUninterruptedRevalidation) {
|
|
1055
1100
|
revalidatingFetchers.forEach(([key]) => {
|
|
1056
|
-
|
|
1101
|
+
let fetcher = state.fetchers.get(key);
|
|
1057
1102
|
let revalidatingFetcher: FetcherStates["Loading"] = {
|
|
1058
1103
|
state: "loading",
|
|
1059
1104
|
data: fetcher && fetcher.data,
|
|
@@ -1081,6 +1126,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1081
1126
|
let { results, loaderResults, fetcherResults } =
|
|
1082
1127
|
await callLoadersAndMaybeResolveData(
|
|
1083
1128
|
state.matches,
|
|
1129
|
+
matches,
|
|
1084
1130
|
matchesToLoad,
|
|
1085
1131
|
revalidatingFetchers,
|
|
1086
1132
|
request
|
|
@@ -1150,7 +1196,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1150
1196
|
href: string,
|
|
1151
1197
|
opts?: RouterFetchOptions
|
|
1152
1198
|
) {
|
|
1153
|
-
if (
|
|
1199
|
+
if (isServer) {
|
|
1154
1200
|
throw new Error(
|
|
1155
1201
|
"router.fetch() was called during the server render, but it shouldn't be. " +
|
|
1156
1202
|
"You are likely calling a useFetcher() method in the body of your component. " +
|
|
@@ -1170,14 +1216,14 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1170
1216
|
let match = getTargetMatch(matches, path);
|
|
1171
1217
|
|
|
1172
1218
|
if (submission) {
|
|
1173
|
-
handleFetcherAction(key, routeId, path, match, submission);
|
|
1219
|
+
handleFetcherAction(key, routeId, path, match, matches, submission);
|
|
1174
1220
|
return;
|
|
1175
1221
|
}
|
|
1176
1222
|
|
|
1177
1223
|
// Store off the match so we can call it's shouldRevalidate on subsequent
|
|
1178
1224
|
// revalidations
|
|
1179
|
-
fetchLoadMatches.set(key, [path, match]);
|
|
1180
|
-
handleFetcherLoader(key, routeId, path, match);
|
|
1225
|
+
fetchLoadMatches.set(key, [path, match, matches]);
|
|
1226
|
+
handleFetcherLoader(key, routeId, path, match, matches);
|
|
1181
1227
|
}
|
|
1182
1228
|
|
|
1183
1229
|
// Call the action for the matched fetcher.submit(), and then handle redirects,
|
|
@@ -1187,6 +1233,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1187
1233
|
routeId: string,
|
|
1188
1234
|
path: string,
|
|
1189
1235
|
match: AgnosticDataRouteMatch,
|
|
1236
|
+
requestMatches: AgnosticDataRouteMatch[],
|
|
1190
1237
|
submission: Submission
|
|
1191
1238
|
) {
|
|
1192
1239
|
interruptActiveLoads();
|
|
@@ -1213,7 +1260,13 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1213
1260
|
let fetchRequest = createRequest(path, abortController.signal, submission);
|
|
1214
1261
|
fetchControllers.set(key, abortController);
|
|
1215
1262
|
|
|
1216
|
-
let actionResult = await callLoaderOrAction(
|
|
1263
|
+
let actionResult = await callLoaderOrAction(
|
|
1264
|
+
"action",
|
|
1265
|
+
fetchRequest,
|
|
1266
|
+
match,
|
|
1267
|
+
requestMatches,
|
|
1268
|
+
router.basename
|
|
1269
|
+
);
|
|
1217
1270
|
|
|
1218
1271
|
if (fetchRequest.signal.aborted) {
|
|
1219
1272
|
// We can delete this so long as we weren't aborted by ou our own fetcher
|
|
@@ -1315,6 +1368,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1315
1368
|
let { results, loaderResults, fetcherResults } =
|
|
1316
1369
|
await callLoadersAndMaybeResolveData(
|
|
1317
1370
|
state.matches,
|
|
1371
|
+
matches,
|
|
1318
1372
|
matchesToLoad,
|
|
1319
1373
|
revalidatingFetchers,
|
|
1320
1374
|
revalidationRequest
|
|
@@ -1395,7 +1449,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1395
1449
|
key: string,
|
|
1396
1450
|
routeId: string,
|
|
1397
1451
|
path: string,
|
|
1398
|
-
match: AgnosticDataRouteMatch
|
|
1452
|
+
match: AgnosticDataRouteMatch,
|
|
1453
|
+
matches: AgnosticDataRouteMatch[]
|
|
1399
1454
|
) {
|
|
1400
1455
|
let existingFetcher = state.fetchers.get(key);
|
|
1401
1456
|
// Put this fetcher into it's loading state
|
|
@@ -1417,7 +1472,9 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1417
1472
|
let result: DataResult = await callLoaderOrAction(
|
|
1418
1473
|
"loader",
|
|
1419
1474
|
fetchRequest,
|
|
1420
|
-
match
|
|
1475
|
+
match,
|
|
1476
|
+
matches,
|
|
1477
|
+
router.basename
|
|
1421
1478
|
);
|
|
1422
1479
|
|
|
1423
1480
|
// Deferred isn't supported or fetcher loads, await everything and treat it
|
|
@@ -1515,6 +1572,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1515
1572
|
|
|
1516
1573
|
let redirectHistoryAction =
|
|
1517
1574
|
replace === true ? HistoryAction.Replace : HistoryAction.Push;
|
|
1575
|
+
|
|
1518
1576
|
await startNavigation(redirectHistoryAction, navigation.location, {
|
|
1519
1577
|
overrideNavigation: navigation,
|
|
1520
1578
|
});
|
|
@@ -1522,6 +1580,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1522
1580
|
|
|
1523
1581
|
async function callLoadersAndMaybeResolveData(
|
|
1524
1582
|
currentMatches: AgnosticDataRouteMatch[],
|
|
1583
|
+
matches: AgnosticDataRouteMatch[],
|
|
1525
1584
|
matchesToLoad: AgnosticDataRouteMatch[],
|
|
1526
1585
|
fetchersToLoad: RevalidatingFetcher[],
|
|
1527
1586
|
request: Request
|
|
@@ -1530,9 +1589,17 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1530
1589
|
// then slice off the results into separate arrays so we can handle them
|
|
1531
1590
|
// accordingly
|
|
1532
1591
|
let results = await Promise.all([
|
|
1533
|
-
...matchesToLoad.map((
|
|
1534
|
-
|
|
1535
|
-
|
|
1592
|
+
...matchesToLoad.map((match) =>
|
|
1593
|
+
callLoaderOrAction("loader", request, match, matches, router.basename)
|
|
1594
|
+
),
|
|
1595
|
+
...fetchersToLoad.map(([, href, match, fetchMatches]) =>
|
|
1596
|
+
callLoaderOrAction(
|
|
1597
|
+
"loader",
|
|
1598
|
+
createRequest(href, request.signal),
|
|
1599
|
+
match,
|
|
1600
|
+
fetchMatches,
|
|
1601
|
+
router.basename
|
|
1602
|
+
)
|
|
1536
1603
|
),
|
|
1537
1604
|
]);
|
|
1538
1605
|
let loaderResults = results.slice(0, matchesToLoad.length);
|
|
@@ -1739,7 +1806,9 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1739
1806
|
navigate,
|
|
1740
1807
|
fetch,
|
|
1741
1808
|
revalidate,
|
|
1742
|
-
createHref
|
|
1809
|
+
// Passthrough to history-aware createHref used by useHref so we get proper
|
|
1810
|
+
// hash-aware URLs in DOM paths
|
|
1811
|
+
createHref: (to: To) => init.history.createHref(to),
|
|
1743
1812
|
getFetcher,
|
|
1744
1813
|
deleteFetcher,
|
|
1745
1814
|
dispose,
|
|
@@ -1755,6 +1824,9 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1755
1824
|
//#region createStaticHandler
|
|
1756
1825
|
////////////////////////////////////////////////////////////////////////////////
|
|
1757
1826
|
|
|
1827
|
+
const validActionMethods = new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
1828
|
+
const validRequestMethods = new Set(["GET", "HEAD", ...validActionMethods]);
|
|
1829
|
+
|
|
1758
1830
|
export function unstable_createStaticHandler(
|
|
1759
1831
|
routes: AgnosticRouteObject[]
|
|
1760
1832
|
): StaticHandler {
|
|
@@ -1765,37 +1837,133 @@ export function unstable_createStaticHandler(
|
|
|
1765
1837
|
|
|
1766
1838
|
let dataRoutes = convertRoutesToDataRoutes(routes);
|
|
1767
1839
|
|
|
1840
|
+
/**
|
|
1841
|
+
* The query() method is intended for document requests, in which we want to
|
|
1842
|
+
* call an optional action and potentially multiple loaders for all nested
|
|
1843
|
+
* routes. It returns a StaticHandlerContext object, which is very similar
|
|
1844
|
+
* to the router state (location, loaderData, actionData, errors, etc.) and
|
|
1845
|
+
* also adds SSR-specific information such as the statusCode and headers
|
|
1846
|
+
* from action/loaders Responses.
|
|
1847
|
+
*
|
|
1848
|
+
* It _should_ never throw and should report all errors through the
|
|
1849
|
+
* returned context.errors object, properly associating errors to their error
|
|
1850
|
+
* boundary. Additionally, it tracks _deepestRenderedBoundaryId which can be
|
|
1851
|
+
* used to emulate React error boundaries during SSr by performing a second
|
|
1852
|
+
* pass only down to the boundaryId.
|
|
1853
|
+
*
|
|
1854
|
+
* The one exception where we do not return a StaticHandlerContext is when a
|
|
1855
|
+
* redirect response is returned or thrown from any action/loader. We
|
|
1856
|
+
* propagate that out and return the raw Response so the HTTP server can
|
|
1857
|
+
* return it directly.
|
|
1858
|
+
*/
|
|
1768
1859
|
async function query(
|
|
1769
1860
|
request: Request
|
|
1770
1861
|
): Promise<StaticHandlerContext | Response> {
|
|
1771
|
-
let
|
|
1862
|
+
let url = new URL(request.url);
|
|
1863
|
+
let location = createLocation("", createPath(url), null, "default");
|
|
1864
|
+
let matches = matchRoutes(dataRoutes, location);
|
|
1865
|
+
|
|
1866
|
+
if (!validRequestMethods.has(request.method)) {
|
|
1867
|
+
let {
|
|
1868
|
+
matches: methodNotAllowedMatches,
|
|
1869
|
+
route,
|
|
1870
|
+
error,
|
|
1871
|
+
} = getMethodNotAllowedMatches(dataRoutes);
|
|
1872
|
+
return {
|
|
1873
|
+
location,
|
|
1874
|
+
matches: methodNotAllowedMatches,
|
|
1875
|
+
loaderData: {},
|
|
1876
|
+
actionData: null,
|
|
1877
|
+
errors: {
|
|
1878
|
+
[route.id]: error,
|
|
1879
|
+
},
|
|
1880
|
+
statusCode: error.status,
|
|
1881
|
+
loaderHeaders: {},
|
|
1882
|
+
actionHeaders: {},
|
|
1883
|
+
};
|
|
1884
|
+
} else if (!matches) {
|
|
1885
|
+
let {
|
|
1886
|
+
matches: notFoundMatches,
|
|
1887
|
+
route,
|
|
1888
|
+
error,
|
|
1889
|
+
} = getNotFoundMatches(dataRoutes);
|
|
1890
|
+
return {
|
|
1891
|
+
location,
|
|
1892
|
+
matches: notFoundMatches,
|
|
1893
|
+
loaderData: {},
|
|
1894
|
+
actionData: null,
|
|
1895
|
+
errors: {
|
|
1896
|
+
[route.id]: error,
|
|
1897
|
+
},
|
|
1898
|
+
statusCode: error.status,
|
|
1899
|
+
loaderHeaders: {},
|
|
1900
|
+
actionHeaders: {},
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
let result = await queryImpl(request, location, matches);
|
|
1772
1905
|
if (result instanceof Response) {
|
|
1773
1906
|
return result;
|
|
1774
1907
|
}
|
|
1908
|
+
|
|
1775
1909
|
// When returning StaticHandlerContext, we patch back in the location here
|
|
1776
1910
|
// since we need it for React Context. But this helps keep our submit and
|
|
1777
1911
|
// loadRouteData operating on a Request instead of a Location
|
|
1778
1912
|
return { location, ...result };
|
|
1779
1913
|
}
|
|
1780
1914
|
|
|
1781
|
-
|
|
1782
|
-
|
|
1915
|
+
/**
|
|
1916
|
+
* The queryRoute() method is intended for targeted route requests, either
|
|
1917
|
+
* for fetch ?_data requests or resource route requests. In this case, we
|
|
1918
|
+
* are only ever calling a single action or loader, and we are returning the
|
|
1919
|
+
* returned value directly. In most cases, this will be a Response returned
|
|
1920
|
+
* from the action/loader, but it may be a primitive or other value as well -
|
|
1921
|
+
* and in such cases the calling context should handle that accordingly.
|
|
1922
|
+
*
|
|
1923
|
+
* We do respect the throw/return differentiation, so if an action/loader
|
|
1924
|
+
* throws, then this method will throw the value. This is important so we
|
|
1925
|
+
* can do proper boundary identification in Remix where a thrown Response
|
|
1926
|
+
* must go to the Catch Boundary but a returned Response is happy-path.
|
|
1927
|
+
*
|
|
1928
|
+
* One thing to note is that any Router-initiated thrown Response (such as a
|
|
1929
|
+
* 404 or 405) will have a custom X-Remix-Router-Error: "yes" header on it
|
|
1930
|
+
* in order to differentiate from responses thrown from user actions/loaders.
|
|
1931
|
+
*/
|
|
1932
|
+
async function queryRoute(request: Request, routeId?: string): Promise<any> {
|
|
1933
|
+
let url = new URL(request.url);
|
|
1934
|
+
let location = createLocation("", createPath(url), null, "default");
|
|
1935
|
+
let matches = matchRoutes(dataRoutes, location);
|
|
1936
|
+
|
|
1937
|
+
if (!validRequestMethods.has(request.method)) {
|
|
1938
|
+
throw createRouterErrorResponse(null, {
|
|
1939
|
+
status: 405,
|
|
1940
|
+
statusText: "Method Not Allowed",
|
|
1941
|
+
});
|
|
1942
|
+
} else if (!matches) {
|
|
1943
|
+
throw createRouterErrorResponse(null, {
|
|
1944
|
+
status: 404,
|
|
1945
|
+
statusText: "Not Found",
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
let match = routeId
|
|
1950
|
+
? matches.find((m) => m.route.id === routeId)
|
|
1951
|
+
: getTargetMatch(matches, location);
|
|
1952
|
+
|
|
1953
|
+
if (!match) {
|
|
1954
|
+
throw createRouterErrorResponse(null, {
|
|
1955
|
+
status: 404,
|
|
1956
|
+
statusText: "Not Found",
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
let result = await queryImpl(request, location, matches, match);
|
|
1783
1961
|
if (result instanceof Response) {
|
|
1784
1962
|
return result;
|
|
1785
1963
|
}
|
|
1786
1964
|
|
|
1787
1965
|
let error = result.errors ? Object.values(result.errors)[0] : undefined;
|
|
1788
1966
|
if (error !== undefined) {
|
|
1789
|
-
// While we always re-throw Responses returned from loaders/actions
|
|
1790
|
-
// directly for route requests and prevent the unwrapping into an
|
|
1791
|
-
// ErrorResponse, we still need this for error cases _prior_ the
|
|
1792
|
-
// execution of the loader/action, such as a 404/405 error.
|
|
1793
|
-
if (isRouteErrorResponse(error)) {
|
|
1794
|
-
return new Response(error.data, {
|
|
1795
|
-
status: error.status,
|
|
1796
|
-
statusText: error.statusText,
|
|
1797
|
-
});
|
|
1798
|
-
}
|
|
1799
1967
|
// If we got back result.errors, that means the loader/action threw
|
|
1800
1968
|
// _something_ that wasn't a Response, but it's not guaranteed/required
|
|
1801
1969
|
// to be an `instanceof Error` either, so we have to use throw here to
|
|
@@ -1805,66 +1973,53 @@ export function unstable_createStaticHandler(
|
|
|
1805
1973
|
|
|
1806
1974
|
// Pick off the right state value to return
|
|
1807
1975
|
let routeData = [result.actionData, result.loaderData].find((v) => v);
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
if (isRouteErrorResponse(value)) {
|
|
1811
|
-
return new Response(value.data, {
|
|
1812
|
-
status: value.status,
|
|
1813
|
-
statusText: value.statusText,
|
|
1814
|
-
});
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
|
-
return value;
|
|
1976
|
+
return Object.values(routeData || {})[0];
|
|
1818
1977
|
}
|
|
1819
1978
|
|
|
1820
1979
|
async function queryImpl(
|
|
1821
1980
|
request: Request,
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
}> {
|
|
1827
|
-
invariant(
|
|
1828
|
-
request.method !== "HEAD",
|
|
1829
|
-
"query()/queryRoute() do not support HEAD requests"
|
|
1830
|
-
);
|
|
1981
|
+
location: Location,
|
|
1982
|
+
matches: AgnosticDataRouteMatch[],
|
|
1983
|
+
routeMatch?: AgnosticDataRouteMatch
|
|
1984
|
+
): Promise<Omit<StaticHandlerContext, "location"> | Response> {
|
|
1831
1985
|
invariant(
|
|
1832
1986
|
request.signal,
|
|
1833
1987
|
"query()/queryRoute() requests must contain an AbortController signal"
|
|
1834
1988
|
);
|
|
1835
1989
|
|
|
1836
|
-
let { location, matches, shortCircuitState } = matchRequest(
|
|
1837
|
-
request,
|
|
1838
|
-
routeId
|
|
1839
|
-
);
|
|
1840
|
-
|
|
1841
1990
|
try {
|
|
1842
|
-
if (
|
|
1843
|
-
return { location, result: shortCircuitState };
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
if (request.method !== "GET") {
|
|
1991
|
+
if (validActionMethods.has(request.method)) {
|
|
1847
1992
|
let result = await submit(
|
|
1848
1993
|
request,
|
|
1849
1994
|
matches,
|
|
1850
|
-
getTargetMatch(matches, location),
|
|
1851
|
-
|
|
1995
|
+
routeMatch || getTargetMatch(matches, location),
|
|
1996
|
+
routeMatch != null
|
|
1852
1997
|
);
|
|
1853
|
-
return
|
|
1998
|
+
return result;
|
|
1854
1999
|
}
|
|
1855
2000
|
|
|
1856
|
-
let result = await loadRouteData(request, matches,
|
|
1857
|
-
return
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
};
|
|
2001
|
+
let result = await loadRouteData(request, matches, routeMatch);
|
|
2002
|
+
return result instanceof Response
|
|
2003
|
+
? result
|
|
2004
|
+
: {
|
|
2005
|
+
...result,
|
|
2006
|
+
actionData: null,
|
|
2007
|
+
actionHeaders: {},
|
|
2008
|
+
};
|
|
1865
2009
|
} catch (e) {
|
|
1866
|
-
|
|
1867
|
-
|
|
2010
|
+
// If the user threw/returned a Response in callLoaderOrAction, we throw
|
|
2011
|
+
// it to bail out and then return or throw here based on whether the user
|
|
2012
|
+
// returned or threw
|
|
2013
|
+
if (isQueryRouteResponse(e)) {
|
|
2014
|
+
if (e.type === ResultType.error && !isRedirectResponse(e.response)) {
|
|
2015
|
+
throw e.response;
|
|
2016
|
+
}
|
|
2017
|
+
return e.response;
|
|
2018
|
+
}
|
|
2019
|
+
// Redirects are always returned since they don't propagate to catch
|
|
2020
|
+
// boundaries
|
|
2021
|
+
if (isRedirectResponse(e)) {
|
|
2022
|
+
return e;
|
|
1868
2023
|
}
|
|
1869
2024
|
throw e;
|
|
1870
2025
|
}
|
|
@@ -1878,13 +2033,20 @@ export function unstable_createStaticHandler(
|
|
|
1878
2033
|
): Promise<Omit<StaticHandlerContext, "location"> | Response> {
|
|
1879
2034
|
let result: DataResult;
|
|
1880
2035
|
if (!actionMatch.route.action) {
|
|
1881
|
-
|
|
1882
|
-
|
|
2036
|
+
if (isRouteRequest) {
|
|
2037
|
+
throw createRouterErrorResponse(null, {
|
|
2038
|
+
status: 405,
|
|
2039
|
+
statusText: "Method Not Allowed",
|
|
2040
|
+
});
|
|
2041
|
+
}
|
|
2042
|
+
result = getMethodNotAllowedResult(request.url);
|
|
1883
2043
|
} else {
|
|
1884
2044
|
result = await callLoaderOrAction(
|
|
1885
2045
|
"action",
|
|
1886
2046
|
request,
|
|
1887
2047
|
actionMatch,
|
|
2048
|
+
matches,
|
|
2049
|
+
undefined, // Basename not currently supported in static handlers
|
|
1888
2050
|
true,
|
|
1889
2051
|
isRouteRequest
|
|
1890
2052
|
);
|
|
@@ -1897,7 +2059,7 @@ export function unstable_createStaticHandler(
|
|
|
1897
2059
|
|
|
1898
2060
|
if (isRedirectResult(result)) {
|
|
1899
2061
|
// Uhhhh - this should never happen, we should always throw these from
|
|
1900
|
-
//
|
|
2062
|
+
// callLoaderOrAction, but the type narrowing here keeps TS happy and we
|
|
1901
2063
|
// can get back on the "throw all redirect responses" train here should
|
|
1902
2064
|
// this ever happen :/
|
|
1903
2065
|
throw new Response(null, {
|
|
@@ -1913,6 +2075,8 @@ export function unstable_createStaticHandler(
|
|
|
1913
2075
|
}
|
|
1914
2076
|
|
|
1915
2077
|
if (isRouteRequest) {
|
|
2078
|
+
// Note: This should only be non-Response values if we get here, since
|
|
2079
|
+
// isRouteRequest should throw any Response received in callLoaderOrAction
|
|
1916
2080
|
if (isErrorResult(result)) {
|
|
1917
2081
|
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
1918
2082
|
return {
|
|
@@ -1947,7 +2111,7 @@ export function unstable_createStaticHandler(
|
|
|
1947
2111
|
// Store off the pending error - we use it to determine which loaders
|
|
1948
2112
|
// to call and will commit it when we complete the navigation
|
|
1949
2113
|
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
1950
|
-
let context = await loadRouteData(request, matches,
|
|
2114
|
+
let context = await loadRouteData(request, matches, undefined, {
|
|
1951
2115
|
[boundaryMatch.route.id]: result.error,
|
|
1952
2116
|
});
|
|
1953
2117
|
|
|
@@ -1964,7 +2128,7 @@ export function unstable_createStaticHandler(
|
|
|
1964
2128
|
};
|
|
1965
2129
|
}
|
|
1966
2130
|
|
|
1967
|
-
let context = await loadRouteData(request, matches
|
|
2131
|
+
let context = await loadRouteData(request, matches);
|
|
1968
2132
|
|
|
1969
2133
|
return {
|
|
1970
2134
|
...context,
|
|
@@ -1982,16 +2146,20 @@ export function unstable_createStaticHandler(
|
|
|
1982
2146
|
async function loadRouteData(
|
|
1983
2147
|
request: Request,
|
|
1984
2148
|
matches: AgnosticDataRouteMatch[],
|
|
1985
|
-
|
|
2149
|
+
routeMatch?: AgnosticDataRouteMatch,
|
|
1986
2150
|
pendingActionError?: RouteData
|
|
1987
2151
|
): Promise<
|
|
1988
2152
|
| Omit<StaticHandlerContext, "location" | "actionData" | "actionHeaders">
|
|
1989
2153
|
| Response
|
|
1990
2154
|
> {
|
|
1991
|
-
let
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2155
|
+
let isRouteRequest = routeMatch != null;
|
|
2156
|
+
let requestMatches = routeMatch
|
|
2157
|
+
? [routeMatch]
|
|
2158
|
+
: getLoaderMatchesUntilBoundary(
|
|
2159
|
+
matches,
|
|
2160
|
+
Object.keys(pendingActionError || {})[0]
|
|
2161
|
+
);
|
|
2162
|
+
let matchesToLoad = requestMatches.filter((m) => m.route.loader);
|
|
1995
2163
|
|
|
1996
2164
|
// Short circuit if we have no loaders to run
|
|
1997
2165
|
if (matchesToLoad.length === 0) {
|
|
@@ -2005,8 +2173,16 @@ export function unstable_createStaticHandler(
|
|
|
2005
2173
|
}
|
|
2006
2174
|
|
|
2007
2175
|
let results = await Promise.all([
|
|
2008
|
-
...matchesToLoad.map((
|
|
2009
|
-
callLoaderOrAction(
|
|
2176
|
+
...matchesToLoad.map((match) =>
|
|
2177
|
+
callLoaderOrAction(
|
|
2178
|
+
"loader",
|
|
2179
|
+
request,
|
|
2180
|
+
match,
|
|
2181
|
+
matches,
|
|
2182
|
+
undefined, // Basename not currently supported in static handlers
|
|
2183
|
+
true,
|
|
2184
|
+
isRouteRequest
|
|
2185
|
+
)
|
|
2010
2186
|
),
|
|
2011
2187
|
]);
|
|
2012
2188
|
|
|
@@ -2037,47 +2213,17 @@ export function unstable_createStaticHandler(
|
|
|
2037
2213
|
};
|
|
2038
2214
|
}
|
|
2039
2215
|
|
|
2040
|
-
function
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
)
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
let matches = matchRoutes(dataRoutes, location);
|
|
2052
|
-
if (matches && routeId) {
|
|
2053
|
-
matches = matches.filter((m) => m.route.id === routeId);
|
|
2054
|
-
}
|
|
2055
|
-
|
|
2056
|
-
// Short circuit with a 404 if we match nothing
|
|
2057
|
-
if (!matches) {
|
|
2058
|
-
let {
|
|
2059
|
-
matches: notFoundMatches,
|
|
2060
|
-
route,
|
|
2061
|
-
error,
|
|
2062
|
-
} = getNotFoundMatches(dataRoutes);
|
|
2063
|
-
return {
|
|
2064
|
-
location,
|
|
2065
|
-
matches: notFoundMatches,
|
|
2066
|
-
shortCircuitState: {
|
|
2067
|
-
matches: notFoundMatches,
|
|
2068
|
-
loaderData: {},
|
|
2069
|
-
actionData: null,
|
|
2070
|
-
errors: {
|
|
2071
|
-
[route.id]: error,
|
|
2072
|
-
},
|
|
2073
|
-
statusCode: 404,
|
|
2074
|
-
loaderHeaders: {},
|
|
2075
|
-
actionHeaders: {},
|
|
2076
|
-
},
|
|
2077
|
-
};
|
|
2078
|
-
}
|
|
2079
|
-
|
|
2080
|
-
return { location, matches };
|
|
2216
|
+
function createRouterErrorResponse(
|
|
2217
|
+
body: BodyInit | null | undefined,
|
|
2218
|
+
init: ResponseInit
|
|
2219
|
+
) {
|
|
2220
|
+
return new Response(body, {
|
|
2221
|
+
...init,
|
|
2222
|
+
headers: {
|
|
2223
|
+
...init.headers,
|
|
2224
|
+
"X-Remix-Router-Error": "yes",
|
|
2225
|
+
},
|
|
2226
|
+
});
|
|
2081
2227
|
}
|
|
2082
2228
|
|
|
2083
2229
|
return {
|
|
@@ -2136,7 +2282,7 @@ function normalizeNavigateOptions(
|
|
|
2136
2282
|
path,
|
|
2137
2283
|
submission: {
|
|
2138
2284
|
formMethod: opts.formMethod,
|
|
2139
|
-
formAction:
|
|
2285
|
+
formAction: stripHashFromPath(path),
|
|
2140
2286
|
formEncType:
|
|
2141
2287
|
(opts && opts.formEncType) || "application/x-www-form-urlencoded",
|
|
2142
2288
|
formData: opts.formData,
|
|
@@ -2251,10 +2397,10 @@ function getMatchesToLoad(
|
|
|
2251
2397
|
// Pick fetcher.loads that need to be revalidated
|
|
2252
2398
|
let revalidatingFetchers: RevalidatingFetcher[] = [];
|
|
2253
2399
|
fetchLoadMatches &&
|
|
2254
|
-
fetchLoadMatches.forEach(([href, match], key) => {
|
|
2400
|
+
fetchLoadMatches.forEach(([href, match, fetchMatches], key) => {
|
|
2255
2401
|
// This fetcher was cancelled from a prior action submission - force reload
|
|
2256
2402
|
if (cancelledFetcherLoads.includes(key)) {
|
|
2257
|
-
revalidatingFetchers.push([key, href, match]);
|
|
2403
|
+
revalidatingFetchers.push([key, href, match, fetchMatches]);
|
|
2258
2404
|
} else if (isRevalidationRequired) {
|
|
2259
2405
|
let shouldRevalidate = shouldRevalidateLoader(
|
|
2260
2406
|
href,
|
|
@@ -2266,7 +2412,7 @@ function getMatchesToLoad(
|
|
|
2266
2412
|
actionResult
|
|
2267
2413
|
);
|
|
2268
2414
|
if (shouldRevalidate) {
|
|
2269
|
-
revalidatingFetchers.push([key, href, match]);
|
|
2415
|
+
revalidatingFetchers.push([key, href, match, fetchMatches]);
|
|
2270
2416
|
}
|
|
2271
2417
|
}
|
|
2272
2418
|
});
|
|
@@ -2360,7 +2506,9 @@ async function callLoaderOrAction(
|
|
|
2360
2506
|
type: "loader" | "action",
|
|
2361
2507
|
request: Request,
|
|
2362
2508
|
match: AgnosticDataRouteMatch,
|
|
2363
|
-
|
|
2509
|
+
matches: AgnosticDataRouteMatch[],
|
|
2510
|
+
basename: string | undefined,
|
|
2511
|
+
isStaticRequest: boolean = false,
|
|
2364
2512
|
isRouteRequest: boolean = false
|
|
2365
2513
|
): Promise<DataResult> {
|
|
2366
2514
|
let resultType;
|
|
@@ -2391,23 +2539,46 @@ async function callLoaderOrAction(
|
|
|
2391
2539
|
}
|
|
2392
2540
|
|
|
2393
2541
|
if (result instanceof Response) {
|
|
2394
|
-
// Process redirects
|
|
2395
2542
|
let status = result.status;
|
|
2396
|
-
let location = result.headers.get("Location");
|
|
2397
2543
|
|
|
2398
|
-
//
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2544
|
+
// Process redirects
|
|
2545
|
+
if (status >= 300 && status <= 399) {
|
|
2546
|
+
let location = result.headers.get("Location");
|
|
2547
|
+
invariant(
|
|
2548
|
+
location,
|
|
2549
|
+
"Redirects returned/thrown from loaders/actions must have a Location header"
|
|
2550
|
+
);
|
|
2551
|
+
|
|
2552
|
+
// Support relative routing in redirects
|
|
2553
|
+
let activeMatches = matches.slice(0, matches.indexOf(match) + 1);
|
|
2554
|
+
let routePathnames = getPathContributingMatches(activeMatches).map(
|
|
2555
|
+
(match) => match.pathnameBase
|
|
2556
|
+
);
|
|
2557
|
+
let requestPath = createURL(request.url).pathname;
|
|
2558
|
+
let resolvedLocation = resolveTo(location, routePathnames, requestPath);
|
|
2559
|
+
invariant(
|
|
2560
|
+
createPath(resolvedLocation),
|
|
2561
|
+
`Unable to resolve redirect location: ${result.headers.get("Location")}`
|
|
2562
|
+
);
|
|
2563
|
+
|
|
2564
|
+
// Prepend the basename to the redirect location if we have one
|
|
2565
|
+
if (basename) {
|
|
2566
|
+
let path = resolvedLocation.pathname;
|
|
2567
|
+
resolvedLocation.pathname =
|
|
2568
|
+
path === "/" ? basename : joinPaths([basename, path]);
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
location = createPath(resolvedLocation);
|
|
2403
2572
|
|
|
2404
|
-
|
|
2405
|
-
// Don't process redirects in the router during SSR document requests.
|
|
2573
|
+
// Don't process redirects in the router during static requests requests.
|
|
2406
2574
|
// Instead, throw the Response and let the server handle it with an HTTP
|
|
2407
|
-
// redirect
|
|
2408
|
-
|
|
2575
|
+
// redirect. We also update the Location header in place in this flow so
|
|
2576
|
+
// basename and relative routing is taken into account
|
|
2577
|
+
if (isStaticRequest) {
|
|
2578
|
+
result.headers.set("Location", location);
|
|
2409
2579
|
throw result;
|
|
2410
2580
|
}
|
|
2581
|
+
|
|
2411
2582
|
return {
|
|
2412
2583
|
type: ResultType.redirect,
|
|
2413
2584
|
status,
|
|
@@ -2416,6 +2587,17 @@ async function callLoaderOrAction(
|
|
|
2416
2587
|
};
|
|
2417
2588
|
}
|
|
2418
2589
|
|
|
2590
|
+
// For SSR single-route requests, we want to hand Responses back directly
|
|
2591
|
+
// without unwrapping. We do this with the QueryRouteResponse wrapper
|
|
2592
|
+
// interface so we can know whether it was returned or thrown
|
|
2593
|
+
if (isRouteRequest) {
|
|
2594
|
+
// eslint-disable-next-line no-throw-literal
|
|
2595
|
+
throw {
|
|
2596
|
+
type: resultType || ResultType.data,
|
|
2597
|
+
response: result,
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2419
2601
|
let data: any;
|
|
2420
2602
|
let contentType = result.headers.get("Content-Type");
|
|
2421
2603
|
if (contentType && contentType.startsWith("application/json")) {
|
|
@@ -2456,7 +2638,7 @@ function createRequest(
|
|
|
2456
2638
|
signal: AbortSignal,
|
|
2457
2639
|
submission?: Submission
|
|
2458
2640
|
): Request {
|
|
2459
|
-
let url = createURL(location).toString();
|
|
2641
|
+
let url = createURL(stripHashFromPath(location)).toString();
|
|
2460
2642
|
let init: RequestInit = { signal };
|
|
2461
2643
|
|
|
2462
2644
|
if (submission) {
|
|
@@ -2669,16 +2851,18 @@ function findNearestBoundary(
|
|
|
2669
2851
|
);
|
|
2670
2852
|
}
|
|
2671
2853
|
|
|
2672
|
-
function
|
|
2854
|
+
function getShortCircuitMatches(
|
|
2855
|
+
routes: AgnosticDataRouteObject[],
|
|
2856
|
+
status: number,
|
|
2857
|
+
statusText: string
|
|
2858
|
+
): {
|
|
2673
2859
|
matches: AgnosticDataRouteMatch[];
|
|
2674
2860
|
route: AgnosticDataRouteObject;
|
|
2675
2861
|
error: ErrorResponse;
|
|
2676
2862
|
} {
|
|
2677
2863
|
// Prefer a root layout route if present, otherwise shim in a route object
|
|
2678
|
-
let route = routes.find(
|
|
2679
|
-
|
|
2680
|
-
) || {
|
|
2681
|
-
id: "__shim-404-route__",
|
|
2864
|
+
let route = routes.find((r) => r.index || !r.path || r.path === "/") || {
|
|
2865
|
+
id: `__shim-${status}-route__`,
|
|
2682
2866
|
};
|
|
2683
2867
|
|
|
2684
2868
|
return {
|
|
@@ -2691,12 +2875,20 @@ function getNotFoundMatches(routes: AgnosticDataRouteObject[]): {
|
|
|
2691
2875
|
},
|
|
2692
2876
|
],
|
|
2693
2877
|
route,
|
|
2694
|
-
error: new ErrorResponse(
|
|
2878
|
+
error: new ErrorResponse(status, statusText, null),
|
|
2695
2879
|
};
|
|
2696
2880
|
}
|
|
2697
2881
|
|
|
2882
|
+
function getNotFoundMatches(routes: AgnosticDataRouteObject[]) {
|
|
2883
|
+
return getShortCircuitMatches(routes, 404, "Not Found");
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
function getMethodNotAllowedMatches(routes: AgnosticDataRouteObject[]) {
|
|
2887
|
+
return getShortCircuitMatches(routes, 405, "Method Not Allowed");
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2698
2890
|
function getMethodNotAllowedResult(path: Location | string): ErrorResult {
|
|
2699
|
-
let href = typeof path === "string" ? path :
|
|
2891
|
+
let href = typeof path === "string" ? path : createPath(path);
|
|
2700
2892
|
console.warn(
|
|
2701
2893
|
"You're trying to submit to a route that does not have an action. To " +
|
|
2702
2894
|
"fix this, please add an `action` function to the route for " +
|
|
@@ -2704,11 +2896,7 @@ function getMethodNotAllowedResult(path: Location | string): ErrorResult {
|
|
|
2704
2896
|
);
|
|
2705
2897
|
return {
|
|
2706
2898
|
type: ResultType.error,
|
|
2707
|
-
error: new ErrorResponse(
|
|
2708
|
-
405,
|
|
2709
|
-
"Method Not Allowed",
|
|
2710
|
-
`No action found for [${href}]`
|
|
2711
|
-
),
|
|
2899
|
+
error: new ErrorResponse(405, "Method Not Allowed", ""),
|
|
2712
2900
|
};
|
|
2713
2901
|
}
|
|
2714
2902
|
|
|
@@ -2722,9 +2910,9 @@ function findRedirect(results: DataResult[]): RedirectResult | undefined {
|
|
|
2722
2910
|
}
|
|
2723
2911
|
}
|
|
2724
2912
|
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
return (
|
|
2913
|
+
function stripHashFromPath(path: To) {
|
|
2914
|
+
let parsedPath = typeof path === "string" ? parsePath(path) : path;
|
|
2915
|
+
return createPath({ ...parsedPath, hash: "" });
|
|
2728
2916
|
}
|
|
2729
2917
|
|
|
2730
2918
|
function isHashChangeOnly(a: Location, b: Location): boolean {
|
|
@@ -2745,6 +2933,24 @@ function isRedirectResult(result?: DataResult): result is RedirectResult {
|
|
|
2745
2933
|
return (result && result.type) === ResultType.redirect;
|
|
2746
2934
|
}
|
|
2747
2935
|
|
|
2936
|
+
function isRedirectResponse(result: any): result is Response {
|
|
2937
|
+
if (!(result instanceof Response)) {
|
|
2938
|
+
return false;
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
let status = result.status;
|
|
2942
|
+
let location = result.headers.get("Location");
|
|
2943
|
+
return status >= 300 && status <= 399 && location != null;
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
function isQueryRouteResponse(obj: any): obj is QueryRouteResponse {
|
|
2947
|
+
return (
|
|
2948
|
+
obj &&
|
|
2949
|
+
obj.response instanceof Response &&
|
|
2950
|
+
(obj.type === ResultType.data || ResultType.error)
|
|
2951
|
+
);
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2748
2954
|
async function resolveDeferredResults(
|
|
2749
2955
|
currentMatches: AgnosticDataRouteMatch[],
|
|
2750
2956
|
matchesToLoad: AgnosticDataRouteMatch[],
|
|
@@ -2836,19 +3042,14 @@ function getTargetMatch(
|
|
|
2836
3042
|
typeof location === "string" ? parsePath(location).search : location.search;
|
|
2837
3043
|
if (
|
|
2838
3044
|
matches[matches.length - 1].route.index &&
|
|
2839
|
-
|
|
3045
|
+
hasNakedIndexQuery(search || "")
|
|
2840
3046
|
) {
|
|
2841
|
-
|
|
3047
|
+
// Return the leaf index route when index is present
|
|
3048
|
+
return matches[matches.length - 1];
|
|
2842
3049
|
}
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
let base =
|
|
2848
|
-
typeof window !== "undefined" && typeof window.location !== "undefined"
|
|
2849
|
-
? window.location.origin
|
|
2850
|
-
: "unknown://unknown";
|
|
2851
|
-
let href = typeof location === "string" ? location : createHref(location);
|
|
2852
|
-
return new URL(href, base);
|
|
3050
|
+
// Otherwise grab the deepest "path contributing" match (ignoring index and
|
|
3051
|
+
// pathless layout routes)
|
|
3052
|
+
let pathMatches = getPathContributingMatches(matches);
|
|
3053
|
+
return pathMatches[pathMatches.length - 1];
|
|
2853
3054
|
}
|
|
2854
3055
|
//#endregion
|