@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.12 → 3.2.0-ultramodern.121

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