@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.
Files changed (87) hide show
  1. package/dist/cjs/cli/index.js +47 -9
  2. package/dist/cjs/cli/routeSplitting.js +87 -0
  3. package/dist/cjs/cli/tanstackTypes.js +230 -63
  4. package/dist/cjs/cli.js +12 -8
  5. package/dist/cjs/runtime/DefaultNotFound.js +9 -5
  6. package/dist/cjs/runtime/basepathRewrite.js +12 -8
  7. package/dist/cjs/runtime/dataMutation.js +9 -5
  8. package/dist/cjs/runtime/hooks.js +9 -5
  9. package/dist/cjs/runtime/hydrationBoundary.js +48 -0
  10. package/dist/cjs/runtime/index.js +330 -74
  11. package/dist/cjs/runtime/lifecycle.js +15 -11
  12. package/dist/cjs/runtime/outlet.js +58 -0
  13. package/dist/cjs/runtime/plugin.js +203 -98
  14. package/dist/cjs/runtime/plugin.node.js +38 -16
  15. package/dist/cjs/runtime/plugin.worker.js +53 -0
  16. package/dist/cjs/runtime/prefetchLink.js +10 -6
  17. package/dist/cjs/runtime/routeTree.js +81 -17
  18. package/dist/cjs/runtime/rsc/ClientSlot.js +9 -5
  19. package/dist/cjs/runtime/rsc/CompositeComponent.js +9 -5
  20. package/dist/cjs/runtime/rsc/ReplayableStream.js +14 -9
  21. package/dist/cjs/runtime/rsc/RscNodeRenderer.js +9 -5
  22. package/dist/cjs/runtime/rsc/SlotContext.js +9 -5
  23. package/dist/cjs/runtime/rsc/client.js +9 -5
  24. package/dist/cjs/runtime/rsc/createRscProxy.js +9 -5
  25. package/dist/cjs/runtime/rsc/index.js +9 -5
  26. package/dist/cjs/runtime/rsc/payloadRouter.js +9 -5
  27. package/dist/cjs/runtime/rsc/server.js +9 -5
  28. package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +9 -5
  29. package/dist/cjs/runtime/rsc/symbols.js +20 -15
  30. package/dist/cjs/runtime/types.js +31 -1
  31. package/dist/cjs/runtime/utils.js +9 -5
  32. package/dist/cjs/runtime.js +9 -5
  33. package/dist/esm/cli/index.mjs +28 -6
  34. package/dist/esm/cli/routeSplitting.mjs +43 -0
  35. package/dist/esm/cli/tanstackTypes.mjs +219 -59
  36. package/dist/esm/runtime/hydrationBoundary.mjs +10 -0
  37. package/dist/esm/runtime/index.mjs +3 -2
  38. package/dist/esm/runtime/outlet.mjs +17 -0
  39. package/dist/esm/runtime/plugin.mjs +197 -96
  40. package/dist/esm/runtime/plugin.node.mjs +30 -12
  41. package/dist/esm/runtime/plugin.worker.mjs +1 -0
  42. package/dist/esm/runtime/prefetchLink.mjs +1 -1
  43. package/dist/esm/runtime/routeTree.mjs +73 -13
  44. package/dist/esm/runtime/types.mjs +7 -0
  45. package/dist/esm-node/cli/index.mjs +28 -6
  46. package/dist/esm-node/cli/routeSplitting.mjs +44 -0
  47. package/dist/esm-node/cli/tanstackTypes.mjs +219 -59
  48. package/dist/esm-node/runtime/hydrationBoundary.mjs +11 -0
  49. package/dist/esm-node/runtime/index.mjs +3 -2
  50. package/dist/esm-node/runtime/outlet.mjs +18 -0
  51. package/dist/esm-node/runtime/plugin.mjs +197 -96
  52. package/dist/esm-node/runtime/plugin.node.mjs +30 -12
  53. package/dist/esm-node/runtime/plugin.worker.mjs +2 -0
  54. package/dist/esm-node/runtime/prefetchLink.mjs +1 -1
  55. package/dist/esm-node/runtime/routeTree.mjs +73 -13
  56. package/dist/esm-node/runtime/types.mjs +7 -0
  57. package/dist/types/cli/index.d.ts +7 -1
  58. package/dist/types/cli/routeSplitting.d.ts +29 -0
  59. package/dist/types/cli/tanstackTypes.d.ts +9 -0
  60. package/dist/types/runtime/hooks.d.ts +9 -24
  61. package/dist/types/runtime/hydrationBoundary.d.ts +2 -0
  62. package/dist/types/runtime/index.d.ts +5 -2
  63. package/dist/types/runtime/outlet.d.ts +2 -0
  64. package/dist/types/runtime/plugin.d.ts +1 -1
  65. package/dist/types/runtime/plugin.node.d.ts +1 -1
  66. package/dist/types/runtime/plugin.worker.d.ts +1 -0
  67. package/dist/types/runtime/types.d.ts +7 -0
  68. package/package.json +20 -20
  69. package/src/cli/index.ts +59 -2
  70. package/src/cli/routeSplitting.ts +81 -0
  71. package/src/cli/tanstackTypes.ts +347 -67
  72. package/src/runtime/hydrationBoundary.tsx +12 -0
  73. package/src/runtime/index.tsx +107 -2
  74. package/src/runtime/outlet.tsx +48 -0
  75. package/src/runtime/plugin.node.tsx +58 -8
  76. package/src/runtime/plugin.tsx +372 -157
  77. package/src/runtime/plugin.worker.tsx +4 -0
  78. package/src/runtime/prefetchLink.tsx +1 -1
  79. package/src/runtime/routeTree.ts +194 -23
  80. package/src/runtime/ssr-shim.d.ts +1 -3
  81. package/src/runtime/types.ts +13 -0
  82. package/tests/router/cli.test.ts +315 -0
  83. package/tests/router/fastDefaults.test.ts +25 -0
  84. package/tests/router/hydrationBoundary.test.tsx +23 -0
  85. package/tests/router/prefetchLink.test.tsx +43 -7
  86. package/tests/router/routeTree.test.ts +416 -1
  87. package/tests/router/tanstackTypes.test.ts +415 -1
@@ -24,8 +24,7 @@ import {
24
24
  useNavigate,
25
25
  useRouter,
26
26
  } from '@tanstack/react-router';
27
- import { RouterClient } from '@tanstack/react-router/ssr/client';
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 type { RouterConfig } from './types';
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.onBeforeRender(context => {
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
- const mergedConfig = merge(
165
- pluginConfig.router || {},
166
- userConfig,
167
- ) as RouterConfig;
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
- normalizePathname(ssrContext.request.pathname);
486
+ typeof ssrContext?.request?.pathname === 'string'
487
+ ? normalizePathname(ssrContext.request.pathname)
488
+ : undefined;
178
489
 
179
- if (initialPathname && initialPathname !== currentPathname) {
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
- api.wrapRoot(App => {
195
- const mergedConfig = merge(
196
- api.getRuntimeConfig().router || {},
197
- userConfig,
198
- ) as RouterConfig;
199
-
200
- const {
201
- serverBase = [],
202
- supportHtml5History = true,
203
- basename = '',
204
- routesConfig,
205
- createRoutes,
206
- } = mergedConfig;
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
- const finalRouteConfig = {
209
- routes: getGlobalRoutes(),
210
- globalApp: getGlobalLayoutApp(),
211
- ...routesConfig,
212
- };
523
+ return;
524
+ });
213
525
 
214
- if (!finalRouteConfig.routes && !createRoutes) {
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 baseUrl = selectBasePath(location.pathname).replace(
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
- const lifecycleContext: RouterLifecycleContext = {
293
- framework: 'tanstack',
294
- phase: 'client-create',
295
- routes: getRouteObjects(),
296
- runtimeContext,
297
- basename: _basename,
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
- if (hasSSRBootstrap) {
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 = hasSSRBootstrap ? (
371
- <React.Suspense fallback={null}>
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
- if (hasSSRBootstrap) {
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 ? <App>{RouterContent}</App> : RouterContent;
597
+ return App ? (
598
+ <App>{HydratableRouterContent}</App>
599
+ ) : (
600
+ HydratableRouterContent
601
+ );
387
602
  };
388
603
 
389
604
  return RouterWrapper;
@@ -0,0 +1,4 @@
1
+ export {
2
+ default,
3
+ tanstackRouterPlugin,
4
+ } from './plugin.node';
@@ -28,7 +28,7 @@ function resolvePreloadFromPrefetch(
28
28
  return prefetch;
29
29
  }
30
30
 
31
- return preload;
31
+ return 'viewport';
32
32
  }
33
33
 
34
34
  export type LinkProps<