@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.12 → 3.2.0-ultramodern.120
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/dist/cjs/cli/index.js +47 -9
- package/dist/cjs/cli/routeSplitting.js +87 -0
- package/dist/cjs/cli/tanstackTypes.js +230 -63
- package/dist/cjs/cli.js +12 -8
- package/dist/cjs/runtime/DefaultNotFound.js +9 -5
- package/dist/cjs/runtime/basepathRewrite.js +12 -8
- package/dist/cjs/runtime/dataMutation.js +9 -5
- package/dist/cjs/runtime/hooks.js +9 -5
- package/dist/cjs/runtime/hydrationBoundary.js +48 -0
- package/dist/cjs/runtime/index.js +330 -74
- package/dist/cjs/runtime/lifecycle.js +15 -11
- package/dist/cjs/runtime/outlet.js +58 -0
- package/dist/cjs/runtime/plugin.js +203 -98
- package/dist/cjs/runtime/plugin.node.js +38 -16
- package/dist/cjs/runtime/plugin.worker.js +53 -0
- package/dist/cjs/runtime/prefetchLink.js +10 -6
- package/dist/cjs/runtime/routeTree.js +81 -17
- package/dist/cjs/runtime/rsc/ClientSlot.js +9 -5
- package/dist/cjs/runtime/rsc/CompositeComponent.js +9 -5
- package/dist/cjs/runtime/rsc/ReplayableStream.js +14 -9
- package/dist/cjs/runtime/rsc/RscNodeRenderer.js +9 -5
- package/dist/cjs/runtime/rsc/SlotContext.js +9 -5
- package/dist/cjs/runtime/rsc/client.js +9 -5
- package/dist/cjs/runtime/rsc/createRscProxy.js +9 -5
- package/dist/cjs/runtime/rsc/index.js +9 -5
- package/dist/cjs/runtime/rsc/payloadRouter.js +9 -5
- package/dist/cjs/runtime/rsc/server.js +9 -5
- package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +9 -5
- package/dist/cjs/runtime/rsc/symbols.js +20 -15
- package/dist/cjs/runtime/types.js +31 -1
- package/dist/cjs/runtime/utils.js +9 -5
- package/dist/cjs/runtime.js +9 -5
- package/dist/esm/cli/index.mjs +28 -6
- package/dist/esm/cli/routeSplitting.mjs +43 -0
- package/dist/esm/cli/tanstackTypes.mjs +219 -59
- package/dist/esm/runtime/hydrationBoundary.mjs +10 -0
- package/dist/esm/runtime/index.mjs +3 -2
- package/dist/esm/runtime/outlet.mjs +17 -0
- package/dist/esm/runtime/plugin.mjs +197 -96
- package/dist/esm/runtime/plugin.node.mjs +30 -12
- package/dist/esm/runtime/plugin.worker.mjs +1 -0
- package/dist/esm/runtime/prefetchLink.mjs +1 -1
- package/dist/esm/runtime/routeTree.mjs +73 -13
- package/dist/esm/runtime/types.mjs +7 -0
- package/dist/esm-node/cli/index.mjs +28 -6
- package/dist/esm-node/cli/routeSplitting.mjs +44 -0
- package/dist/esm-node/cli/tanstackTypes.mjs +219 -59
- package/dist/esm-node/runtime/hydrationBoundary.mjs +11 -0
- package/dist/esm-node/runtime/index.mjs +3 -2
- package/dist/esm-node/runtime/outlet.mjs +18 -0
- package/dist/esm-node/runtime/plugin.mjs +197 -96
- package/dist/esm-node/runtime/plugin.node.mjs +30 -12
- package/dist/esm-node/runtime/plugin.worker.mjs +2 -0
- package/dist/esm-node/runtime/prefetchLink.mjs +1 -1
- package/dist/esm-node/runtime/routeTree.mjs +73 -13
- package/dist/esm-node/runtime/types.mjs +7 -0
- package/dist/types/cli/index.d.ts +7 -1
- package/dist/types/cli/routeSplitting.d.ts +29 -0
- package/dist/types/cli/tanstackTypes.d.ts +9 -0
- package/dist/types/runtime/hooks.d.ts +9 -24
- package/dist/types/runtime/hydrationBoundary.d.ts +2 -0
- package/dist/types/runtime/index.d.ts +5 -2
- package/dist/types/runtime/outlet.d.ts +2 -0
- package/dist/types/runtime/plugin.d.ts +1 -1
- package/dist/types/runtime/plugin.node.d.ts +1 -1
- package/dist/types/runtime/plugin.worker.d.ts +1 -0
- package/dist/types/runtime/types.d.ts +7 -0
- package/package.json +20 -20
- package/src/cli/index.ts +59 -2
- package/src/cli/routeSplitting.ts +81 -0
- package/src/cli/tanstackTypes.ts +347 -67
- package/src/runtime/hydrationBoundary.tsx +12 -0
- package/src/runtime/index.tsx +107 -2
- package/src/runtime/outlet.tsx +48 -0
- package/src/runtime/plugin.node.tsx +58 -8
- package/src/runtime/plugin.tsx +372 -157
- package/src/runtime/plugin.worker.tsx +4 -0
- package/src/runtime/prefetchLink.tsx +1 -1
- package/src/runtime/routeTree.ts +194 -23
- package/src/runtime/ssr-shim.d.ts +1 -3
- package/src/runtime/types.ts +13 -0
- package/tests/router/cli.test.ts +315 -0
- package/tests/router/fastDefaults.test.ts +25 -0
- package/tests/router/hydrationBoundary.test.tsx +23 -0
- package/tests/router/prefetchLink.test.tsx +43 -7
- package/tests/router/routeTree.test.ts +416 -1
- package/tests/router/tanstackTypes.test.ts +415 -1
package/src/runtime/plugin.tsx
CHANGED
|
@@ -24,8 +24,7 @@ import {
|
|
|
24
24
|
useNavigate,
|
|
25
25
|
useRouter,
|
|
26
26
|
} from '@tanstack/react-router';
|
|
27
|
-
import {
|
|
28
|
-
import * as React from 'react';
|
|
27
|
+
import { hydrate as hydrateTanstackRouter } from '@tanstack/react-router/ssr/client';
|
|
29
28
|
import { useContext, useMemo } from 'react';
|
|
30
29
|
import { createModernBasepathRewrite } from './basepathRewrite';
|
|
31
30
|
import {
|
|
@@ -37,13 +36,19 @@ import {
|
|
|
37
36
|
onBeforeHydrateRouter as onBeforeHydrateRouterHook,
|
|
38
37
|
type RouterExtendsHooks,
|
|
39
38
|
} from './hooks';
|
|
39
|
+
import { wrapTanstackSsrHydrationBoundary } from './hydrationBoundary';
|
|
40
40
|
import {
|
|
41
41
|
applyRouterRuntimeState,
|
|
42
42
|
type RouterLifecycleContext,
|
|
43
43
|
} from './lifecycle';
|
|
44
|
+
import { withModernRouteMatchContext } from './outlet';
|
|
45
|
+
import { Link } from './prefetchLink';
|
|
44
46
|
import { createRouteTreeFromRouteObjects } from './routeTree';
|
|
45
47
|
import { getTanstackRscSerializationAdapters } from './rsc/client';
|
|
46
|
-
import
|
|
48
|
+
import {
|
|
49
|
+
getModernTanstackRouterFastDefaults,
|
|
50
|
+
type RouterConfig,
|
|
51
|
+
} from './types';
|
|
47
52
|
import { createRouteObjectsFromConfig, urlJoin } from './utils';
|
|
48
53
|
|
|
49
54
|
const BLOCKING_SUBSCRIBE_SYMBOL = Symbol.for(
|
|
@@ -52,7 +57,6 @@ const BLOCKING_SUBSCRIBE_SYMBOL = Symbol.for(
|
|
|
52
57
|
const BLOCKING_STATE_SYMBOL = Symbol.for(
|
|
53
58
|
'@modern-js/plugin-tanstack:blocking-state',
|
|
54
59
|
);
|
|
55
|
-
|
|
56
60
|
type TanstackRouterWithSubscribe = {
|
|
57
61
|
[BLOCKING_STATE_SYMBOL]?: () => boolean;
|
|
58
62
|
[BLOCKING_SUBSCRIBE_SYMBOL]?: boolean;
|
|
@@ -66,6 +70,30 @@ type WindowWithTanstackSsr = Window & {
|
|
|
66
70
|
$_TSR?: unknown;
|
|
67
71
|
};
|
|
68
72
|
|
|
73
|
+
type RouteComponentPreloadable = {
|
|
74
|
+
load?: () => Promise<unknown>;
|
|
75
|
+
preload?: () => Promise<unknown>;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
type ModernRouteModule = {
|
|
79
|
+
Component?: unknown;
|
|
80
|
+
default?: unknown;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
type RouterWithPreloadableRoutes = AnyRouter & {
|
|
84
|
+
routesById?: Record<
|
|
85
|
+
string,
|
|
86
|
+
{
|
|
87
|
+
options?: {
|
|
88
|
+
component?: unknown;
|
|
89
|
+
staticData?: {
|
|
90
|
+
modernRouteId?: string;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
>;
|
|
95
|
+
};
|
|
96
|
+
|
|
69
97
|
type TanstackRouterRuntimeConfig = {
|
|
70
98
|
plugins?: TanstackRouterRuntimePlugin[];
|
|
71
99
|
router?: Partial<RouterConfig>;
|
|
@@ -129,6 +157,151 @@ function wrapRouterSubscribeWithBlockState(
|
|
|
129
157
|
target[BLOCKING_SUBSCRIBE_SYMBOL] = true;
|
|
130
158
|
}
|
|
131
159
|
|
|
160
|
+
type RouterHydrationRecord = {
|
|
161
|
+
error?: unknown;
|
|
162
|
+
promise: Promise<unknown>;
|
|
163
|
+
status: 'pending' | 'fulfilled' | 'rejected';
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const routerHydrationRecords = new WeakMap<AnyRouter, RouterHydrationRecord>();
|
|
167
|
+
const routeModulesKey = '_routeModules';
|
|
168
|
+
|
|
169
|
+
function pickRouteModuleComponent(
|
|
170
|
+
routeModule: unknown,
|
|
171
|
+
seen: Set<unknown> = new Set(),
|
|
172
|
+
): unknown {
|
|
173
|
+
if (
|
|
174
|
+
typeof routeModule === 'function' ||
|
|
175
|
+
(routeModule &&
|
|
176
|
+
typeof routeModule === 'object' &&
|
|
177
|
+
'$$typeof' in routeModule)
|
|
178
|
+
) {
|
|
179
|
+
return routeModule;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!routeModule || typeof routeModule !== 'object') {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
if (seen.has(routeModule)) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
seen.add(routeModule);
|
|
189
|
+
|
|
190
|
+
const module = routeModule as ModernRouteModule;
|
|
191
|
+
for (const candidate of [module.default, module.Component]) {
|
|
192
|
+
const component = pickRouteModuleComponent(candidate, seen);
|
|
193
|
+
if (component) {
|
|
194
|
+
return component;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function getCachedRouteModule(routeId: string) {
|
|
202
|
+
if (typeof window === 'undefined') {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return (window as unknown as Record<string, Record<string, unknown>>)[
|
|
207
|
+
routeModulesKey
|
|
208
|
+
]?.[routeId];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function preloadHydratedRouteComponents(router: AnyRouter): Promise<void> {
|
|
212
|
+
const preloadableRouter = router as RouterWithPreloadableRoutes;
|
|
213
|
+
const routesById = preloadableRouter.routesById || {};
|
|
214
|
+
const matches = preloadableRouter.stores.matches.get() as Array<{
|
|
215
|
+
routeId?: string;
|
|
216
|
+
}>;
|
|
217
|
+
|
|
218
|
+
return Promise.all(
|
|
219
|
+
matches.map(match => {
|
|
220
|
+
if (match.routeId === undefined || match.routeId === '') {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const route = routesById[match.routeId];
|
|
225
|
+
const component = route?.options?.component as RouteComponentPreloadable;
|
|
226
|
+
const preload = component?.load || component?.preload;
|
|
227
|
+
if (typeof preload !== 'function') {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return Promise.resolve(preload.call(component)).then(routeModule => {
|
|
232
|
+
const modernRouteId = route?.options?.staticData?.modernRouteId;
|
|
233
|
+
const cachedRouteModule =
|
|
234
|
+
typeof modernRouteId === 'string' && modernRouteId !== ''
|
|
235
|
+
? getCachedRouteModule(modernRouteId)
|
|
236
|
+
: undefined;
|
|
237
|
+
const resolvedComponent = pickRouteModuleComponent(
|
|
238
|
+
cachedRouteModule ?? routeModule,
|
|
239
|
+
);
|
|
240
|
+
if (
|
|
241
|
+
resolvedComponent !== undefined &&
|
|
242
|
+
typeof modernRouteId === 'string' &&
|
|
243
|
+
modernRouteId !== ''
|
|
244
|
+
) {
|
|
245
|
+
route.options.component = withModernRouteMatchContext(
|
|
246
|
+
resolvedComponent,
|
|
247
|
+
modernRouteId,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}),
|
|
252
|
+
).then(() => undefined);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getTanstackSsrHydrationRecord(router: AnyRouter) {
|
|
256
|
+
const existingHydrationRecord = routerHydrationRecords.get(router);
|
|
257
|
+
if (existingHydrationRecord !== undefined) {
|
|
258
|
+
return existingHydrationRecord;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const hydrationRecord: RouterHydrationRecord = {
|
|
262
|
+
promise: Promise.resolve(),
|
|
263
|
+
status: 'pending',
|
|
264
|
+
};
|
|
265
|
+
routerHydrationRecords.set(router, hydrationRecord);
|
|
266
|
+
try {
|
|
267
|
+
hydrationRecord.promise = hydrateTanstackRouter(router)
|
|
268
|
+
.then(value => preloadHydratedRouteComponents(router).then(() => value))
|
|
269
|
+
.then(
|
|
270
|
+
value => {
|
|
271
|
+
hydrationRecord.status = 'fulfilled';
|
|
272
|
+
return value;
|
|
273
|
+
},
|
|
274
|
+
error => {
|
|
275
|
+
hydrationRecord.status = 'rejected';
|
|
276
|
+
hydrationRecord.error = error;
|
|
277
|
+
throw error;
|
|
278
|
+
},
|
|
279
|
+
);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
hydrationRecord.status = 'rejected';
|
|
282
|
+
hydrationRecord.error = error;
|
|
283
|
+
hydrationRecord.promise = Promise.reject(error);
|
|
284
|
+
hydrationRecord.promise.catch(() => {});
|
|
285
|
+
}
|
|
286
|
+
return hydrationRecord;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function getTanstackSsrHydrationPromise(router: AnyRouter) {
|
|
290
|
+
return getTanstackSsrHydrationRecord(router).promise;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function hasTanstackSsrHydrationRecord(router: AnyRouter) {
|
|
294
|
+
return routerHydrationRecords.has(router);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function ModernRouterClient({ router }: { router: AnyRouter }) {
|
|
298
|
+
const hydrationRecord = getTanstackSsrHydrationRecord(router);
|
|
299
|
+
if (hydrationRecord.status === 'rejected') {
|
|
300
|
+
throw hydrationRecord.error;
|
|
301
|
+
}
|
|
302
|
+
return <RouterProvider router={router} />;
|
|
303
|
+
}
|
|
304
|
+
|
|
132
305
|
function stripSyntheticNotFoundRoute(routes: RouteObject[]): RouteObject[] {
|
|
133
306
|
return routes
|
|
134
307
|
.filter(route => !(route.path === '*' && !route.id && !route.loader))
|
|
@@ -157,26 +330,168 @@ export const tanstackRouterPlugin = (
|
|
|
157
330
|
onBeforeHydrateRouter: onBeforeHydrateRouterHook,
|
|
158
331
|
},
|
|
159
332
|
setup: (api: TanstackRouterPluginAPI) => {
|
|
160
|
-
api.
|
|
333
|
+
const hooks = api.getHooks();
|
|
334
|
+
let cachedRouteObjects: RouteObject[] | undefined;
|
|
335
|
+
let cachedRouteTree: ReturnType<
|
|
336
|
+
typeof createRouteTreeFromRouteObjects
|
|
337
|
+
> | null = null;
|
|
338
|
+
let cachedRouter: AnyRouter | null = null;
|
|
339
|
+
let cachedRouterBasepath: string | null = null;
|
|
340
|
+
|
|
341
|
+
const getMergedConfig = () => {
|
|
161
342
|
const pluginConfig = api.getRuntimeConfig() as {
|
|
162
343
|
router?: Partial<RouterConfig>;
|
|
163
344
|
};
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
345
|
+
return merge(pluginConfig.router || {}, userConfig) as RouterConfig;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const getRouteObjects = () => {
|
|
349
|
+
if (typeof cachedRouteObjects !== 'undefined') {
|
|
350
|
+
return cachedRouteObjects;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const mergedConfig = getMergedConfig();
|
|
354
|
+
const { routesConfig, createRoutes } = mergedConfig;
|
|
355
|
+
const finalRouteConfig = {
|
|
356
|
+
routes: getGlobalRoutes(),
|
|
357
|
+
globalApp: getGlobalLayoutApp(),
|
|
358
|
+
...routesConfig,
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const routeObjects = createRoutes
|
|
362
|
+
? createRoutes()
|
|
363
|
+
: createRouteObjectsFromConfig({
|
|
364
|
+
routesConfig: finalRouteConfig,
|
|
365
|
+
}) || [];
|
|
366
|
+
|
|
367
|
+
const normalizedRouteObjects = createRoutes
|
|
368
|
+
? routeObjects
|
|
369
|
+
: stripSyntheticNotFoundRoute(routeObjects);
|
|
370
|
+
|
|
371
|
+
cachedRouteObjects = hooks.modifyRoutes.call(
|
|
372
|
+
normalizedRouteObjects,
|
|
373
|
+
) as RouteObject[];
|
|
374
|
+
return cachedRouteObjects;
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const getRouteTree = () => {
|
|
378
|
+
if (cachedRouteTree) {
|
|
379
|
+
return cachedRouteTree;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const routeObjects = getRouteObjects();
|
|
383
|
+
if (!routeObjects.length) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
cachedRouteTree = createRouteTreeFromRouteObjects(routeObjects, {
|
|
388
|
+
rscPayloadRouter: getGlobalEnableRsc(),
|
|
389
|
+
});
|
|
390
|
+
return cachedRouteTree;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const selectBasePath = (pathname: string) => {
|
|
394
|
+
const { serverBase = [] } = getMergedConfig();
|
|
395
|
+
const match = serverBase.find(baseUrl =>
|
|
396
|
+
isSegmentPrefix(pathname, baseUrl),
|
|
397
|
+
);
|
|
398
|
+
return match || '/';
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const getClientBasename = (runtimeContext: TInternalRuntimeContext) => {
|
|
402
|
+
const { basename = '' } = getMergedConfig();
|
|
403
|
+
const baseUrl = selectBasePath(location.pathname).replace(/^\/*/, '/');
|
|
404
|
+
return baseUrl === '/'
|
|
405
|
+
? urlJoin(
|
|
406
|
+
baseUrl,
|
|
407
|
+
runtimeContext._internalRouterBaseName || basename || '',
|
|
408
|
+
)
|
|
409
|
+
: baseUrl;
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const getRouter = (
|
|
413
|
+
runtimeContext: TInternalRuntimeContext,
|
|
414
|
+
_basename: string,
|
|
415
|
+
) => {
|
|
416
|
+
const routeTree = getRouteTree();
|
|
417
|
+
if (!routeTree) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const lifecycleContext: RouterLifecycleContext = {
|
|
422
|
+
framework: 'tanstack',
|
|
423
|
+
phase: 'client-create',
|
|
424
|
+
routes: getRouteObjects(),
|
|
425
|
+
runtimeContext,
|
|
426
|
+
basename: _basename,
|
|
427
|
+
};
|
|
428
|
+
hooks.onBeforeCreateRouter.call(lifecycleContext);
|
|
429
|
+
|
|
430
|
+
if (cachedRouter && cachedRouterBasepath === _basename) {
|
|
431
|
+
wrapRouterSubscribeWithBlockState(
|
|
432
|
+
cachedRouter,
|
|
433
|
+
runtimeContext.unstable_getBlockNavState,
|
|
434
|
+
);
|
|
435
|
+
hooks.onAfterCreateRouter.call({
|
|
436
|
+
...lifecycleContext,
|
|
437
|
+
router: cachedRouter,
|
|
438
|
+
runtimeContext,
|
|
439
|
+
});
|
|
440
|
+
return cachedRouter;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const mergedConfig = getMergedConfig();
|
|
444
|
+
const { supportHtml5History = true } = mergedConfig;
|
|
445
|
+
const history = supportHtml5History
|
|
446
|
+
? createBrowserHistory()
|
|
447
|
+
: createHashHistory();
|
|
448
|
+
const rewrite = createModernBasepathRewrite(_basename);
|
|
449
|
+
const serializationAdapters = getGlobalEnableRsc()
|
|
450
|
+
? getTanstackRscSerializationAdapters()
|
|
451
|
+
: undefined;
|
|
452
|
+
|
|
453
|
+
cachedRouter = createRouter({
|
|
454
|
+
...getModernTanstackRouterFastDefaults(mergedConfig),
|
|
455
|
+
routeTree,
|
|
456
|
+
basepath: '/',
|
|
457
|
+
rewrite,
|
|
458
|
+
history,
|
|
459
|
+
context: {},
|
|
460
|
+
...(serializationAdapters ? { serializationAdapters } : {}),
|
|
461
|
+
});
|
|
462
|
+
cachedRouterBasepath = _basename;
|
|
463
|
+
wrapRouterSubscribeWithBlockState(
|
|
464
|
+
cachedRouter,
|
|
465
|
+
runtimeContext.unstable_getBlockNavState,
|
|
466
|
+
);
|
|
467
|
+
hooks.onAfterCreateRouter.call({
|
|
468
|
+
...lifecycleContext,
|
|
469
|
+
router: cachedRouter,
|
|
470
|
+
runtimeContext,
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
return cachedRouter;
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
api.onBeforeRender(context => {
|
|
477
|
+
const mergedConfig = getMergedConfig();
|
|
168
478
|
if (
|
|
169
479
|
typeof window !== 'undefined' &&
|
|
170
|
-
(window as { _SSR_DATA?: unknown })._SSR_DATA &&
|
|
480
|
+
(window as { _SSR_DATA?: unknown })._SSR_DATA !== undefined &&
|
|
171
481
|
mergedConfig.unstable_reloadOnURLMismatch
|
|
172
482
|
) {
|
|
173
483
|
const { ssrContext } = context;
|
|
174
484
|
const currentPathname = normalizePathname(window.location.pathname);
|
|
175
485
|
const initialPathname =
|
|
176
|
-
ssrContext?.request?.pathname
|
|
177
|
-
|
|
486
|
+
typeof ssrContext?.request?.pathname === 'string'
|
|
487
|
+
? normalizePathname(ssrContext.request.pathname)
|
|
488
|
+
: undefined;
|
|
178
489
|
|
|
179
|
-
if (
|
|
490
|
+
if (
|
|
491
|
+
initialPathname !== undefined &&
|
|
492
|
+
initialPathname !== '' &&
|
|
493
|
+
initialPathname !== currentPathname
|
|
494
|
+
) {
|
|
180
495
|
const errorMsg = `The initial URL ${initialPathname} and the URL ${currentPathname} to be hydrated do not match, reload.`;
|
|
181
496
|
console.error(errorMsg);
|
|
182
497
|
window.location.reload();
|
|
@@ -184,163 +499,55 @@ export const tanstackRouterPlugin = (
|
|
|
184
499
|
}
|
|
185
500
|
|
|
186
501
|
context.router = {
|
|
502
|
+
Link,
|
|
187
503
|
useMatches,
|
|
188
504
|
useLocation,
|
|
189
505
|
useNavigate,
|
|
190
506
|
useRouter,
|
|
191
507
|
};
|
|
192
|
-
});
|
|
193
508
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
509
|
+
const hasSSRBootstrap =
|
|
510
|
+
typeof window !== 'undefined' &&
|
|
511
|
+
Boolean((window as WindowWithTanstackSsr).$_TSR);
|
|
512
|
+
if (hasSSRBootstrap && getRouteObjects().length > 0) {
|
|
513
|
+
const runtimeContext = context as TInternalRuntimeContext;
|
|
514
|
+
const router = getRouter(
|
|
515
|
+
runtimeContext,
|
|
516
|
+
getClientBasename(runtimeContext),
|
|
517
|
+
);
|
|
518
|
+
if (router !== undefined && router !== null) {
|
|
519
|
+
return getTanstackSsrHydrationPromise(router).then(() => undefined);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
207
522
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
globalApp: getGlobalLayoutApp(),
|
|
211
|
-
...routesConfig,
|
|
212
|
-
};
|
|
523
|
+
return;
|
|
524
|
+
});
|
|
213
525
|
|
|
214
|
-
|
|
526
|
+
api.wrapRoot(App => {
|
|
527
|
+
if (getRouteObjects().length === 0) {
|
|
215
528
|
return App;
|
|
216
529
|
}
|
|
217
530
|
|
|
218
|
-
const hooks = api.getHooks();
|
|
219
|
-
|
|
220
|
-
let cachedRouteObjects: RouteObject[] | undefined;
|
|
221
|
-
|
|
222
|
-
const getRouteObjects = () => {
|
|
223
|
-
if (typeof cachedRouteObjects !== 'undefined') {
|
|
224
|
-
return cachedRouteObjects;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const routeObjects = createRoutes
|
|
228
|
-
? createRoutes()
|
|
229
|
-
: createRouteObjectsFromConfig({
|
|
230
|
-
routesConfig: finalRouteConfig,
|
|
231
|
-
}) || [];
|
|
232
|
-
|
|
233
|
-
const normalizedRouteObjects = createRoutes
|
|
234
|
-
? routeObjects
|
|
235
|
-
: stripSyntheticNotFoundRoute(routeObjects);
|
|
236
|
-
|
|
237
|
-
cachedRouteObjects = hooks.modifyRoutes.call(
|
|
238
|
-
normalizedRouteObjects,
|
|
239
|
-
) as RouteObject[];
|
|
240
|
-
return cachedRouteObjects;
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
const selectBasePath = (pathname: string) => {
|
|
244
|
-
const match = serverBase.find(baseUrl =>
|
|
245
|
-
isSegmentPrefix(pathname, baseUrl),
|
|
246
|
-
);
|
|
247
|
-
return match || '/';
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
let cachedRouteTree: ReturnType<
|
|
251
|
-
typeof createRouteTreeFromRouteObjects
|
|
252
|
-
> | null = null;
|
|
253
|
-
let cachedRouter: AnyRouter | null = null;
|
|
254
|
-
let cachedRouterBasepath: string | null = null;
|
|
255
|
-
|
|
256
531
|
const RouterWrapper = () => {
|
|
257
532
|
const runtimeContext = useContext(
|
|
258
533
|
InternalRuntimeContext,
|
|
259
534
|
) as TInternalRuntimeContext;
|
|
260
535
|
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
);
|
|
265
|
-
const _basename =
|
|
266
|
-
baseUrl === '/'
|
|
267
|
-
? urlJoin(
|
|
268
|
-
baseUrl,
|
|
269
|
-
runtimeContext._internalRouterBaseName || basename || '',
|
|
270
|
-
)
|
|
271
|
-
: baseUrl;
|
|
272
|
-
|
|
273
|
-
const routeTree = useMemo(() => {
|
|
274
|
-
if (cachedRouteTree) {
|
|
275
|
-
return cachedRouteTree;
|
|
276
|
-
}
|
|
277
|
-
const routeObjects = getRouteObjects();
|
|
278
|
-
if (!routeObjects.length) {
|
|
279
|
-
return null;
|
|
280
|
-
}
|
|
281
|
-
cachedRouteTree = createRouteTreeFromRouteObjects(routeObjects, {
|
|
282
|
-
rscPayloadRouter: getGlobalEnableRsc(),
|
|
283
|
-
});
|
|
284
|
-
return cachedRouteTree;
|
|
285
|
-
}, []);
|
|
536
|
+
const _basename = getClientBasename(runtimeContext);
|
|
537
|
+
|
|
538
|
+
const routeTree = useMemo(() => getRouteTree(), []);
|
|
286
539
|
|
|
287
540
|
if (!routeTree) {
|
|
288
541
|
return App ? <App /> : null;
|
|
289
542
|
}
|
|
290
543
|
|
|
291
|
-
const router = useMemo(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
};
|
|
299
|
-
hooks.onBeforeCreateRouter.call(lifecycleContext);
|
|
300
|
-
|
|
301
|
-
if (cachedRouter && cachedRouterBasepath === _basename) {
|
|
302
|
-
wrapRouterSubscribeWithBlockState(
|
|
303
|
-
cachedRouter,
|
|
304
|
-
runtimeContext.unstable_getBlockNavState,
|
|
305
|
-
);
|
|
306
|
-
hooks.onAfterCreateRouter.call({
|
|
307
|
-
...lifecycleContext,
|
|
308
|
-
router: cachedRouter,
|
|
309
|
-
runtimeContext,
|
|
310
|
-
});
|
|
311
|
-
return cachedRouter;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const history = supportHtml5History
|
|
315
|
-
? createBrowserHistory()
|
|
316
|
-
: createHashHistory();
|
|
317
|
-
|
|
318
|
-
const rewrite = createModernBasepathRewrite(_basename);
|
|
319
|
-
const serializationAdapters = getGlobalEnableRsc()
|
|
320
|
-
? getTanstackRscSerializationAdapters()
|
|
321
|
-
: undefined;
|
|
322
|
-
|
|
323
|
-
cachedRouter = createRouter({
|
|
324
|
-
routeTree,
|
|
325
|
-
basepath: '/',
|
|
326
|
-
rewrite,
|
|
327
|
-
history,
|
|
328
|
-
context: {},
|
|
329
|
-
...(serializationAdapters ? { serializationAdapters } : {}),
|
|
330
|
-
});
|
|
331
|
-
cachedRouterBasepath = _basename;
|
|
332
|
-
wrapRouterSubscribeWithBlockState(
|
|
333
|
-
cachedRouter,
|
|
334
|
-
runtimeContext.unstable_getBlockNavState,
|
|
335
|
-
);
|
|
336
|
-
hooks.onAfterCreateRouter.call({
|
|
337
|
-
...lifecycleContext,
|
|
338
|
-
router: cachedRouter,
|
|
339
|
-
runtimeContext,
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
return cachedRouter;
|
|
343
|
-
}, [_basename, routeTree, supportHtml5History, runtimeContext]);
|
|
544
|
+
const router = useMemo(
|
|
545
|
+
() => getRouter(runtimeContext, _basename),
|
|
546
|
+
[_basename, routeTree, runtimeContext],
|
|
547
|
+
);
|
|
548
|
+
if (!router) {
|
|
549
|
+
return App ? <App /> : null;
|
|
550
|
+
}
|
|
344
551
|
const runtimeState = applyRouterRuntimeState(runtimeContext, {
|
|
345
552
|
framework: 'tanstack',
|
|
346
553
|
basename: _basename,
|
|
@@ -357,8 +564,10 @@ export const tanstackRouterPlugin = (
|
|
|
357
564
|
|
|
358
565
|
const hasSSRBootstrap =
|
|
359
566
|
typeof window !== 'undefined' &&
|
|
360
|
-
Boolean((window as WindowWithTanstackSsr).$_TSR)
|
|
361
|
-
|
|
567
|
+
(Boolean((window as WindowWithTanstackSsr).$_TSR) ||
|
|
568
|
+
hasTanstackSsrHydrationRecord(router));
|
|
569
|
+
const needsRouterClient = hasSSRBootstrap;
|
|
570
|
+
if (needsRouterClient) {
|
|
362
571
|
hooks.onBeforeHydrateRouter.call({
|
|
363
572
|
...lifecycleContext,
|
|
364
573
|
phase: 'hydrate',
|
|
@@ -367,14 +576,16 @@ export const tanstackRouterPlugin = (
|
|
|
367
576
|
});
|
|
368
577
|
}
|
|
369
578
|
|
|
370
|
-
const RouterContent =
|
|
371
|
-
<
|
|
372
|
-
<RouterClient router={router} />
|
|
373
|
-
</React.Suspense>
|
|
579
|
+
const RouterContent = needsRouterClient ? (
|
|
580
|
+
<ModernRouterClient router={router} />
|
|
374
581
|
) : (
|
|
375
582
|
<RouterProvider router={router} />
|
|
376
583
|
);
|
|
377
|
-
|
|
584
|
+
const HydratableRouterContent = wrapTanstackSsrHydrationBoundary(
|
|
585
|
+
RouterContent,
|
|
586
|
+
hasSSRBootstrap,
|
|
587
|
+
);
|
|
588
|
+
if (needsRouterClient) {
|
|
378
589
|
hooks.onAfterHydrateRouter.call({
|
|
379
590
|
...lifecycleContext,
|
|
380
591
|
phase: 'hydrate',
|
|
@@ -383,7 +594,11 @@ export const tanstackRouterPlugin = (
|
|
|
383
594
|
});
|
|
384
595
|
}
|
|
385
596
|
|
|
386
|
-
return App ?
|
|
597
|
+
return App ? (
|
|
598
|
+
<App>{HydratableRouterContent}</App>
|
|
599
|
+
) : (
|
|
600
|
+
HydratableRouterContent
|
|
601
|
+
);
|
|
387
602
|
};
|
|
388
603
|
|
|
389
604
|
return RouterWrapper;
|