@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.1 → 3.2.0-ultramodern.100

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