@remix-run/router 0.0.0-experimental-e7e9ce6e → 0.0.0-experimental-91f2bf54
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 +3 -8
- package/dist/history.d.ts +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/router.cjs.js +231 -91
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +6 -3
- package/dist/router.js +227 -91
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +231 -91
- 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 +255 -31
- package/utils.ts +102 -69
package/dist/utils.d.ts
CHANGED
|
@@ -117,6 +117,27 @@ export interface ShouldRevalidateFunction {
|
|
|
117
117
|
defaultShouldRevalidate: boolean;
|
|
118
118
|
}): boolean;
|
|
119
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Function provided by the framework-aware layers to set `hasErrorBoundary`
|
|
122
|
+
* from the framework-aware `errorElement` prop
|
|
123
|
+
*/
|
|
124
|
+
export interface DetectErrorBoundaryFunction {
|
|
125
|
+
(route: AgnosticRouteObject): boolean;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Keys we cannot change from within a lazy() function. We spread all other keys
|
|
129
|
+
* onto the route. Either they're meaningful to the router, or they'll get
|
|
130
|
+
* ignored.
|
|
131
|
+
*/
|
|
132
|
+
export declare type ImmutableRouteKey = "lazy" | "caseSensitive" | "path" | "id" | "index" | "children";
|
|
133
|
+
export declare const immutableRouteKeys: Set<ImmutableRouteKey>;
|
|
134
|
+
/**
|
|
135
|
+
* lazy() function to load a route definition, which can add non-matching
|
|
136
|
+
* related properties to a route
|
|
137
|
+
*/
|
|
138
|
+
export interface LazyRouteFunction<R extends AgnosticRouteObject> {
|
|
139
|
+
(): Promise<Omit<R, ImmutableRouteKey>>;
|
|
140
|
+
}
|
|
120
141
|
/**
|
|
121
142
|
* Base RouteObject with common props shared by all types of routes
|
|
122
143
|
*/
|
|
@@ -129,6 +150,7 @@ declare type AgnosticBaseRouteObject = {
|
|
|
129
150
|
hasErrorBoundary?: boolean;
|
|
130
151
|
shouldRevalidate?: ShouldRevalidateFunction;
|
|
131
152
|
handle?: any;
|
|
153
|
+
lazy?: LazyRouteFunction<AgnosticBaseRouteObject>;
|
|
132
154
|
};
|
|
133
155
|
/**
|
|
134
156
|
* Index routes must not have children
|
|
@@ -160,6 +182,7 @@ export declare type AgnosticDataNonIndexRouteObject = AgnosticNonIndexRouteObjec
|
|
|
160
182
|
* A data route object, which is just a RouteObject with a required unique ID
|
|
161
183
|
*/
|
|
162
184
|
export declare type AgnosticDataRouteObject = AgnosticDataIndexRouteObject | AgnosticDataNonIndexRouteObject;
|
|
185
|
+
export declare type RouteManifest = Record<string, AgnosticDataRouteObject | undefined>;
|
|
163
186
|
declare type _PathParam<Path extends string> = Path extends `${infer L}/${infer R}` ? _PathParam<L> | _PathParam<R> : Path extends `:${infer Param}` ? Param extends `${infer Optional}?` ? Optional : Param : never;
|
|
164
187
|
/**
|
|
165
188
|
* Examples:
|
|
@@ -170,7 +193,7 @@ declare type _PathParam<Path extends string> = Path extends `${infer L}/${infer
|
|
|
170
193
|
* "/:a/:b" -> "a" | "b"
|
|
171
194
|
* "/:a/b/:c/*" -> "a" | "c" | "*"
|
|
172
195
|
*/
|
|
173
|
-
declare type PathParam<Path extends string> = Path extends "*" ? "*" : Path extends `${infer Rest}/*` ? "*" | _PathParam<Rest> : _PathParam<Path>;
|
|
196
|
+
declare type PathParam<Path extends string> = Path extends "*" | "/*" ? "*" : Path extends `${infer Rest}/*` ? "*" | _PathParam<Rest> : _PathParam<Path>;
|
|
174
197
|
export declare type ParamParseKey<Segment extends string> = [
|
|
175
198
|
PathParam<Segment>
|
|
176
199
|
] extends [never] ? string : PathParam<Segment>;
|
|
@@ -203,7 +226,7 @@ export interface AgnosticRouteMatch<ParamKey extends string = string, RouteObjec
|
|
|
203
226
|
}
|
|
204
227
|
export interface AgnosticDataRouteMatch extends AgnosticRouteMatch<string, AgnosticDataRouteObject> {
|
|
205
228
|
}
|
|
206
|
-
export declare function convertRoutesToDataRoutes(routes: AgnosticRouteObject[], parentPath?: number[],
|
|
229
|
+
export declare function convertRoutesToDataRoutes(routes: AgnosticRouteObject[], detectErrorBoundary: DetectErrorBoundaryFunction, parentPath?: number[], manifest?: RouteManifest): AgnosticDataRouteObject[];
|
|
207
230
|
/**
|
|
208
231
|
* Matches the given routes to a location and returns the match data.
|
|
209
232
|
*
|
|
@@ -270,10 +293,6 @@ export declare function matchPath<ParamKey extends ParamParseKey<Path>, Path ext
|
|
|
270
293
|
* @private
|
|
271
294
|
*/
|
|
272
295
|
export declare function stripBasename(pathname: string, basename: string): string | null;
|
|
273
|
-
/**
|
|
274
|
-
* @private
|
|
275
|
-
*/
|
|
276
|
-
export declare function warning(cond: any, message: string): void;
|
|
277
296
|
/**
|
|
278
297
|
* Returns a resolved path object relative to the given pathname.
|
|
279
298
|
*
|
package/history.ts
CHANGED
|
@@ -481,7 +481,7 @@ export function invariant(value: any, message?: string) {
|
|
|
481
481
|
}
|
|
482
482
|
}
|
|
483
483
|
|
|
484
|
-
function warning(cond: any, message: string) {
|
|
484
|
+
export function warning(cond: any, message: string) {
|
|
485
485
|
if (!cond) {
|
|
486
486
|
// eslint-disable-next-line no-console
|
|
487
487
|
if (typeof console !== "undefined") console.warn(message);
|
package/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type {
|
|
|
9
9
|
AgnosticNonIndexRouteObject,
|
|
10
10
|
AgnosticRouteMatch,
|
|
11
11
|
AgnosticRouteObject,
|
|
12
|
+
LazyRouteFunction,
|
|
12
13
|
TrackedPromise,
|
|
13
14
|
FormEncType,
|
|
14
15
|
FormMethod,
|
|
@@ -40,7 +41,6 @@ export {
|
|
|
40
41
|
resolvePath,
|
|
41
42
|
resolveTo,
|
|
42
43
|
stripBasename,
|
|
43
|
-
warning,
|
|
44
44
|
} from "./utils";
|
|
45
45
|
|
|
46
46
|
export type {
|
|
@@ -76,10 +76,14 @@ export * from "./router";
|
|
|
76
76
|
///////////////////////////////////////////////////////////////////////////////
|
|
77
77
|
|
|
78
78
|
/** @internal */
|
|
79
|
+
export type { RouteManifest as UNSAFE_RouteManifest } from "./utils";
|
|
79
80
|
export {
|
|
80
81
|
DeferredData as UNSAFE_DeferredData,
|
|
81
82
|
convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes,
|
|
82
83
|
getPathContributingMatches as UNSAFE_getPathContributingMatches,
|
|
83
84
|
} from "./utils";
|
|
84
85
|
|
|
85
|
-
export {
|
|
86
|
+
export {
|
|
87
|
+
invariant as UNSAFE_invariant,
|
|
88
|
+
warning as UNSAFE_warning,
|
|
89
|
+
} from "./history";
|
package/package.json
CHANGED
package/router.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
createPath,
|
|
6
6
|
invariant,
|
|
7
7
|
parsePath,
|
|
8
|
+
warning,
|
|
8
9
|
} from "./history";
|
|
9
10
|
import type {
|
|
10
11
|
DataResult,
|
|
@@ -14,6 +15,7 @@ import type {
|
|
|
14
15
|
ErrorResult,
|
|
15
16
|
FormEncType,
|
|
16
17
|
FormMethod,
|
|
18
|
+
DetectErrorBoundaryFunction,
|
|
17
19
|
RedirectResult,
|
|
18
20
|
RouteData,
|
|
19
21
|
AgnosticRouteObject,
|
|
@@ -22,6 +24,10 @@ import type {
|
|
|
22
24
|
AgnosticRouteMatch,
|
|
23
25
|
MutationFormMethod,
|
|
24
26
|
ShouldRevalidateFunction,
|
|
27
|
+
RouteManifest,
|
|
28
|
+
ImmutableRouteKey,
|
|
29
|
+
ActionFunction,
|
|
30
|
+
LoaderFunction,
|
|
25
31
|
} from "./utils";
|
|
26
32
|
import {
|
|
27
33
|
DeferredData,
|
|
@@ -29,12 +35,12 @@ import {
|
|
|
29
35
|
ResultType,
|
|
30
36
|
convertRoutesToDataRoutes,
|
|
31
37
|
getPathContributingMatches,
|
|
38
|
+
immutableRouteKeys,
|
|
32
39
|
isRouteErrorResponse,
|
|
33
40
|
joinPaths,
|
|
34
41
|
matchRoutes,
|
|
35
42
|
resolveTo,
|
|
36
43
|
stripBasename,
|
|
37
|
-
warning,
|
|
38
44
|
} from "./utils";
|
|
39
45
|
|
|
40
46
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -328,6 +334,7 @@ export interface RouterInit {
|
|
|
328
334
|
routes: AgnosticRouteObject[];
|
|
329
335
|
history: History;
|
|
330
336
|
hydrationData?: HydrationState;
|
|
337
|
+
detectErrorBoundary?: DetectErrorBoundaryFunction;
|
|
331
338
|
}
|
|
332
339
|
|
|
333
340
|
/**
|
|
@@ -638,6 +645,9 @@ const isBrowser =
|
|
|
638
645
|
typeof window.document !== "undefined" &&
|
|
639
646
|
typeof window.document.createElement !== "undefined";
|
|
640
647
|
const isServer = !isBrowser;
|
|
648
|
+
|
|
649
|
+
const defaultDetectErrorBoundary = (route: AgnosticRouteObject) =>
|
|
650
|
+
Boolean(route.hasErrorBoundary);
|
|
641
651
|
//#endregion
|
|
642
652
|
|
|
643
653
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -653,7 +663,18 @@ export function createRouter(init: RouterInit): Router {
|
|
|
653
663
|
"You must provide a non-empty routes array to createRouter"
|
|
654
664
|
);
|
|
655
665
|
|
|
656
|
-
let
|
|
666
|
+
let detectErrorBoundary =
|
|
667
|
+
init.detectErrorBoundary || defaultDetectErrorBoundary;
|
|
668
|
+
|
|
669
|
+
// Routes keyed by ID
|
|
670
|
+
let manifest: RouteManifest = {};
|
|
671
|
+
// Routes in tree format for matching
|
|
672
|
+
let dataRoutes = convertRoutesToDataRoutes(
|
|
673
|
+
init.routes,
|
|
674
|
+
detectErrorBoundary,
|
|
675
|
+
undefined,
|
|
676
|
+
manifest
|
|
677
|
+
);
|
|
657
678
|
let inFlightDataRoutes: AgnosticDataRouteObject[] | undefined;
|
|
658
679
|
// Cleanup function for history
|
|
659
680
|
let unlistenHistory: (() => void) | null = null;
|
|
@@ -692,7 +713,11 @@ export function createRouter(init: RouterInit): Router {
|
|
|
692
713
|
}
|
|
693
714
|
|
|
694
715
|
let initialized =
|
|
695
|
-
|
|
716
|
+
// All initialMatches need to be loaded before we're ready. If we have lazy
|
|
717
|
+
// functions around still then we'll need to run them in initialize()
|
|
718
|
+
!initialMatches.some((m) => m.route.lazy) &&
|
|
719
|
+
// And we have to either have no loaders or have been provided hydrationData
|
|
720
|
+
(!initialMatches.some((m) => m.route.loader) || init.hydrationData != null);
|
|
696
721
|
|
|
697
722
|
let router: Router;
|
|
698
723
|
let state: RouterState = {
|
|
@@ -837,11 +862,35 @@ export function createRouter(init: RouterInit): Router {
|
|
|
837
862
|
}
|
|
838
863
|
);
|
|
839
864
|
|
|
840
|
-
|
|
841
|
-
|
|
865
|
+
if (state.initialized) {
|
|
866
|
+
return router;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
let lazyMatches = state.matches.filter((m) => m.route.lazy);
|
|
870
|
+
|
|
871
|
+
if (lazyMatches.length === 0) {
|
|
872
|
+
// Kick off initial data load if needed. Use Pop to avoid modifying history
|
|
842
873
|
startNavigation(HistoryAction.Pop, state.location);
|
|
874
|
+
return router;
|
|
843
875
|
}
|
|
844
876
|
|
|
877
|
+
// Load lazy modules, then kick off initial data load if needed
|
|
878
|
+
let lazyPromises = lazyMatches.map((m) =>
|
|
879
|
+
loadLazyRouteModule(m.route, detectErrorBoundary, manifest)
|
|
880
|
+
);
|
|
881
|
+
Promise.all(lazyPromises).then(() => {
|
|
882
|
+
let initialized =
|
|
883
|
+
!state.matches.some((m) => m.route.loader) ||
|
|
884
|
+
init.hydrationData != null;
|
|
885
|
+
if (initialized) {
|
|
886
|
+
// We already have required loaderData so we can just set initialized
|
|
887
|
+
updateState({ initialized: true });
|
|
888
|
+
} else {
|
|
889
|
+
// We still need to kick off initial data loads
|
|
890
|
+
startNavigation(HistoryAction.Pop, state.location);
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
|
|
845
894
|
return router;
|
|
846
895
|
}
|
|
847
896
|
|
|
@@ -871,12 +920,27 @@ export function createRouter(init: RouterInit): Router {
|
|
|
871
920
|
subscribers.forEach((subscriber) => subscriber(state));
|
|
872
921
|
}
|
|
873
922
|
|
|
923
|
+
function completeNavigation(
|
|
924
|
+
location: Location,
|
|
925
|
+
newState: Partial<Omit<RouterState, "action" | "location" | "navigation">>
|
|
926
|
+
) {
|
|
927
|
+
// @ts-expect-error
|
|
928
|
+
if (typeof document !== "undefined" && document.startViewTransition) {
|
|
929
|
+
// @ts-expect-error
|
|
930
|
+
document.startViewTransition(() =>
|
|
931
|
+
completeNavigationForRealz(location, newState)
|
|
932
|
+
);
|
|
933
|
+
} else {
|
|
934
|
+
completeNavigationForRealz(location, newState);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
874
938
|
// Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
|
|
875
939
|
// and setting state.[historyAction/location/matches] to the new route.
|
|
876
940
|
// - Location is a required param
|
|
877
941
|
// - Navigation will always be set to IDLE_NAVIGATION
|
|
878
942
|
// - Can pass any other state in newState
|
|
879
|
-
function
|
|
943
|
+
function completeNavigationForRealz(
|
|
880
944
|
location: Location,
|
|
881
945
|
newState: Partial<Omit<RouterState, "action" | "location" | "navigation">>
|
|
882
946
|
): void {
|
|
@@ -1259,7 +1323,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1259
1323
|
let result: DataResult;
|
|
1260
1324
|
let actionMatch = getTargetMatch(matches, location);
|
|
1261
1325
|
|
|
1262
|
-
if (!actionMatch.route.action) {
|
|
1326
|
+
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
1263
1327
|
result = {
|
|
1264
1328
|
type: ResultType.error,
|
|
1265
1329
|
error: getInternalRouterError(405, {
|
|
@@ -1274,6 +1338,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1274
1338
|
request,
|
|
1275
1339
|
actionMatch,
|
|
1276
1340
|
matches,
|
|
1341
|
+
manifest,
|
|
1342
|
+
detectErrorBoundary,
|
|
1277
1343
|
router.basename
|
|
1278
1344
|
);
|
|
1279
1345
|
|
|
@@ -1566,7 +1632,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1566
1632
|
interruptActiveLoads();
|
|
1567
1633
|
fetchLoadMatches.delete(key);
|
|
1568
1634
|
|
|
1569
|
-
if (!match.route.action) {
|
|
1635
|
+
if (!match.route.action && !match.route.lazy) {
|
|
1570
1636
|
let error = getInternalRouterError(405, {
|
|
1571
1637
|
method: submission.formMethod,
|
|
1572
1638
|
pathname: path,
|
|
@@ -1602,6 +1668,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1602
1668
|
fetchRequest,
|
|
1603
1669
|
match,
|
|
1604
1670
|
requestMatches,
|
|
1671
|
+
manifest,
|
|
1672
|
+
detectErrorBoundary,
|
|
1605
1673
|
router.basename
|
|
1606
1674
|
);
|
|
1607
1675
|
|
|
@@ -1821,11 +1889,14 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1821
1889
|
abortController.signal
|
|
1822
1890
|
);
|
|
1823
1891
|
fetchControllers.set(key, abortController);
|
|
1892
|
+
|
|
1824
1893
|
let result: DataResult = await callLoaderOrAction(
|
|
1825
1894
|
"loader",
|
|
1826
1895
|
fetchRequest,
|
|
1827
1896
|
match,
|
|
1828
1897
|
matches,
|
|
1898
|
+
manifest,
|
|
1899
|
+
detectErrorBoundary,
|
|
1829
1900
|
router.basename
|
|
1830
1901
|
);
|
|
1831
1902
|
|
|
@@ -2021,7 +2092,15 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2021
2092
|
// accordingly
|
|
2022
2093
|
let results = await Promise.all([
|
|
2023
2094
|
...matchesToLoad.map((match) =>
|
|
2024
|
-
callLoaderOrAction(
|
|
2095
|
+
callLoaderOrAction(
|
|
2096
|
+
"loader",
|
|
2097
|
+
request,
|
|
2098
|
+
match,
|
|
2099
|
+
matches,
|
|
2100
|
+
manifest,
|
|
2101
|
+
detectErrorBoundary,
|
|
2102
|
+
router.basename
|
|
2103
|
+
)
|
|
2025
2104
|
),
|
|
2026
2105
|
...fetchersToLoad.map((f) => {
|
|
2027
2106
|
if (f.matches && f.match) {
|
|
@@ -2030,6 +2109,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2030
2109
|
createClientSideRequest(init.history, f.path, request.signal),
|
|
2031
2110
|
f.match,
|
|
2032
2111
|
f.matches,
|
|
2112
|
+
manifest,
|
|
2113
|
+
detectErrorBoundary,
|
|
2033
2114
|
router.basename
|
|
2034
2115
|
);
|
|
2035
2116
|
} else {
|
|
@@ -2346,18 +2427,29 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2346
2427
|
|
|
2347
2428
|
export const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
|
|
2348
2429
|
|
|
2430
|
+
export interface CreateStaticHandlerOptions {
|
|
2431
|
+
basename?: string;
|
|
2432
|
+
detectErrorBoundary?: DetectErrorBoundaryFunction;
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2349
2435
|
export function createStaticHandler(
|
|
2350
2436
|
routes: AgnosticRouteObject[],
|
|
2351
|
-
opts?:
|
|
2352
|
-
basename?: string;
|
|
2353
|
-
}
|
|
2437
|
+
opts?: CreateStaticHandlerOptions
|
|
2354
2438
|
): StaticHandler {
|
|
2355
2439
|
invariant(
|
|
2356
2440
|
routes.length > 0,
|
|
2357
2441
|
"You must provide a non-empty routes array to createStaticHandler"
|
|
2358
2442
|
);
|
|
2359
2443
|
|
|
2360
|
-
let
|
|
2444
|
+
let manifest: RouteManifest = {};
|
|
2445
|
+
let detectErrorBoundary =
|
|
2446
|
+
opts?.detectErrorBoundary || defaultDetectErrorBoundary;
|
|
2447
|
+
let dataRoutes = convertRoutesToDataRoutes(
|
|
2448
|
+
routes,
|
|
2449
|
+
detectErrorBoundary,
|
|
2450
|
+
undefined,
|
|
2451
|
+
manifest
|
|
2452
|
+
);
|
|
2361
2453
|
let basename = (opts ? opts.basename : null) || "/";
|
|
2362
2454
|
|
|
2363
2455
|
/**
|
|
@@ -2592,7 +2684,7 @@ export function createStaticHandler(
|
|
|
2592
2684
|
): Promise<Omit<StaticHandlerContext, "location" | "basename"> | Response> {
|
|
2593
2685
|
let result: DataResult;
|
|
2594
2686
|
|
|
2595
|
-
if (!actionMatch.route.action) {
|
|
2687
|
+
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
2596
2688
|
let error = getInternalRouterError(405, {
|
|
2597
2689
|
method: request.method,
|
|
2598
2690
|
pathname: new URL(request.url).pathname,
|
|
@@ -2611,6 +2703,8 @@ export function createStaticHandler(
|
|
|
2611
2703
|
request,
|
|
2612
2704
|
actionMatch,
|
|
2613
2705
|
matches,
|
|
2706
|
+
manifest,
|
|
2707
|
+
detectErrorBoundary,
|
|
2614
2708
|
basename,
|
|
2615
2709
|
true,
|
|
2616
2710
|
isRouteRequest,
|
|
@@ -2732,7 +2826,11 @@ export function createStaticHandler(
|
|
|
2732
2826
|
let isRouteRequest = routeMatch != null;
|
|
2733
2827
|
|
|
2734
2828
|
// Short circuit if we have no loaders to run (queryRoute())
|
|
2735
|
-
if (
|
|
2829
|
+
if (
|
|
2830
|
+
isRouteRequest &&
|
|
2831
|
+
!routeMatch?.route.loader &&
|
|
2832
|
+
!routeMatch?.route.lazy
|
|
2833
|
+
) {
|
|
2736
2834
|
throw getInternalRouterError(400, {
|
|
2737
2835
|
method: request.method,
|
|
2738
2836
|
pathname: new URL(request.url).pathname,
|
|
@@ -2746,7 +2844,9 @@ export function createStaticHandler(
|
|
|
2746
2844
|
matches,
|
|
2747
2845
|
Object.keys(pendingActionError || {})[0]
|
|
2748
2846
|
);
|
|
2749
|
-
let matchesToLoad = requestMatches.filter(
|
|
2847
|
+
let matchesToLoad = requestMatches.filter(
|
|
2848
|
+
(m) => m.route.loader || m.route.lazy
|
|
2849
|
+
);
|
|
2750
2850
|
|
|
2751
2851
|
// Short circuit if we have no loaders to run (query())
|
|
2752
2852
|
if (matchesToLoad.length === 0) {
|
|
@@ -2771,6 +2871,8 @@ export function createStaticHandler(
|
|
|
2771
2871
|
request,
|
|
2772
2872
|
match,
|
|
2773
2873
|
matches,
|
|
2874
|
+
manifest,
|
|
2875
|
+
detectErrorBoundary,
|
|
2774
2876
|
basename,
|
|
2775
2877
|
true,
|
|
2776
2878
|
isRouteRequest,
|
|
@@ -2960,6 +3062,10 @@ function getMatchesToLoad(
|
|
|
2960
3062
|
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
2961
3063
|
|
|
2962
3064
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
3065
|
+
if (match.route.lazy) {
|
|
3066
|
+
// We haven't loaded this route yet so we don't know if it's got a loader!
|
|
3067
|
+
return true;
|
|
3068
|
+
}
|
|
2963
3069
|
if (match.route.loader == null) {
|
|
2964
3070
|
return false;
|
|
2965
3071
|
}
|
|
@@ -3096,11 +3202,90 @@ function shouldRevalidateLoader(
|
|
|
3096
3202
|
return arg.defaultShouldRevalidate;
|
|
3097
3203
|
}
|
|
3098
3204
|
|
|
3205
|
+
/**
|
|
3206
|
+
* Execute route.lazy() methods to lazily load route modules (loader, action,
|
|
3207
|
+
* shouldRevalidate) and update the routeManifest in place which shares objects
|
|
3208
|
+
* with dataRoutes so those get updated as well.
|
|
3209
|
+
*/
|
|
3210
|
+
async function loadLazyRouteModule(
|
|
3211
|
+
route: AgnosticDataRouteObject,
|
|
3212
|
+
detectErrorBoundary: DetectErrorBoundaryFunction,
|
|
3213
|
+
manifest: RouteManifest
|
|
3214
|
+
) {
|
|
3215
|
+
if (!route.lazy) {
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
let lazyRoute = await route.lazy();
|
|
3220
|
+
|
|
3221
|
+
// If the lazy route function was executed and removed by another parallel
|
|
3222
|
+
// call then we can return - first lazy() to finish wins because the return
|
|
3223
|
+
// value of lazy is expected to be static
|
|
3224
|
+
if (!route.lazy) {
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
let routeToUpdate = manifest[route.id];
|
|
3229
|
+
invariant(routeToUpdate, "No route found in manifest");
|
|
3230
|
+
|
|
3231
|
+
// Update the route in place. This should be safe because there's no way
|
|
3232
|
+
// we could yet be sitting on this route as we can't get there without
|
|
3233
|
+
// resolving lazy() first.
|
|
3234
|
+
//
|
|
3235
|
+
// This is different than the HMR "update" use-case where we may actively be
|
|
3236
|
+
// on the route being updated. The main concern boils down to "does this
|
|
3237
|
+
// mutation affect any ongoing navigations or any current state.matches
|
|
3238
|
+
// values?". If not, it should be safe to update in place.
|
|
3239
|
+
let routeUpdates: Record<string, any> = {};
|
|
3240
|
+
for (let lazyRouteProperty in lazyRoute) {
|
|
3241
|
+
let staticRouteValue =
|
|
3242
|
+
routeToUpdate[lazyRouteProperty as keyof typeof routeToUpdate];
|
|
3243
|
+
|
|
3244
|
+
let isPropertyStaticallyDefined =
|
|
3245
|
+
staticRouteValue !== undefined &&
|
|
3246
|
+
// This property isn't static since it should always be updated based
|
|
3247
|
+
// on the route updates
|
|
3248
|
+
lazyRouteProperty !== "hasErrorBoundary";
|
|
3249
|
+
|
|
3250
|
+
warning(
|
|
3251
|
+
!isPropertyStaticallyDefined,
|
|
3252
|
+
`Route "${routeToUpdate.id}" has a static property "${lazyRouteProperty}" ` +
|
|
3253
|
+
`defined but its lazy function is also returning a value for this property. ` +
|
|
3254
|
+
`The lazy route property "${lazyRouteProperty}" will be ignored.`
|
|
3255
|
+
);
|
|
3256
|
+
|
|
3257
|
+
if (
|
|
3258
|
+
!isPropertyStaticallyDefined &&
|
|
3259
|
+
!immutableRouteKeys.has(lazyRouteProperty as ImmutableRouteKey)
|
|
3260
|
+
) {
|
|
3261
|
+
routeUpdates[lazyRouteProperty] =
|
|
3262
|
+
lazyRoute[lazyRouteProperty as keyof typeof lazyRoute];
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
// Mutate the route with the provided updates. Do this first so we pass
|
|
3267
|
+
// the updated version to detectErrorBoundary
|
|
3268
|
+
Object.assign(routeToUpdate, routeUpdates);
|
|
3269
|
+
|
|
3270
|
+
// Mutate the `hasErrorBoundary` property on the route based on the route
|
|
3271
|
+
// updates and remove the `lazy` function so we don't resolve the lazy
|
|
3272
|
+
// route again.
|
|
3273
|
+
Object.assign(routeToUpdate, {
|
|
3274
|
+
// To keep things framework agnostic, we use the provided
|
|
3275
|
+
// `detectErrorBoundary` function to set the `hasErrorBoundary` route
|
|
3276
|
+
// property since the logic will differ between frameworks.
|
|
3277
|
+
hasErrorBoundary: detectErrorBoundary({ ...routeToUpdate }),
|
|
3278
|
+
lazy: undefined,
|
|
3279
|
+
});
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3099
3282
|
async function callLoaderOrAction(
|
|
3100
3283
|
type: "loader" | "action",
|
|
3101
3284
|
request: Request,
|
|
3102
3285
|
match: AgnosticDataRouteMatch,
|
|
3103
3286
|
matches: AgnosticDataRouteMatch[],
|
|
3287
|
+
manifest: RouteManifest,
|
|
3288
|
+
detectErrorBoundary: DetectErrorBoundaryFunction,
|
|
3104
3289
|
basename = "/",
|
|
3105
3290
|
isStaticRequest: boolean = false,
|
|
3106
3291
|
isRouteRequest: boolean = false,
|
|
@@ -3108,24 +3293,61 @@ async function callLoaderOrAction(
|
|
|
3108
3293
|
): Promise<DataResult> {
|
|
3109
3294
|
let resultType;
|
|
3110
3295
|
let result;
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
let
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3296
|
+
let onReject: (() => void) | undefined;
|
|
3297
|
+
|
|
3298
|
+
let runHandler = (handler: ActionFunction | LoaderFunction) => {
|
|
3299
|
+
// Setup a promise we can race against so that abort signals short circuit
|
|
3300
|
+
let reject: () => void;
|
|
3301
|
+
let abortPromise = new Promise((_, r) => (reject = r));
|
|
3302
|
+
onReject = () => reject();
|
|
3303
|
+
request.signal.addEventListener("abort", onReject);
|
|
3304
|
+
return Promise.race([
|
|
3305
|
+
handler({ request, params: match.params, context: requestContext }),
|
|
3306
|
+
abortPromise,
|
|
3307
|
+
]);
|
|
3308
|
+
};
|
|
3117
3309
|
|
|
3118
3310
|
try {
|
|
3119
3311
|
let handler = match.route[type];
|
|
3120
|
-
invariant<Function>(
|
|
3121
|
-
handler,
|
|
3122
|
-
`Could not find the ${type} to run on the "${match.route.id}" route`
|
|
3123
|
-
);
|
|
3124
3312
|
|
|
3125
|
-
|
|
3126
|
-
handler
|
|
3127
|
-
|
|
3128
|
-
|
|
3313
|
+
if (match.route.lazy) {
|
|
3314
|
+
if (handler) {
|
|
3315
|
+
// Run statically defined handler in parallel with lazy()
|
|
3316
|
+
let values = await Promise.all([
|
|
3317
|
+
runHandler(handler),
|
|
3318
|
+
loadLazyRouteModule(match.route, detectErrorBoundary, manifest),
|
|
3319
|
+
]);
|
|
3320
|
+
result = values[0];
|
|
3321
|
+
} else {
|
|
3322
|
+
// Load lazy route module, then run any returned handler
|
|
3323
|
+
await loadLazyRouteModule(match.route, detectErrorBoundary, manifest);
|
|
3324
|
+
|
|
3325
|
+
handler = match.route[type];
|
|
3326
|
+
if (handler) {
|
|
3327
|
+
// Handler still run even if we got interrupted to maintain consistency
|
|
3328
|
+
// with un-abortable behavior of handler execution on non-lazy or
|
|
3329
|
+
// previously-lazy-loaded routes
|
|
3330
|
+
result = await runHandler(handler);
|
|
3331
|
+
} else if (type === "action") {
|
|
3332
|
+
throw getInternalRouterError(405, {
|
|
3333
|
+
method: request.method,
|
|
3334
|
+
pathname: new URL(request.url).pathname,
|
|
3335
|
+
routeId: match.route.id,
|
|
3336
|
+
});
|
|
3337
|
+
} else {
|
|
3338
|
+
// lazy() route has no loader to run. Short circuit here so we don't
|
|
3339
|
+
// hit the invariant below that errors on returning undefined.
|
|
3340
|
+
return { type: ResultType.data, data: undefined };
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
} else {
|
|
3344
|
+
invariant<Function>(
|
|
3345
|
+
handler,
|
|
3346
|
+
`Could not find the ${type} to run on the "${match.route.id}" route`
|
|
3347
|
+
);
|
|
3348
|
+
|
|
3349
|
+
result = await runHandler(handler);
|
|
3350
|
+
}
|
|
3129
3351
|
|
|
3130
3352
|
invariant(
|
|
3131
3353
|
result !== undefined,
|
|
@@ -3137,7 +3359,9 @@ async function callLoaderOrAction(
|
|
|
3137
3359
|
resultType = ResultType.error;
|
|
3138
3360
|
result = e;
|
|
3139
3361
|
} finally {
|
|
3140
|
-
|
|
3362
|
+
if (onReject) {
|
|
3363
|
+
request.signal.removeEventListener("abort", onReject);
|
|
3364
|
+
}
|
|
3141
3365
|
}
|
|
3142
3366
|
|
|
3143
3367
|
if (isResponse(result)) {
|