@remix-run/router 1.3.3-pre.1 → 1.4.0-pre.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -4
- package/dist/history.d.ts +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/router.cjs.js +220 -90
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +6 -3
- package/dist/router.js +216 -90
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +220 -90
- 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 +25 -6
- package/history.ts +1 -1
- package/index.ts +6 -2
- package/package.json +1 -1
- package/router.ts +239 -30
- package/utils.ts +102 -69
package/CHANGELOG.md
CHANGED
|
@@ -1,17 +1,78 @@
|
|
|
1
1
|
# `@remix-run/router`
|
|
2
2
|
|
|
3
|
-
## 1.
|
|
3
|
+
## 1.4.0-pre.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **Introducing Lazy Route Modules!** ([#10045](https://github.com/remix-run/react-router/pull/10045))
|
|
8
|
+
|
|
9
|
+
In order to keep your application bundles small and support code-splitting of your routes, we've introduced a new `lazy()` route property. This is an async function that resolves the non-route-matching portions of your route definition (`loader`, `action`, `element`/`Component`, `errorElement`/`ErrorBoundary`, `shouldRevalidate`, `handle`).
|
|
10
|
+
|
|
11
|
+
Lazy routes are resolved on initial load and during the `loading` or `submitting` phase of a navigation or fetcher call. You cannot lazily define route-matching properties (`path`, `index`, `children`) since we only execute your lazy route functions after we've matched known routes.
|
|
12
|
+
|
|
13
|
+
Your `lazy` functions will typically return the result of a dynamic import.
|
|
14
|
+
|
|
15
|
+
```jsx
|
|
16
|
+
// In this example, we assume most folks land on the homepage so we include that
|
|
17
|
+
// in our critical-path bundle, but then we lazily load modules for /a and /b so
|
|
18
|
+
// they don't load until the user navigates to those routes
|
|
19
|
+
let routes = createRoutesFromElements(
|
|
20
|
+
<Route path="/" element={<Layout />}>
|
|
21
|
+
<Route index element={<Home />} />
|
|
22
|
+
<Route path="a" lazy={() => import("./a")} />
|
|
23
|
+
<Route path="b" lazy={() => import("./b")} />
|
|
24
|
+
</Route>
|
|
25
|
+
);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then in your lazy route modules, export the properties you want defined for the route:
|
|
29
|
+
|
|
30
|
+
```jsx
|
|
31
|
+
export async function loader({ request }) {
|
|
32
|
+
let data = await fetchData(request);
|
|
33
|
+
return json(data);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Export a `Component` directly instead of needing to create a React Element from it
|
|
37
|
+
export function Component() {
|
|
38
|
+
let data = useLoaderData();
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
<h1>You made it!</h1>
|
|
43
|
+
<p>{data}</p>
|
|
44
|
+
</>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Export an `ErrorBoundary` directly instead of needing to create a React Element from it
|
|
49
|
+
export function ErrorBoundary() {
|
|
50
|
+
let error = useRouteError();
|
|
51
|
+
return isRouteErrorResponse(error) ? (
|
|
52
|
+
<h1>
|
|
53
|
+
{error.status} {error.statusText}
|
|
54
|
+
</h1>
|
|
55
|
+
) : (
|
|
56
|
+
<h1>{error.message || error}</h1>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
An example of this in action can be found in the [`examples/lazy-loading-router-provider`](https://github.com/remix-run/react-router/tree/main/examples/lazy-loading-router-provider) directory of the repository.
|
|
62
|
+
|
|
63
|
+
🙌 Huge thanks to @rossipedia for the [Initial Proposal](https://github.com/remix-run/react-router/discussions/9826) and [POC Implementation](https://github.com/remix-run/react-router/pull/9830).
|
|
4
64
|
|
|
5
65
|
### Patch Changes
|
|
6
66
|
|
|
7
|
-
-
|
|
67
|
+
- Fix `generatePath` incorrectly applying parameters in some cases ([`bc6fefa1`](https://github.com/remix-run/react-router/commit/bc6fefa19019ce9f5250c8b5af9b8c5d3390e9d1))
|
|
8
68
|
|
|
9
|
-
## 1.3.3
|
|
69
|
+
## 1.3.3
|
|
10
70
|
|
|
11
71
|
### Patch Changes
|
|
12
72
|
|
|
13
|
-
-
|
|
73
|
+
- Correctly perform a hard redirect for same-origin absolute URLs outside of the router `basename` ([#10076](https://github.com/remix-run/react-router/pull/10076))
|
|
14
74
|
- Ensure status code and headers are maintained for `defer` loader responses in `createStaticHandler`'s `query()` method ([#10077](https://github.com/remix-run/react-router/pull/10077))
|
|
75
|
+
- Change `invariant` to an `UNSAFE_invariant` export since it's only intended for internal use ([#10066](https://github.com/remix-run/react-router/pull/10066))
|
|
15
76
|
- Add internal API for custom HMR implementations ([#9996](https://github.com/remix-run/react-router/pull/9996))
|
|
16
77
|
|
|
17
78
|
## 1.3.2
|
package/dist/history.d.ts
CHANGED
|
@@ -229,6 +229,7 @@ export declare function createHashHistory(options?: HashHistoryOptions): HashHis
|
|
|
229
229
|
*/
|
|
230
230
|
export declare function invariant(value: boolean, message?: string): asserts value;
|
|
231
231
|
export declare function invariant<T>(value: T | null | undefined, message?: string): asserts value is T;
|
|
232
|
+
export declare function warning(cond: any, message: string): void;
|
|
232
233
|
/**
|
|
233
234
|
* Creates a Location object with a unique key from the given Path
|
|
234
235
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export type { ActionFunction, ActionFunctionArgs, AgnosticDataIndexRouteObject, AgnosticDataNonIndexRouteObject, AgnosticDataRouteMatch, AgnosticDataRouteObject, AgnosticIndexRouteObject, AgnosticNonIndexRouteObject, AgnosticRouteMatch, AgnosticRouteObject, TrackedPromise, FormEncType, FormMethod, JsonFunction, LoaderFunction, LoaderFunctionArgs, ParamParseKey, Params, PathMatch, PathPattern, RedirectFunction, ShouldRevalidateFunction, Submission, } from "./utils";
|
|
2
|
-
export { AbortedDeferredError, ErrorResponse, defer, generatePath, getToPathname, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, redirect, resolvePath, resolveTo, stripBasename,
|
|
1
|
+
export type { ActionFunction, ActionFunctionArgs, AgnosticDataIndexRouteObject, AgnosticDataNonIndexRouteObject, AgnosticDataRouteMatch, AgnosticDataRouteObject, AgnosticIndexRouteObject, AgnosticNonIndexRouteObject, AgnosticRouteMatch, AgnosticRouteObject, LazyRouteFunction, TrackedPromise, FormEncType, FormMethod, JsonFunction, LoaderFunction, LoaderFunctionArgs, ParamParseKey, Params, PathMatch, PathPattern, RedirectFunction, ShouldRevalidateFunction, Submission, } from "./utils";
|
|
2
|
+
export { AbortedDeferredError, ErrorResponse, defer, generatePath, getToPathname, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, redirect, resolvePath, resolveTo, stripBasename, } from "./utils";
|
|
3
3
|
export type { BrowserHistory, BrowserHistoryOptions, HashHistory, HashHistoryOptions, History, InitialEntry, Location, MemoryHistory, MemoryHistoryOptions, Path, To, } from "./history";
|
|
4
4
|
export { Action, createBrowserHistory, createPath, createHashHistory, createMemoryHistory, parsePath, } from "./history";
|
|
5
5
|
export * from "./router";
|
|
6
6
|
/** @internal */
|
|
7
|
+
export type { RouteManifest as UNSAFE_RouteManifest } from "./utils";
|
|
7
8
|
export { DeferredData as UNSAFE_DeferredData, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, getPathContributingMatches as UNSAFE_getPathContributingMatches, } from "./utils";
|
|
8
|
-
export { invariant as UNSAFE_invariant } from "./history";
|
|
9
|
+
export { invariant as UNSAFE_invariant, warning as UNSAFE_warning, } from "./history";
|
package/dist/router.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @remix-run/router v1.
|
|
2
|
+
* @remix-run/router v1.4.0-pre.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -92,7 +92,7 @@ function createMemoryHistory(options) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
let location = createLocation(entries ? getCurrentLocation().pathname : "/", to, state, key);
|
|
95
|
-
warning
|
|
95
|
+
warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to));
|
|
96
96
|
return location;
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -283,7 +283,7 @@ function createHashHistory(options) {
|
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
function validateHashLocation(location, to) {
|
|
286
|
-
warning
|
|
286
|
+
warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")");
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);
|
|
@@ -301,8 +301,7 @@ function invariant(value, message) {
|
|
|
301
301
|
throw new Error(message);
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
|
-
|
|
305
|
-
function warning$1(cond, message) {
|
|
304
|
+
function warning(cond, message) {
|
|
306
305
|
if (!cond) {
|
|
307
306
|
// eslint-disable-next-line no-console
|
|
308
307
|
if (typeof console !== "undefined") console.warn(message);
|
|
@@ -565,40 +564,54 @@ let ResultType;
|
|
|
565
564
|
ResultType["error"] = "error";
|
|
566
565
|
})(ResultType || (ResultType = {}));
|
|
567
566
|
|
|
567
|
+
const immutableRouteKeys = new Set(["lazy", "caseSensitive", "path", "id", "index", "children"]);
|
|
568
|
+
/**
|
|
569
|
+
* lazy() function to load a route definition, which can add non-matching
|
|
570
|
+
* related properties to a route
|
|
571
|
+
*/
|
|
572
|
+
|
|
568
573
|
function isIndexRoute(route) {
|
|
569
574
|
return route.index === true;
|
|
570
575
|
} // Walk the route tree generating unique IDs where necessary so we are working
|
|
571
576
|
// solely with AgnosticDataRouteObject's within the Router
|
|
572
577
|
|
|
573
578
|
|
|
574
|
-
function convertRoutesToDataRoutes(routes, parentPath,
|
|
579
|
+
function convertRoutesToDataRoutes(routes, detectErrorBoundary, parentPath, manifest) {
|
|
575
580
|
if (parentPath === void 0) {
|
|
576
581
|
parentPath = [];
|
|
577
582
|
}
|
|
578
583
|
|
|
579
|
-
if (
|
|
580
|
-
|
|
584
|
+
if (manifest === void 0) {
|
|
585
|
+
manifest = {};
|
|
581
586
|
}
|
|
582
587
|
|
|
583
588
|
return routes.map((route, index) => {
|
|
584
589
|
let treePath = [...parentPath, index];
|
|
585
590
|
let id = typeof route.id === "string" ? route.id : treePath.join("-");
|
|
586
591
|
invariant(route.index !== true || !route.children, "Cannot specify children on an index route");
|
|
587
|
-
invariant(!
|
|
588
|
-
allIds.add(id);
|
|
592
|
+
invariant(!manifest[id], "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
|
|
589
593
|
|
|
590
594
|
if (isIndexRoute(route)) {
|
|
591
595
|
let indexRoute = _extends({}, route, {
|
|
596
|
+
hasErrorBoundary: detectErrorBoundary(route),
|
|
592
597
|
id
|
|
593
598
|
});
|
|
594
599
|
|
|
600
|
+
manifest[id] = indexRoute;
|
|
595
601
|
return indexRoute;
|
|
596
602
|
} else {
|
|
597
603
|
let pathOrLayoutRoute = _extends({}, route, {
|
|
598
604
|
id,
|
|
599
|
-
|
|
605
|
+
hasErrorBoundary: detectErrorBoundary(route),
|
|
606
|
+
children: undefined
|
|
600
607
|
});
|
|
601
608
|
|
|
609
|
+
manifest[id] = pathOrLayoutRoute;
|
|
610
|
+
|
|
611
|
+
if (route.children) {
|
|
612
|
+
pathOrLayoutRoute.children = convertRoutesToDataRoutes(route.children, detectErrorBoundary, treePath, manifest);
|
|
613
|
+
}
|
|
614
|
+
|
|
602
615
|
return pathOrLayoutRoute;
|
|
603
616
|
}
|
|
604
617
|
});
|
|
@@ -845,45 +858,42 @@ function generatePath(originalPath, params) {
|
|
|
845
858
|
if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
|
|
846
859
|
warning(false, "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
|
|
847
860
|
path = path.replace(/\*$/, "/*");
|
|
848
|
-
}
|
|
861
|
+
} // ensure `/` is added at the beginning if the path is absolute
|
|
849
862
|
|
|
850
|
-
return path.replace(/^:(\w+)(\??)/g, (_, key, optional) => {
|
|
851
|
-
let param = params[key];
|
|
852
863
|
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
864
|
+
const prefix = path.startsWith("/") ? "/" : "";
|
|
865
|
+
const segments = path.split(/\/+/).map((segment, index, array) => {
|
|
866
|
+
const isLastSegment = index === array.length - 1; // only apply the splat if it's the last segment
|
|
867
|
+
|
|
868
|
+
if (isLastSegment && segment === "*") {
|
|
869
|
+
const star = "*";
|
|
870
|
+
const starParam = params[star]; // Apply the splat
|
|
856
871
|
|
|
857
|
-
|
|
858
|
-
invariant(false, "Missing \":" + key + "\" param");
|
|
872
|
+
return starParam;
|
|
859
873
|
}
|
|
860
874
|
|
|
861
|
-
|
|
862
|
-
}).replace(/\/:(\w+)(\??)/g, (_, key, optional) => {
|
|
863
|
-
let param = params[key];
|
|
875
|
+
const keyMatch = segment.match(/^:(\w+)(\??)$/);
|
|
864
876
|
|
|
865
|
-
if (
|
|
866
|
-
|
|
867
|
-
|
|
877
|
+
if (keyMatch) {
|
|
878
|
+
const [, key, optional] = keyMatch;
|
|
879
|
+
let param = params[key];
|
|
868
880
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
881
|
+
if (optional === "?") {
|
|
882
|
+
return param == null ? "" : param;
|
|
883
|
+
}
|
|
872
884
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
const star = "*";
|
|
885
|
+
if (param == null) {
|
|
886
|
+
invariant(false, "Missing \":" + key + "\" param");
|
|
887
|
+
}
|
|
877
888
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
// the entire path
|
|
881
|
-
return str === "/*" ? "/" : "";
|
|
882
|
-
} // Apply the splat
|
|
889
|
+
return param;
|
|
890
|
+
} // Remove any optional markers from optional static segments
|
|
883
891
|
|
|
884
892
|
|
|
885
|
-
return ""
|
|
886
|
-
})
|
|
893
|
+
return segment.replace(/\?$/g, "");
|
|
894
|
+
}) // Remove empty segments
|
|
895
|
+
.filter(segment => !!segment);
|
|
896
|
+
return prefix + segments.join("/");
|
|
887
897
|
}
|
|
888
898
|
/**
|
|
889
899
|
* A PathPattern is used to match on some portion of a URL pathname.
|
|
@@ -1011,25 +1021,6 @@ function stripBasename(pathname, basename) {
|
|
|
1011
1021
|
|
|
1012
1022
|
return pathname.slice(startIndex) || "/";
|
|
1013
1023
|
}
|
|
1014
|
-
/**
|
|
1015
|
-
* @private
|
|
1016
|
-
*/
|
|
1017
|
-
|
|
1018
|
-
function warning(cond, message) {
|
|
1019
|
-
if (!cond) {
|
|
1020
|
-
// eslint-disable-next-line no-console
|
|
1021
|
-
if (typeof console !== "undefined") console.warn(message);
|
|
1022
|
-
|
|
1023
|
-
try {
|
|
1024
|
-
// Welcome to debugging @remix-run/router!
|
|
1025
|
-
//
|
|
1026
|
-
// This error is thrown as a convenience so you can more easily
|
|
1027
|
-
// find the source for a warning that appears in the console by
|
|
1028
|
-
// enabling "pause on exceptions" in your JavaScript debugger.
|
|
1029
|
-
throw new Error(message); // eslint-disable-next-line no-empty
|
|
1030
|
-
} catch (e) {}
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
1024
|
/**
|
|
1034
1025
|
* Returns a resolved path object relative to the given pathname.
|
|
1035
1026
|
*
|
|
@@ -1478,7 +1469,9 @@ const IDLE_BLOCKER = {
|
|
|
1478
1469
|
};
|
|
1479
1470
|
const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
|
|
1480
1471
|
const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
|
|
1481
|
-
const isServer = !isBrowser;
|
|
1472
|
+
const isServer = !isBrowser;
|
|
1473
|
+
|
|
1474
|
+
const defaultDetectErrorBoundary = route => Boolean(route.hasErrorBoundary); //#endregion
|
|
1482
1475
|
////////////////////////////////////////////////////////////////////////////////
|
|
1483
1476
|
//#region createRouter
|
|
1484
1477
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -1487,9 +1480,14 @@ const isServer = !isBrowser; //#endregion
|
|
|
1487
1480
|
* Create a router and listen to history POP navigations
|
|
1488
1481
|
*/
|
|
1489
1482
|
|
|
1483
|
+
|
|
1490
1484
|
function createRouter(init) {
|
|
1491
1485
|
invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
|
|
1492
|
-
let
|
|
1486
|
+
let detectErrorBoundary = init.detectErrorBoundary || defaultDetectErrorBoundary; // Routes keyed by ID
|
|
1487
|
+
|
|
1488
|
+
let manifest = {}; // Routes in tree format for matching
|
|
1489
|
+
|
|
1490
|
+
let dataRoutes = convertRoutesToDataRoutes(init.routes, detectErrorBoundary, undefined, manifest);
|
|
1493
1491
|
let inFlightDataRoutes; // Cleanup function for history
|
|
1494
1492
|
|
|
1495
1493
|
let unlistenHistory = null; // Externally-provided functions to call on all state changes
|
|
@@ -1527,7 +1525,10 @@ function createRouter(init) {
|
|
|
1527
1525
|
};
|
|
1528
1526
|
}
|
|
1529
1527
|
|
|
1530
|
-
let initialized =
|
|
1528
|
+
let initialized = // All initialMatches need to be loaded before we're ready. If we have lazy
|
|
1529
|
+
// functions around still then we'll need to run them in initialize()
|
|
1530
|
+
!initialMatches.some(m => m.route.lazy) && ( // And we have to either have no loaders or have been provided hydrationData
|
|
1531
|
+
!initialMatches.some(m => m.route.loader) || init.hydrationData != null);
|
|
1531
1532
|
let router;
|
|
1532
1533
|
let state = {
|
|
1533
1534
|
historyAction: init.history.action,
|
|
@@ -1651,12 +1652,35 @@ function createRouter(init) {
|
|
|
1651
1652
|
}
|
|
1652
1653
|
|
|
1653
1654
|
return startNavigation(historyAction, location);
|
|
1654
|
-
});
|
|
1655
|
+
});
|
|
1655
1656
|
|
|
1656
|
-
if (
|
|
1657
|
-
|
|
1657
|
+
if (state.initialized) {
|
|
1658
|
+
return router;
|
|
1658
1659
|
}
|
|
1659
1660
|
|
|
1661
|
+
let lazyMatches = state.matches.filter(m => m.route.lazy);
|
|
1662
|
+
|
|
1663
|
+
if (lazyMatches.length === 0) {
|
|
1664
|
+
// Kick off initial data load if needed. Use Pop to avoid modifying history
|
|
1665
|
+
startNavigation(exports.Action.Pop, state.location);
|
|
1666
|
+
return router;
|
|
1667
|
+
} // Load lazy modules, then kick off initial data load if needed
|
|
1668
|
+
|
|
1669
|
+
|
|
1670
|
+
let lazyPromises = lazyMatches.map(m => loadLazyRouteModule(m.route, detectErrorBoundary, manifest));
|
|
1671
|
+
Promise.all(lazyPromises).then(() => {
|
|
1672
|
+
let initialized = !state.matches.some(m => m.route.loader) || init.hydrationData != null;
|
|
1673
|
+
|
|
1674
|
+
if (initialized) {
|
|
1675
|
+
// We already have required loaderData so we can just set initialized
|
|
1676
|
+
updateState({
|
|
1677
|
+
initialized: true
|
|
1678
|
+
});
|
|
1679
|
+
} else {
|
|
1680
|
+
// We still need to kick off initial data loads
|
|
1681
|
+
startNavigation(exports.Action.Pop, state.location);
|
|
1682
|
+
}
|
|
1683
|
+
});
|
|
1660
1684
|
return router;
|
|
1661
1685
|
} // Clean up a router and it's side effects
|
|
1662
1686
|
|
|
@@ -2002,7 +2026,7 @@ function createRouter(init) {
|
|
|
2002
2026
|
let result;
|
|
2003
2027
|
let actionMatch = getTargetMatch(matches, location);
|
|
2004
2028
|
|
|
2005
|
-
if (!actionMatch.route.action) {
|
|
2029
|
+
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
2006
2030
|
result = {
|
|
2007
2031
|
type: ResultType.error,
|
|
2008
2032
|
error: getInternalRouterError(405, {
|
|
@@ -2012,7 +2036,7 @@ function createRouter(init) {
|
|
|
2012
2036
|
})
|
|
2013
2037
|
};
|
|
2014
2038
|
} else {
|
|
2015
|
-
result = await callLoaderOrAction("action", request, actionMatch, matches, router.basename);
|
|
2039
|
+
result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, detectErrorBoundary, router.basename);
|
|
2016
2040
|
|
|
2017
2041
|
if (request.signal.aborted) {
|
|
2018
2042
|
return {
|
|
@@ -2258,7 +2282,7 @@ function createRouter(init) {
|
|
|
2258
2282
|
interruptActiveLoads();
|
|
2259
2283
|
fetchLoadMatches.delete(key);
|
|
2260
2284
|
|
|
2261
|
-
if (!match.route.action) {
|
|
2285
|
+
if (!match.route.action && !match.route.lazy) {
|
|
2262
2286
|
let error = getInternalRouterError(405, {
|
|
2263
2287
|
method: submission.formMethod,
|
|
2264
2288
|
pathname: path,
|
|
@@ -2286,7 +2310,7 @@ function createRouter(init) {
|
|
|
2286
2310
|
let abortController = new AbortController();
|
|
2287
2311
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
|
|
2288
2312
|
fetchControllers.set(key, abortController);
|
|
2289
|
-
let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, router.basename);
|
|
2313
|
+
let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, detectErrorBoundary, router.basename);
|
|
2290
2314
|
|
|
2291
2315
|
if (fetchRequest.signal.aborted) {
|
|
2292
2316
|
// We can delete this so long as we weren't aborted by ou our own fetcher
|
|
@@ -2457,7 +2481,7 @@ function createRouter(init) {
|
|
|
2457
2481
|
let abortController = new AbortController();
|
|
2458
2482
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
|
|
2459
2483
|
fetchControllers.set(key, abortController);
|
|
2460
|
-
let result = await callLoaderOrAction("loader", fetchRequest, match, matches, router.basename); // Deferred isn't supported for fetcher loads, await everything and treat it
|
|
2484
|
+
let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, detectErrorBoundary, router.basename); // Deferred isn't supported for fetcher loads, await everything and treat it
|
|
2461
2485
|
// as a normal load. resolveDeferredData will return undefined if this
|
|
2462
2486
|
// fetcher gets aborted, so we just leave result untouched and short circuit
|
|
2463
2487
|
// below if that happens
|
|
@@ -2626,9 +2650,9 @@ function createRouter(init) {
|
|
|
2626
2650
|
// Call all navigation loaders and revalidating fetcher loaders in parallel,
|
|
2627
2651
|
// then slice off the results into separate arrays so we can handle them
|
|
2628
2652
|
// accordingly
|
|
2629
|
-
let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(f => {
|
|
2653
|
+
let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, detectErrorBoundary, router.basename)), ...fetchersToLoad.map(f => {
|
|
2630
2654
|
if (f.matches && f.match) {
|
|
2631
|
-
return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, router.basename);
|
|
2655
|
+
return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, manifest, detectErrorBoundary, router.basename);
|
|
2632
2656
|
} else {
|
|
2633
2657
|
let error = {
|
|
2634
2658
|
type: ResultType.error,
|
|
@@ -2920,7 +2944,9 @@ function createRouter(init) {
|
|
|
2920
2944
|
const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
|
|
2921
2945
|
function createStaticHandler(routes, opts) {
|
|
2922
2946
|
invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
|
|
2923
|
-
let
|
|
2947
|
+
let manifest = {};
|
|
2948
|
+
let detectErrorBoundary = (opts == null ? void 0 : opts.detectErrorBoundary) || defaultDetectErrorBoundary;
|
|
2949
|
+
let dataRoutes = convertRoutesToDataRoutes(routes, detectErrorBoundary, undefined, manifest);
|
|
2924
2950
|
let basename = (opts ? opts.basename : null) || "/";
|
|
2925
2951
|
/**
|
|
2926
2952
|
* The query() method is intended for document requests, in which we want to
|
|
@@ -3142,7 +3168,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3142
3168
|
async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
|
|
3143
3169
|
let result;
|
|
3144
3170
|
|
|
3145
|
-
if (!actionMatch.route.action) {
|
|
3171
|
+
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
3146
3172
|
let error = getInternalRouterError(405, {
|
|
3147
3173
|
method: request.method,
|
|
3148
3174
|
pathname: new URL(request.url).pathname,
|
|
@@ -3158,7 +3184,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3158
3184
|
error
|
|
3159
3185
|
};
|
|
3160
3186
|
} else {
|
|
3161
|
-
result = await callLoaderOrAction("action", request, actionMatch, matches, basename, true, isRouteRequest, requestContext);
|
|
3187
|
+
result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, detectErrorBoundary, basename, true, isRouteRequest, requestContext);
|
|
3162
3188
|
|
|
3163
3189
|
if (request.signal.aborted) {
|
|
3164
3190
|
let method = isRouteRequest ? "queryRoute" : "query";
|
|
@@ -3256,7 +3282,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3256
3282
|
async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
|
|
3257
3283
|
let isRouteRequest = routeMatch != null; // Short circuit if we have no loaders to run (queryRoute())
|
|
3258
3284
|
|
|
3259
|
-
if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader)) {
|
|
3285
|
+
if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
|
|
3260
3286
|
throw getInternalRouterError(400, {
|
|
3261
3287
|
method: request.method,
|
|
3262
3288
|
pathname: new URL(request.url).pathname,
|
|
@@ -3265,7 +3291,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3265
3291
|
}
|
|
3266
3292
|
|
|
3267
3293
|
let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches, Object.keys(pendingActionError || {})[0]);
|
|
3268
|
-
let matchesToLoad = requestMatches.filter(m => m.route.loader); // Short circuit if we have no loaders to run (query())
|
|
3294
|
+
let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy); // Short circuit if we have no loaders to run (query())
|
|
3269
3295
|
|
|
3270
3296
|
if (matchesToLoad.length === 0) {
|
|
3271
3297
|
return {
|
|
@@ -3281,7 +3307,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3281
3307
|
};
|
|
3282
3308
|
}
|
|
3283
3309
|
|
|
3284
|
-
let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, basename, true, isRouteRequest, requestContext))]);
|
|
3310
|
+
let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, detectErrorBoundary, basename, true, isRouteRequest, requestContext))]);
|
|
3285
3311
|
|
|
3286
3312
|
if (request.signal.aborted) {
|
|
3287
3313
|
let method = isRouteRequest ? "queryRoute" : "query";
|
|
@@ -3422,6 +3448,11 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
|
|
|
3422
3448
|
let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
|
|
3423
3449
|
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
3424
3450
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
3451
|
+
if (match.route.lazy) {
|
|
3452
|
+
// We haven't loaded this route yet so we don't know if it's got a loader!
|
|
3453
|
+
return true;
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3425
3456
|
if (match.route.loader == null) {
|
|
3426
3457
|
return false;
|
|
3427
3458
|
} // Always call the loader on new route instances and pending defer cancellations
|
|
@@ -3535,8 +3566,66 @@ function shouldRevalidateLoader(loaderMatch, arg) {
|
|
|
3535
3566
|
|
|
3536
3567
|
return arg.defaultShouldRevalidate;
|
|
3537
3568
|
}
|
|
3569
|
+
/**
|
|
3570
|
+
* Execute route.lazy() methods to lazily load route modules (loader, action,
|
|
3571
|
+
* shouldRevalidate) and update the routeManifest in place which shares objects
|
|
3572
|
+
* with dataRoutes so those get updated as well.
|
|
3573
|
+
*/
|
|
3574
|
+
|
|
3575
|
+
|
|
3576
|
+
async function loadLazyRouteModule(route, detectErrorBoundary, manifest) {
|
|
3577
|
+
if (!route.lazy) {
|
|
3578
|
+
return;
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
let lazyRoute = await route.lazy(); // If the lazy route function was executed and removed by another parallel
|
|
3582
|
+
// call then we can return - first lazy() to finish wins because the return
|
|
3583
|
+
// value of lazy is expected to be static
|
|
3584
|
+
|
|
3585
|
+
if (!route.lazy) {
|
|
3586
|
+
return;
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3589
|
+
let routeToUpdate = manifest[route.id];
|
|
3590
|
+
invariant(routeToUpdate, "No route found in manifest"); // Update the route in place. This should be safe because there's no way
|
|
3591
|
+
// we could yet be sitting on this route as we can't get there without
|
|
3592
|
+
// resolving lazy() first.
|
|
3593
|
+
//
|
|
3594
|
+
// This is different than the HMR "update" use-case where we may actively be
|
|
3595
|
+
// on the route being updated. The main concern boils down to "does this
|
|
3596
|
+
// mutation affect any ongoing navigations or any current state.matches
|
|
3597
|
+
// values?". If not, it should be safe to update in place.
|
|
3598
|
+
|
|
3599
|
+
let routeUpdates = {};
|
|
3600
|
+
|
|
3601
|
+
for (let lazyRouteProperty in lazyRoute) {
|
|
3602
|
+
let staticRouteValue = routeToUpdate[lazyRouteProperty];
|
|
3603
|
+
let isPropertyStaticallyDefined = staticRouteValue !== undefined && // This property isn't static since it should always be updated based
|
|
3604
|
+
// on the route updates
|
|
3605
|
+
lazyRouteProperty !== "hasErrorBoundary";
|
|
3606
|
+
warning(!isPropertyStaticallyDefined, "Route \"" + routeToUpdate.id + "\" has a static property \"" + lazyRouteProperty + "\" " + "defined but its lazy function is also returning a value for this property. " + ("The lazy route property \"" + lazyRouteProperty + "\" will be ignored."));
|
|
3607
|
+
|
|
3608
|
+
if (!isPropertyStaticallyDefined && !immutableRouteKeys.has(lazyRouteProperty)) {
|
|
3609
|
+
routeUpdates[lazyRouteProperty] = lazyRoute[lazyRouteProperty];
|
|
3610
|
+
}
|
|
3611
|
+
} // Mutate the route with the provided updates. Do this first so we pass
|
|
3612
|
+
// the updated version to detectErrorBoundary
|
|
3613
|
+
|
|
3614
|
+
|
|
3615
|
+
Object.assign(routeToUpdate, routeUpdates); // Mutate the `hasErrorBoundary` property on the route based on the route
|
|
3616
|
+
// updates and remove the `lazy` function so we don't resolve the lazy
|
|
3617
|
+
// route again.
|
|
3618
|
+
|
|
3619
|
+
Object.assign(routeToUpdate, {
|
|
3620
|
+
// To keep things framework agnostic, we use the provided
|
|
3621
|
+
// `detectErrorBoundary` function to set the `hasErrorBoundary` route
|
|
3622
|
+
// property since the logic will differ between frameworks.
|
|
3623
|
+
hasErrorBoundary: detectErrorBoundary(_extends({}, routeToUpdate)),
|
|
3624
|
+
lazy: undefined
|
|
3625
|
+
});
|
|
3626
|
+
}
|
|
3538
3627
|
|
|
3539
|
-
async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest, requestContext) {
|
|
3628
|
+
async function callLoaderOrAction(type, request, match, matches, manifest, detectErrorBoundary, basename, isStaticRequest, isRouteRequest, requestContext) {
|
|
3540
3629
|
if (basename === void 0) {
|
|
3541
3630
|
basename = "/";
|
|
3542
3631
|
}
|
|
@@ -3550,29 +3639,70 @@ async function callLoaderOrAction(type, request, match, matches, basename, isSta
|
|
|
3550
3639
|
}
|
|
3551
3640
|
|
|
3552
3641
|
let resultType;
|
|
3553
|
-
let result;
|
|
3554
|
-
|
|
3555
|
-
let reject;
|
|
3556
|
-
let abortPromise = new Promise((_, r) => reject = r);
|
|
3642
|
+
let result;
|
|
3643
|
+
let onReject;
|
|
3557
3644
|
|
|
3558
|
-
let
|
|
3645
|
+
let runHandler = handler => {
|
|
3646
|
+
// Setup a promise we can race against so that abort signals short circuit
|
|
3647
|
+
let reject;
|
|
3648
|
+
let abortPromise = new Promise((_, r) => reject = r);
|
|
3559
3649
|
|
|
3560
|
-
|
|
3650
|
+
onReject = () => reject();
|
|
3561
3651
|
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
invariant(handler, "Could not find the " + type + " to run on the \"" + match.route.id + "\" route");
|
|
3565
|
-
result = await Promise.race([handler({
|
|
3652
|
+
request.signal.addEventListener("abort", onReject);
|
|
3653
|
+
return Promise.race([handler({
|
|
3566
3654
|
request,
|
|
3567
3655
|
params: match.params,
|
|
3568
3656
|
context: requestContext
|
|
3569
3657
|
}), abortPromise]);
|
|
3658
|
+
};
|
|
3659
|
+
|
|
3660
|
+
try {
|
|
3661
|
+
let handler = match.route[type];
|
|
3662
|
+
|
|
3663
|
+
if (match.route.lazy) {
|
|
3664
|
+
if (handler) {
|
|
3665
|
+
// Run statically defined handler in parallel with lazy()
|
|
3666
|
+
let values = await Promise.all([runHandler(handler), loadLazyRouteModule(match.route, detectErrorBoundary, manifest)]);
|
|
3667
|
+
result = values[0];
|
|
3668
|
+
} else {
|
|
3669
|
+
// Load lazy route module, then run any returned handler
|
|
3670
|
+
await loadLazyRouteModule(match.route, detectErrorBoundary, manifest);
|
|
3671
|
+
handler = match.route[type];
|
|
3672
|
+
|
|
3673
|
+
if (handler) {
|
|
3674
|
+
// Handler still run even if we got interrupted to maintain consistency
|
|
3675
|
+
// with un-abortable behavior of handler execution on non-lazy or
|
|
3676
|
+
// previously-lazy-loaded routes
|
|
3677
|
+
result = await runHandler(handler);
|
|
3678
|
+
} else if (type === "action") {
|
|
3679
|
+
throw getInternalRouterError(405, {
|
|
3680
|
+
method: request.method,
|
|
3681
|
+
pathname: new URL(request.url).pathname,
|
|
3682
|
+
routeId: match.route.id
|
|
3683
|
+
});
|
|
3684
|
+
} else {
|
|
3685
|
+
// lazy() route has no loader to run. Short circuit here so we don't
|
|
3686
|
+
// hit the invariant below that errors on returning undefined.
|
|
3687
|
+
return {
|
|
3688
|
+
type: ResultType.data,
|
|
3689
|
+
data: undefined
|
|
3690
|
+
};
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
} else {
|
|
3694
|
+
invariant(handler, "Could not find the " + type + " to run on the \"" + match.route.id + "\" route");
|
|
3695
|
+
result = await runHandler(handler);
|
|
3696
|
+
}
|
|
3697
|
+
|
|
3570
3698
|
invariant(result !== undefined, "You defined " + (type === "action" ? "an action" : "a loader") + " for route " + ("\"" + match.route.id + "\" but didn't return anything from your `" + type + "` ") + "function. Please return a value or `null`.");
|
|
3571
3699
|
} catch (e) {
|
|
3572
3700
|
resultType = ResultType.error;
|
|
3573
3701
|
result = e;
|
|
3574
3702
|
} finally {
|
|
3575
|
-
|
|
3703
|
+
if (onReject) {
|
|
3704
|
+
request.signal.removeEventListener("abort", onReject);
|
|
3705
|
+
}
|
|
3576
3706
|
}
|
|
3577
3707
|
|
|
3578
3708
|
if (isResponse(result)) {
|
|
@@ -4099,6 +4229,7 @@ exports.UNSAFE_DeferredData = DeferredData;
|
|
|
4099
4229
|
exports.UNSAFE_convertRoutesToDataRoutes = convertRoutesToDataRoutes;
|
|
4100
4230
|
exports.UNSAFE_getPathContributingMatches = getPathContributingMatches;
|
|
4101
4231
|
exports.UNSAFE_invariant = invariant;
|
|
4232
|
+
exports.UNSAFE_warning = warning;
|
|
4102
4233
|
exports.createBrowserHistory = createBrowserHistory;
|
|
4103
4234
|
exports.createHashHistory = createHashHistory;
|
|
4104
4235
|
exports.createMemoryHistory = createMemoryHistory;
|
|
@@ -4120,5 +4251,4 @@ exports.redirect = redirect;
|
|
|
4120
4251
|
exports.resolvePath = resolvePath;
|
|
4121
4252
|
exports.resolveTo = resolveTo;
|
|
4122
4253
|
exports.stripBasename = stripBasename;
|
|
4123
|
-
exports.warning = warning;
|
|
4124
4254
|
//# sourceMappingURL=router.cjs.js.map
|