@octanejs/tanstack-router 0.2.6 → 0.2.8
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/README.md +48 -0
- package/package.json +4 -3
- package/src/Matches.tsrx +1 -1
- package/src/Transitioner.tsrx +3 -3
- package/src/compiled/Matches.tsrx +266 -0
- package/src/compiled/bindings.ts +40 -0
- package/src/compiled.ts +32 -0
- package/src/fileRoute.ts +3 -2
- package/src/frameworkTypes.ts +11 -8
- package/src/hookBindings.ts +396 -0
- package/src/hooks.ts +17 -374
- package/src/index.ts +2 -0
- package/src/lazyRouteComponent.ts +6 -50
- package/src/lazyRouteLoader.ts +49 -0
- package/src/route.ts +181 -0
- package/src/routerStateBinding.ts +41 -0
- package/src/useRouterState.ts +3 -35
package/src/hooks.ts
CHANGED
|
@@ -1,375 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
// useLoaderData / useLoaderDeps / useRouteContext / useNavigate / useCanGoBack /
|
|
3
|
-
// Matches.tsx (useMatches / useParentMatches / useChildMatches). Everything match-
|
|
4
|
-
// shaped funnels through `useMatch`, which subscribes to ONE match store via
|
|
5
|
-
// `router.stores.getMatchStore(routeId)`:
|
|
6
|
-
// - `from` given → that routeId's match store;
|
|
7
|
-
// - no `from` → the NEAREST match via `matchContext` (the match id the enclosing
|
|
8
|
-
// `<Match>` provided) — NOT the leaf match.
|
|
9
|
-
// A missing match throws unless `shouldThrow: false` (upstream invariant).
|
|
10
|
-
// Selectors run through `useStructuralSharing` (replaceEqualDeep against the
|
|
11
|
-
// previous selection when `structuralSharing ?? defaultStructuralSharing`).
|
|
12
|
-
import { useContext, useRef, useCallback } from 'octane';
|
|
13
|
-
import { replaceEqualDeep } from '@tanstack/router-core';
|
|
1
|
+
import { useContext } from 'octane';
|
|
14
2
|
import { useRouter, matchContext } from './context';
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
import type {
|
|
32
|
-
UseLoaderDataOptions,
|
|
33
|
-
UseLoaderDepsOptions,
|
|
34
|
-
UseLocationBaseOptions,
|
|
35
|
-
UseLocationResult,
|
|
36
|
-
UseMatchOptions,
|
|
37
|
-
UseMatchResult,
|
|
38
|
-
UseMatchesBaseOptions,
|
|
39
|
-
UseMatchesResult,
|
|
40
|
-
UseParamsOptions,
|
|
41
|
-
UseSearchOptions,
|
|
42
|
-
} from './routeHookTypes';
|
|
43
|
-
import type { StructuralSharingOption } from './structuralSharing';
|
|
44
|
-
|
|
45
|
-
// Sentinel store + selection for "no match at this id" (upstream's dummyStore).
|
|
46
|
-
const dummyStore = {
|
|
47
|
-
get() {},
|
|
48
|
-
subscribe() {
|
|
49
|
-
return { unsubscribe() {} };
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// Selector wrapper honoring structural sharing: when enabled, the selection is
|
|
54
|
-
// replaceEqualDeep'd against the previous one so deep-equal slices keep their
|
|
55
|
-
// reference (no re-render). Port of react-router's useStructuralSharing.
|
|
56
|
-
function useStructuralSharing(opts: any, router: any, slot: symbol | undefined) {
|
|
57
|
-
const previousResult = useRef<any>(undefined, subSlot(slot, 'ss'));
|
|
58
|
-
return (slice: any) => {
|
|
59
|
-
const selected = opts?.select ? opts.select(slice) : slice;
|
|
60
|
-
if (opts?.structuralSharing ?? router.options.defaultStructuralSharing) {
|
|
61
|
-
return (previousResult.current = replaceEqualDeep(previousResult.current, selected));
|
|
62
|
-
}
|
|
63
|
-
return selected;
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function useMatch<
|
|
68
|
-
TRouter extends AnyRouter = RegisteredRouter,
|
|
69
|
-
const TFrom extends string | undefined = undefined,
|
|
70
|
-
TStrict extends boolean = true,
|
|
71
|
-
TThrow extends boolean = true,
|
|
72
|
-
TSelected = unknown,
|
|
73
|
-
TStructuralSharing extends boolean = boolean,
|
|
74
|
-
>(
|
|
75
|
-
opts: UseMatchOptions<
|
|
76
|
-
TRouter,
|
|
77
|
-
TFrom,
|
|
78
|
-
TStrict,
|
|
79
|
-
ThrowConstraint<TStrict, TThrow>,
|
|
80
|
-
TSelected,
|
|
81
|
-
TStructuralSharing
|
|
82
|
-
>,
|
|
83
|
-
): ThrowOrOptional<UseMatchResult<TRouter, TFrom, TStrict, TSelected>, TThrow>;
|
|
84
|
-
export function useMatch(opts: any, slot: symbol | undefined): any;
|
|
85
|
-
export function useMatch(...args: any[]): any {
|
|
86
|
-
const [user, slot] = splitSlot(args);
|
|
87
|
-
const opts = user[0] ?? {};
|
|
88
|
-
const router = useRouter();
|
|
89
|
-
// octane has no rules of hooks, so the nearest-match context is read
|
|
90
|
-
// unconditionally (upstream reads a dummy context when `from` is given).
|
|
91
|
-
const nearestMatchId = useContext(matchContext);
|
|
92
|
-
const routeId = opts.from ?? (nearestMatchId as string);
|
|
93
|
-
const matchStore = router.stores.getMatchStore(routeId);
|
|
94
|
-
|
|
95
|
-
const selector = useStructuralSharing(opts, router, subSlot(slot, 'm'));
|
|
96
|
-
const matchSelection = useStore(
|
|
97
|
-
matchStore ?? dummyStore,
|
|
98
|
-
(match: any) => (match ? selector(match) : dummyStore),
|
|
99
|
-
undefined,
|
|
100
|
-
subSlot(slot, 'm:us'),
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
if (matchSelection !== dummyStore) return matchSelection;
|
|
104
|
-
if (opts.shouldThrow ?? true) {
|
|
105
|
-
throw new Error(
|
|
106
|
-
`Invariant failed: Could not find ${
|
|
107
|
-
opts.from ? `an active match from "${opts.from}"` : 'a nearest match!'
|
|
108
|
-
}`,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
return undefined;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function useParams<
|
|
115
|
-
TRouter extends AnyRouter = RegisteredRouter,
|
|
116
|
-
const TFrom extends string | undefined = undefined,
|
|
117
|
-
TStrict extends boolean = true,
|
|
118
|
-
TThrow extends boolean = true,
|
|
119
|
-
TSelected = unknown,
|
|
120
|
-
TStructuralSharing extends boolean = boolean,
|
|
121
|
-
>(
|
|
122
|
-
opts: UseParamsOptions<
|
|
123
|
-
TRouter,
|
|
124
|
-
TFrom,
|
|
125
|
-
TStrict,
|
|
126
|
-
ThrowConstraint<TStrict, TThrow>,
|
|
127
|
-
TSelected,
|
|
128
|
-
TStructuralSharing
|
|
129
|
-
>,
|
|
130
|
-
): ThrowOrOptional<UseParamsResult<TRouter, TFrom, TStrict, TSelected>, TThrow>;
|
|
131
|
-
export function useParams(opts: any, slot: symbol | undefined): any;
|
|
132
|
-
export function useParams(...args: any[]): any {
|
|
133
|
-
const [user, slot] = splitSlot(args);
|
|
134
|
-
const opts = user[0] ?? {};
|
|
135
|
-
return useMatch(
|
|
136
|
-
{
|
|
137
|
-
from: opts.from,
|
|
138
|
-
strict: opts.strict,
|
|
139
|
-
shouldThrow: opts.shouldThrow,
|
|
140
|
-
structuralSharing: opts.structuralSharing,
|
|
141
|
-
select: (match: any) => {
|
|
142
|
-
const params = opts.strict === false ? match.params : match._strictParams;
|
|
143
|
-
return opts.select ? opts.select(params) : params;
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
subSlot(slot, 'params'),
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export function useSearch<
|
|
151
|
-
TRouter extends AnyRouter = RegisteredRouter,
|
|
152
|
-
const TFrom extends string | undefined = undefined,
|
|
153
|
-
TStrict extends boolean = true,
|
|
154
|
-
TThrow extends boolean = true,
|
|
155
|
-
TSelected = unknown,
|
|
156
|
-
TStructuralSharing extends boolean = boolean,
|
|
157
|
-
>(
|
|
158
|
-
opts: UseSearchOptions<
|
|
159
|
-
TRouter,
|
|
160
|
-
TFrom,
|
|
161
|
-
TStrict,
|
|
162
|
-
ThrowConstraint<TStrict, TThrow>,
|
|
163
|
-
TSelected,
|
|
164
|
-
TStructuralSharing
|
|
165
|
-
>,
|
|
166
|
-
): ThrowOrOptional<UseSearchResult<TRouter, TFrom, TStrict, TSelected>, TThrow>;
|
|
167
|
-
export function useSearch(opts: any, slot: symbol | undefined): any;
|
|
168
|
-
export function useSearch(...args: any[]): any {
|
|
169
|
-
const [user, slot] = splitSlot(args);
|
|
170
|
-
const opts = user[0] ?? {};
|
|
171
|
-
return useMatch(
|
|
172
|
-
{
|
|
173
|
-
from: opts.from,
|
|
174
|
-
strict: opts.strict,
|
|
175
|
-
shouldThrow: opts.shouldThrow,
|
|
176
|
-
structuralSharing: opts.structuralSharing,
|
|
177
|
-
select: (match: any) => (opts.select ? opts.select(match.search) : match.search),
|
|
178
|
-
},
|
|
179
|
-
subSlot(slot, 'search'),
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function useLoaderData<
|
|
184
|
-
TRouter extends AnyRouter = RegisteredRouter,
|
|
185
|
-
const TFrom extends string | undefined = undefined,
|
|
186
|
-
TStrict extends boolean = true,
|
|
187
|
-
TSelected = unknown,
|
|
188
|
-
TStructuralSharing extends boolean = boolean,
|
|
189
|
-
>(
|
|
190
|
-
opts: UseLoaderDataOptions<TRouter, TFrom, TStrict, TSelected, TStructuralSharing>,
|
|
191
|
-
): UseLoaderDataResult<TRouter, TFrom, TStrict, TSelected>;
|
|
192
|
-
export function useLoaderData(opts: any, slot: symbol | undefined): any;
|
|
193
|
-
export function useLoaderData(...args: any[]): any {
|
|
194
|
-
const [user, slot] = splitSlot(args);
|
|
195
|
-
const opts = user[0] ?? {};
|
|
196
|
-
return useMatch(
|
|
197
|
-
{
|
|
198
|
-
from: opts.from,
|
|
199
|
-
strict: opts.strict,
|
|
200
|
-
structuralSharing: opts.structuralSharing,
|
|
201
|
-
select: (match: any) => (opts.select ? opts.select(match.loaderData) : match.loaderData),
|
|
202
|
-
},
|
|
203
|
-
subSlot(slot, 'loader'),
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export function useLoaderDeps<
|
|
208
|
-
TRouter extends AnyRouter = RegisteredRouter,
|
|
209
|
-
const TFrom extends string | undefined = undefined,
|
|
210
|
-
TSelected = unknown,
|
|
211
|
-
TStructuralSharing extends boolean = boolean,
|
|
212
|
-
>(
|
|
213
|
-
opts: UseLoaderDepsOptions<TRouter, TFrom, TSelected, TStructuralSharing>,
|
|
214
|
-
): UseLoaderDepsResult<TRouter, TFrom, TSelected>;
|
|
215
|
-
export function useLoaderDeps(opts: any, slot: symbol | undefined): any;
|
|
216
|
-
export function useLoaderDeps(...args: any[]): any {
|
|
217
|
-
const [user, slot] = splitSlot(args);
|
|
218
|
-
const opts = user[0] ?? {};
|
|
219
|
-
const { select, ...rest } = opts;
|
|
220
|
-
return useMatch(
|
|
221
|
-
{
|
|
222
|
-
...rest,
|
|
223
|
-
select: (match: any) => (select ? select(match.loaderDeps) : match.loaderDeps),
|
|
224
|
-
},
|
|
225
|
-
subSlot(slot, 'deps'),
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export function useRouteContext<
|
|
230
|
-
TRouter extends AnyRouter = RegisteredRouter,
|
|
231
|
-
const TFrom extends string | undefined = undefined,
|
|
232
|
-
TStrict extends boolean = true,
|
|
233
|
-
TSelected = unknown,
|
|
234
|
-
>(
|
|
235
|
-
opts: UseRouteContextOptions<TRouter, TFrom, TStrict, TSelected>,
|
|
236
|
-
): UseRouteContextResult<TRouter, TFrom, TStrict, TSelected>;
|
|
237
|
-
export function useRouteContext(opts: any, slot: symbol | undefined): any;
|
|
238
|
-
export function useRouteContext(...args: any[]): any {
|
|
239
|
-
const [user, slot] = splitSlot(args);
|
|
240
|
-
const opts = user[0] ?? {};
|
|
241
|
-
return useMatch(
|
|
242
|
-
{
|
|
243
|
-
...opts,
|
|
244
|
-
select: (match: any) => (opts.select ? opts.select(match.context) : match.context),
|
|
245
|
-
},
|
|
246
|
-
subSlot(slot, 'ctx'),
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export function useLocation<
|
|
251
|
-
TRouter extends AnyRouter = RegisteredRouter,
|
|
252
|
-
TSelected = unknown,
|
|
253
|
-
TStructuralSharing extends boolean = boolean,
|
|
254
|
-
>(
|
|
255
|
-
opts?: UseLocationBaseOptions<TRouter, TSelected, TStructuralSharing> &
|
|
256
|
-
StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
|
|
257
|
-
): UseLocationResult<TRouter, TSelected>;
|
|
258
|
-
export function useLocation(opts: any, slot: symbol | undefined): any;
|
|
259
|
-
export function useLocation(...args: any[]): any {
|
|
260
|
-
const [user, slot] = splitSlot(args);
|
|
261
|
-
const opts = user[0] ?? {};
|
|
262
|
-
const router = useRouter();
|
|
263
|
-
return useStore(
|
|
264
|
-
router.stores.location,
|
|
265
|
-
useStructuralSharing(opts, router, subSlot(slot, 'loc')),
|
|
266
|
-
undefined,
|
|
267
|
-
subSlot(slot, 'loc:us'),
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export function useMatches<
|
|
272
|
-
TRouter extends AnyRouter = RegisteredRouter,
|
|
273
|
-
TSelected = unknown,
|
|
274
|
-
TStructuralSharing extends boolean = boolean,
|
|
275
|
-
>(
|
|
276
|
-
opts?: UseMatchesBaseOptions<TRouter, TSelected, TStructuralSharing> &
|
|
277
|
-
StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
|
|
278
|
-
): UseMatchesResult<TRouter, TSelected>;
|
|
279
|
-
export function useMatches(opts: any, slot: symbol | undefined): any;
|
|
280
|
-
export function useMatches(...args: any[]): any {
|
|
281
|
-
const [user, slot] = splitSlot(args);
|
|
282
|
-
const opts = user[0] ?? {};
|
|
283
|
-
const router = useRouter();
|
|
284
|
-
return useStore(
|
|
285
|
-
router.stores.matches,
|
|
286
|
-
useStructuralSharing(opts, router, subSlot(slot, 'matches')),
|
|
287
|
-
undefined,
|
|
288
|
-
subSlot(slot, 'matches:us'),
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
export function useParentMatches<
|
|
293
|
-
TRouter extends AnyRouter = RegisteredRouter,
|
|
294
|
-
TSelected = unknown,
|
|
295
|
-
TStructuralSharing extends boolean = boolean,
|
|
296
|
-
>(
|
|
297
|
-
opts?: UseMatchesBaseOptions<TRouter, TSelected, TStructuralSharing> &
|
|
298
|
-
StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
|
|
299
|
-
): UseMatchesResult<TRouter, TSelected>;
|
|
300
|
-
export function useParentMatches(opts: any, slot: symbol | undefined): any;
|
|
301
|
-
export function useParentMatches(...args: any[]): any {
|
|
302
|
-
const [user, slot] = splitSlot(args);
|
|
303
|
-
const opts = user[0] ?? {};
|
|
304
|
-
const contextRouteId = useContext(matchContext);
|
|
305
|
-
return useMatches(
|
|
306
|
-
{
|
|
307
|
-
select: (matches: any[]) => {
|
|
308
|
-
matches = matches.slice(
|
|
309
|
-
0,
|
|
310
|
-
matches.findIndex((d: any) => d.routeId === contextRouteId),
|
|
311
|
-
);
|
|
312
|
-
return opts.select ? opts.select(matches) : matches;
|
|
313
|
-
},
|
|
314
|
-
structuralSharing: opts.structuralSharing,
|
|
315
|
-
},
|
|
316
|
-
subSlot(slot, 'parents'),
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
export function useChildMatches<
|
|
321
|
-
TRouter extends AnyRouter = RegisteredRouter,
|
|
322
|
-
TSelected = unknown,
|
|
323
|
-
TStructuralSharing extends boolean = boolean,
|
|
324
|
-
>(
|
|
325
|
-
opts?: UseMatchesBaseOptions<TRouter, TSelected, TStructuralSharing> &
|
|
326
|
-
StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
|
|
327
|
-
): UseMatchesResult<TRouter, TSelected>;
|
|
328
|
-
export function useChildMatches(opts: any, slot: symbol | undefined): any;
|
|
329
|
-
export function useChildMatches(...args: any[]): any {
|
|
330
|
-
const [user, slot] = splitSlot(args);
|
|
331
|
-
const opts = user[0] ?? {};
|
|
332
|
-
const contextRouteId = useContext(matchContext);
|
|
333
|
-
return useMatches(
|
|
334
|
-
{
|
|
335
|
-
select: (matches: any[]) => {
|
|
336
|
-
matches = matches.slice(matches.findIndex((d: any) => d.routeId === contextRouteId) + 1);
|
|
337
|
-
return opts.select ? opts.select(matches) : matches;
|
|
338
|
-
},
|
|
339
|
-
structuralSharing: opts.structuralSharing,
|
|
340
|
-
},
|
|
341
|
-
subSlot(slot, 'children'),
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Returns a STABLE navigate function (upstream useCallback([from, router])) that
|
|
346
|
-
// forwards to `router.navigate`, defaulting `from` to the hook's option.
|
|
347
|
-
export function useNavigate<
|
|
348
|
-
TRouter extends AnyRouter = RegisteredRouter,
|
|
349
|
-
TDefaultFrom extends string = string,
|
|
350
|
-
>(options?: { from?: FromPathOption<TRouter, TDefaultFrom> }): UseNavigateResult<TDefaultFrom>;
|
|
351
|
-
export function useNavigate(options: any, slot: symbol | undefined): (to: any) => any;
|
|
352
|
-
export function useNavigate(...args: any[]): (to: any) => any {
|
|
353
|
-
const [user, slot] = splitSlot(args);
|
|
354
|
-
const opts = user[0] ?? {};
|
|
355
|
-
const router = useRouter(opts.router ? { router: opts.router } : undefined);
|
|
356
|
-
return useCallback(
|
|
357
|
-
(options: any) => router.navigate({ ...options, from: options?.from ?? opts.from }),
|
|
358
|
-
[opts.from, router],
|
|
359
|
-
subSlot(slot, 'nav'),
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// True when the current history entry isn't the first (there is somewhere to go
|
|
364
|
-
// back to) — per upstream useCanGoBack (location.state.__TSR_index !== 0).
|
|
365
|
-
export function useCanGoBack(): boolean;
|
|
366
|
-
export function useCanGoBack(...args: any[]): boolean {
|
|
367
|
-
const [, slot] = splitSlot(args);
|
|
368
|
-
const router = useRouter();
|
|
369
|
-
return useStore(
|
|
370
|
-
router.stores.location,
|
|
371
|
-
(location: any) => location.state.__TSR_index !== 0,
|
|
372
|
-
undefined,
|
|
373
|
-
subSlot(slot, 'back'),
|
|
374
|
-
);
|
|
375
|
-
}
|
|
3
|
+
import { createRouterHooks } from './hookBindings';
|
|
4
|
+
|
|
5
|
+
export const {
|
|
6
|
+
useMatch,
|
|
7
|
+
useParams,
|
|
8
|
+
useSearch,
|
|
9
|
+
useLoaderData,
|
|
10
|
+
useLoaderDeps,
|
|
11
|
+
useRouteContext,
|
|
12
|
+
useLocation,
|
|
13
|
+
useMatches,
|
|
14
|
+
useParentMatches,
|
|
15
|
+
useChildMatches,
|
|
16
|
+
useNavigate,
|
|
17
|
+
useCanGoBack,
|
|
18
|
+
} = createRouterHooks(useRouter, () => useContext(matchContext));
|
package/src/index.ts
CHANGED
|
@@ -166,6 +166,8 @@ export { Scripts } from './Scripts.tsrx';
|
|
|
166
166
|
export { ScriptOnce } from './ScriptOnce.tsrx';
|
|
167
167
|
export { Asset } from './Asset.tsrx';
|
|
168
168
|
export type { AssetProps } from './Asset.tsrx';
|
|
169
|
+
export { getAssetKeys } from './assetKeys.ts';
|
|
170
|
+
export type { AssetKeys } from './assetKeys.ts';
|
|
169
171
|
export { useTags } from './headContentUtils';
|
|
170
172
|
export { Html } from './Html';
|
|
171
173
|
export type { HtmlProps } from './Html';
|
|
@@ -1,57 +1,13 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// module-not-found error (the same recovery react-router does).
|
|
5
|
-
//
|
|
6
|
-
// createRoute({ path: 'item/$id', component: lazyRouteComponent(() => import('./Item')) })
|
|
7
|
-
import { use, createElement } from 'octane';
|
|
8
|
-
import { isModuleNotFoundError } from '@tanstack/router-core';
|
|
9
|
-
import { toExternalHydrationThenable } from './externalHydration';
|
|
1
|
+
// The normal entry accepts returned JSX and descriptors after loading.
|
|
2
|
+
import { createElement } from 'octane';
|
|
3
|
+
import { createLazyRouteLoader } from './lazyRouteLoader';
|
|
10
4
|
|
|
11
5
|
export function lazyRouteComponent(
|
|
12
6
|
importer: () => Promise<any>,
|
|
13
7
|
exportName?: string,
|
|
14
8
|
): ((props: any) => any) & { preload: () => Promise<any> | undefined } {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let reload = false;
|
|
19
|
-
|
|
20
|
-
const load = () => {
|
|
21
|
-
if (!loadPromise) {
|
|
22
|
-
loadPromise = importer()
|
|
23
|
-
.then((res) => {
|
|
24
|
-
loadPromise = undefined;
|
|
25
|
-
comp = res[exportName ?? 'default'];
|
|
26
|
-
})
|
|
27
|
-
.catch((err) => {
|
|
28
|
-
error = err;
|
|
29
|
-
if (
|
|
30
|
-
isModuleNotFoundError(error) &&
|
|
31
|
-
error instanceof Error &&
|
|
32
|
-
typeof window !== 'undefined' &&
|
|
33
|
-
typeof sessionStorage !== 'undefined'
|
|
34
|
-
) {
|
|
35
|
-
const key = `tanstack_router_reload:${error.message}`;
|
|
36
|
-
if (!sessionStorage.getItem(key)) {
|
|
37
|
-
sessionStorage.setItem(key, '1');
|
|
38
|
-
reload = true;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
return loadPromise;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const Lazy = (props: any) => {
|
|
47
|
-
if (reload) {
|
|
48
|
-
window.location.reload();
|
|
49
|
-
throw new Promise(() => {});
|
|
50
|
-
}
|
|
51
|
-
if (error) throw error;
|
|
52
|
-
if (!comp) use(toExternalHydrationThenable(load()));
|
|
53
|
-
return createElement(comp, props);
|
|
54
|
-
};
|
|
55
|
-
Lazy.preload = load;
|
|
9
|
+
const loader = createLazyRouteLoader(importer, exportName);
|
|
10
|
+
const Lazy = (props: any) => createElement(loader.read(), props);
|
|
11
|
+
Lazy.preload = loader.preload;
|
|
56
12
|
return Lazy;
|
|
57
13
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Shared import/preload and stale-module recovery. The renderer decides how to
|
|
2
|
+
// invoke the resolved component.
|
|
3
|
+
import { use } from 'octane';
|
|
4
|
+
import { isModuleNotFoundError } from '@tanstack/router-core';
|
|
5
|
+
import { toExternalHydrationThenable } from './externalHydration';
|
|
6
|
+
|
|
7
|
+
export function createLazyRouteLoader(importer: () => Promise<any>, exportName?: string) {
|
|
8
|
+
let loadPromise: Promise<any> | undefined;
|
|
9
|
+
let comp: any;
|
|
10
|
+
let error: any;
|
|
11
|
+
let reload = false;
|
|
12
|
+
const preload = () => {
|
|
13
|
+
if (!loadPromise) {
|
|
14
|
+
loadPromise = importer()
|
|
15
|
+
.then((res) => {
|
|
16
|
+
loadPromise = undefined;
|
|
17
|
+
comp = res[exportName ?? 'default'];
|
|
18
|
+
})
|
|
19
|
+
.catch((err) => {
|
|
20
|
+
error = err;
|
|
21
|
+
if (
|
|
22
|
+
isModuleNotFoundError(error) &&
|
|
23
|
+
error instanceof Error &&
|
|
24
|
+
typeof window !== 'undefined' &&
|
|
25
|
+
typeof sessionStorage !== 'undefined'
|
|
26
|
+
) {
|
|
27
|
+
const key = `tanstack_router_reload:${error.message}`;
|
|
28
|
+
if (!sessionStorage.getItem(key)) {
|
|
29
|
+
sessionStorage.setItem(key, '1');
|
|
30
|
+
reload = true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return loadPromise;
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
preload,
|
|
39
|
+
read() {
|
|
40
|
+
if (reload) {
|
|
41
|
+
window.location.reload();
|
|
42
|
+
throw new Promise(() => {});
|
|
43
|
+
}
|
|
44
|
+
if (error) throw error;
|
|
45
|
+
if (!comp) use(toExternalHydrationThenable(preload()));
|
|
46
|
+
return comp;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|