@bleedingdev/modern-js-plugin-tanstack 3.9.0-ultramodern.11 → 3.9.0-ultramodern.13

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 (81) hide show
  1. package/dist/cjs/cli/artifacts.js +2 -1
  2. package/dist/cjs/cli/tanstackTypes/canonicalRoutes.js +7 -7
  3. package/dist/cjs/cli/tanstackTypes/shared.js +0 -14
  4. package/dist/cjs/cli/tanstackTypes/source.js +28 -63
  5. package/dist/cjs/runtime/lifecycle.js +0 -3
  6. package/dist/cjs/runtime/loaderBridge.js +10 -4
  7. package/dist/cjs/runtime/navigation.js +102 -0
  8. package/dist/cjs/runtime/plugin.js +6 -1
  9. package/dist/cjs/runtime/plugin.node.js +81 -63
  10. package/dist/cjs/runtime/routeTree/construction.js +19 -61
  11. package/dist/cjs/runtime/routeTree/loaders.js +1 -1
  12. package/dist/cjs/runtime/routeTree/paths.js +0 -6
  13. package/dist/cjs/runtime/ssrManagedTags.js +2 -12
  14. package/dist/cjs/runtime/utils.js +1 -0
  15. package/dist/cjs/shared/routeDescriptor.js +60 -0
  16. package/dist/esm/cli/artifacts.mjs +2 -1
  17. package/dist/esm/cli/index.mjs +1 -7
  18. package/dist/esm/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
  19. package/dist/esm/cli/tanstackTypes/shared.mjs +1 -9
  20. package/dist/esm/cli/tanstackTypes/source.mjs +29 -64
  21. package/dist/esm/runtime/lifecycle.mjs +1 -1
  22. package/dist/esm/runtime/loaderBridge.mjs +8 -5
  23. package/dist/esm/runtime/navigation.mjs +64 -0
  24. package/dist/esm/runtime/plugin.mjs +6 -1
  25. package/dist/esm/runtime/plugin.node.mjs +83 -65
  26. package/dist/esm/runtime/routeTree/construction.mjs +19 -61
  27. package/dist/esm/runtime/routeTree/loaders.mjs +2 -2
  28. package/dist/esm/runtime/routeTree/paths.mjs +1 -4
  29. package/dist/esm/runtime/ssrManagedTags.mjs +1 -8
  30. package/dist/esm/runtime/utils.mjs +1 -0
  31. package/dist/esm/shared/routeDescriptor.mjs +22 -0
  32. package/dist/esm-node/cli/artifacts.mjs +2 -1
  33. package/dist/esm-node/cli/index.mjs +1 -7
  34. package/dist/esm-node/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
  35. package/dist/esm-node/cli/tanstackTypes/shared.mjs +1 -9
  36. package/dist/esm-node/cli/tanstackTypes/source.mjs +29 -64
  37. package/dist/esm-node/runtime/lifecycle.mjs +1 -1
  38. package/dist/esm-node/runtime/loaderBridge.mjs +8 -5
  39. package/dist/esm-node/runtime/navigation.mjs +65 -0
  40. package/dist/esm-node/runtime/plugin.mjs +6 -1
  41. package/dist/esm-node/runtime/plugin.node.mjs +83 -65
  42. package/dist/esm-node/runtime/routeTree/construction.mjs +19 -61
  43. package/dist/esm-node/runtime/routeTree/loaders.mjs +2 -2
  44. package/dist/esm-node/runtime/routeTree/paths.mjs +1 -4
  45. package/dist/esm-node/runtime/ssrManagedTags.mjs +1 -8
  46. package/dist/esm-node/runtime/utils.mjs +1 -0
  47. package/dist/esm-node/shared/routeDescriptor.mjs +23 -0
  48. package/dist/types/cli/tanstackTypes/shared.d.ts +0 -2
  49. package/dist/types/runtime/lifecycle.d.ts +1 -1
  50. package/dist/types/runtime/loaderBridge.d.ts +1 -0
  51. package/dist/types/runtime/navigation.d.ts +4 -0
  52. package/dist/types/runtime/routeTree/paths.d.ts +0 -2
  53. package/dist/types/runtime/ssrManagedTags.d.ts +0 -1
  54. package/dist/types/runtime/ssrTypes.d.ts +0 -7
  55. package/dist/types/shared/routeDescriptor.d.ts +26 -0
  56. package/package.json +21 -21
  57. package/src/cli/artifacts.ts +6 -1
  58. package/src/cli/tanstackTypes/canonicalRoutes.ts +17 -12
  59. package/src/cli/tanstackTypes/shared.ts +0 -14
  60. package/src/cli/tanstackTypes/source.ts +43 -119
  61. package/src/runtime/lifecycle.ts +0 -1
  62. package/src/runtime/loaderBridge.ts +9 -9
  63. package/src/runtime/navigation.ts +78 -0
  64. package/src/runtime/plugin.node.tsx +111 -91
  65. package/src/runtime/plugin.tsx +6 -0
  66. package/src/runtime/routeTree/construction.ts +32 -112
  67. package/src/runtime/routeTree/loaders.ts +2 -4
  68. package/src/runtime/routeTree/paths.ts +0 -6
  69. package/src/runtime/ssrManagedTags.ts +0 -17
  70. package/src/runtime/ssrTypes.ts +0 -7
  71. package/src/runtime/utils.tsx +1 -0
  72. package/src/shared/routeDescriptor.ts +46 -0
  73. package/tests/router/clientNavigation.ts +13 -0
  74. package/tests/router/generateRouteArtifacts.test.ts +36 -2
  75. package/tests/router/localisedIdentity.test.tsx +7 -2
  76. package/tests/router/prefetchLink.test.tsx +29 -0
  77. package/tests/router/routeHooks.test.ts +2 -1
  78. package/tests/router/serverPlugin.test.ts +150 -0
  79. package/tests/router/tanstackTypes.test.ts +121 -2
  80. package/dist/esm/rslib-runtime~1.mjs +0 -18
  81. package/dist/esm-node/rslib-runtime~0.mjs +0 -19
@@ -23,11 +23,13 @@ import { createModernBasepathRewrite } from './basepathRewrite';
23
23
  import { routerProviderRegistryHooks } from './hooks';
24
24
  import { wrapTanstackSsrHydrationBoundary } from './hydrationBoundary';
25
25
  import {
26
+ applyRouterRuntimeState,
26
27
  applyRouterServerPrepareResult,
27
- createRouterServerSnapshot,
28
28
  getRouterRuntimeState,
29
+ getRouterServerSnapshot,
29
30
  type RouterLifecycleContext,
30
31
  } from './lifecycle';
32
+ import { createTanstackNavigation } from './navigation';
31
33
  import {
32
34
  createTanstackRouteObjects,
33
35
  getTanstackRouteConfig,
@@ -54,7 +56,6 @@ import {
54
56
  collectRouterErrors,
55
57
  createGetSsrHref,
56
58
  routerManagedTagsToHtml,
57
- waitForRouterSerialization,
58
59
  } from './ssrManagedTags';
59
60
  import { preloadMatchedRouteComponents } from './ssrPreload';
60
61
  import type {
@@ -168,116 +169,135 @@ export const tanstackRouterPlugin = (
168
169
  setTanstackRscRouter(serverRouter);
169
170
  }
170
171
 
171
- await attachServerSsrUtils(serverRouter);
172
-
173
- const end = time();
174
-
172
+ let disposed = false;
173
+ let prepared = false;
174
+ const onAbort = () => controller.abort(rawRequest.signal.reason);
175
+ const cleanup = () => {
176
+ if (disposed) return;
177
+ disposed = true;
178
+ rawRequest.signal.removeEventListener('abort', onAbort);
179
+ controller.abort();
180
+ serverRouter.serverSsr?.cleanup();
181
+ };
182
+ // Acquire the disposer before attachment: every subsequent failure,
183
+ // including partial attachment, has an owner. Successful preparation
184
+ // leaves disposal to the response lifetime in the SSR pipeline.
185
+ applyRouterRuntimeState(context, {
186
+ framework: 'tanstack',
187
+ basename: _basename,
188
+ instance: serverRouter,
189
+ cleanup,
190
+ });
191
+ rawRequest.signal.addEventListener('abort', onAbort, { once: true });
175
192
  try {
176
- await tanstackRouter.load({ sync: true });
177
- } finally {
178
- const cost = end();
179
- context.ssrContext?.onTiming?.(LOADER_REPORTER_NAME, cost);
180
- }
193
+ rawRequest.signal.throwIfAborted();
194
+ await attachServerSsrUtils(serverRouter);
195
+ rawRequest.signal.throwIfAborted();
196
+
197
+ const end = time();
181
198
 
182
- const serverLoadResult = serverRouter._serverResult;
183
- if (!serverLoadResult) {
184
199
  try {
185
- serverRouter.serverSsr?.cleanup?.();
186
- } catch {}
187
- throw new Error(
188
- 'TanStack Router completed an SSR load without a server result.',
189
- );
190
- }
200
+ await tanstackRouter.load({ sync: true });
201
+ } finally {
202
+ const cost = end();
203
+ context.ssrContext?.onTiming?.(LOADER_REPORTER_NAME, cost);
204
+ }
191
205
 
192
- if (serverLoadResult.type === 'redirect') {
193
- const { redirect } = serverLoadResult;
206
+ rawRequest.signal.throwIfAborted();
207
+ const serverLoadResult = serverRouter._serverResult;
208
+ if (!serverLoadResult) {
209
+ throw new Error(
210
+ 'TanStack Router completed an SSR load without a server result.',
211
+ );
212
+ }
194
213
 
195
- try {
196
- serverRouter.serverSsr?.cleanup?.();
197
- } catch {}
198
-
199
- return interrupt(
200
- isRSCNavigation
201
- ? handleTanstackRscRedirect(
202
- redirect.headers,
203
- _basename,
204
- redirect.status,
205
- )
206
- : redirect,
207
- );
208
- }
214
+ if (serverLoadResult.type === 'redirect') {
215
+ const { redirect } = serverLoadResult;
216
+
217
+ return interrupt(
218
+ isRSCNavigation
219
+ ? handleTanstackRscRedirect(
220
+ redirect.headers,
221
+ _basename,
222
+ redirect.status,
223
+ )
224
+ : redirect,
225
+ );
226
+ }
227
+
228
+ const routerErrors = collectRouterErrors(tanstackRouter);
229
+ if (routerErrors && loaderFailureMode === 'clientRender') {
230
+ (
231
+ context.ssrContext?.response as
232
+ | { status: (code: number) => void }
233
+ | undefined
234
+ )?.status(200);
235
+ throw Object.values(routerErrors)[0];
236
+ }
237
+
238
+ await preloadMatchedRouteComponents(serverRouter);
209
239
 
210
- const routerErrors = collectRouterErrors(tanstackRouter);
211
- if (routerErrors && loaderFailureMode === 'clientRender') {
212
240
  (
213
241
  context.ssrContext?.response as
214
242
  | { status: (code: number) => void }
215
243
  | undefined
216
- )?.status(200);
217
- try {
218
- serverRouter.serverSsr?.cleanup?.();
219
- } catch {}
220
- throw Object.values(routerErrors)[0];
221
- }
222
-
223
- await preloadMatchedRouteComponents(serverRouter);
244
+ )?.status(serverLoadResult.status);
224
245
 
225
- (
226
- context.ssrContext?.response as
227
- | { status: (code: number) => void }
228
- | undefined
229
- )?.status(serverLoadResult.status);
246
+ await serverRouter.serverSsr?.dehydrate();
230
247
 
231
- await serverRouter.serverSsr?.dehydrate?.();
232
-
233
- if (enableRsc) {
234
- if (isRSCNavigation) {
235
- // RSC navigations consume the server payload directly. Normal HTML SSR
236
- // emits the buffered bootstrap script below and must not wait here
237
- // because Modern's non-streaming hook has not rendered the app yet.
238
- await waitForRouterSerialization(serverRouter);
248
+ if (enableRsc) {
249
+ setTanstackRscServerPayload(
250
+ createTanstackRscServerPayload(serverRouter, {
251
+ omitClientLoaderData: isRSCNavigation,
252
+ }),
253
+ );
239
254
  }
240
255
 
241
- setTanstackRscServerPayload(
242
- createTanstackRscServerPayload(serverRouter, {
243
- omitClientLoaderData: isRSCNavigation,
244
- }),
245
- );
246
- }
247
-
248
- const ssrScriptTags = serverRouter.serverSsr?.takeBufferedScripts?.();
249
- const hydrationScripts = routerManagedTagsToHtml(ssrScriptTags);
250
- const matchedRouteIds = getModernRouteIdsFromMatches(serverRouter);
251
- const routerServerSnapshot: InternalRouterServerSnapshot =
252
- createRouterServerSnapshot({
256
+ const initialScriptTags =
257
+ serverRouter.serverSsr?.takeInitialHydrationScriptTags();
258
+ const hydrationScripts = initialScriptTags
259
+ ? routerManagedTagsToHtml([
260
+ ...initialScriptTags.before,
261
+ initialScriptTags.boundary,
262
+ ])
263
+ : [];
264
+ const matchedRouteIds = getModernRouteIdsFromMatches(serverRouter);
265
+ const routerServerSnapshot: InternalRouterServerSnapshot = {
253
266
  framework: 'tanstack',
254
267
  basename: _basename,
255
268
  statusCode: serverLoadResult.status,
256
269
  errors: routerErrors,
257
270
  matchedRouteIds,
258
271
  hydrationScripts,
259
- });
260
- const runtimeContext = applyRouterServerPrepareResult(
261
- context as TInternalRuntimeContext,
262
- {
263
- snapshot: routerServerSnapshot,
264
- cleanup: () => serverRouter.serverSsr?.cleanup?.(),
265
- state: {
266
- framework: 'tanstack',
267
- basename: _basename,
268
- instance: serverRouter,
269
- hydrationScripts,
270
- matchedRouteIds,
271
- serverSnapshot: routerServerSnapshot,
272
+ };
273
+ const runtimeContext = applyRouterServerPrepareResult(
274
+ context as TInternalRuntimeContext,
275
+ {
276
+ snapshot: routerServerSnapshot,
277
+ cleanup,
278
+ state: {
279
+ framework: 'tanstack',
280
+ basename: _basename,
281
+ instance: serverRouter,
282
+ navigation: createTanstackNavigation(tanstackRouter),
283
+ },
272
284
  },
273
- },
274
- );
275
- hooks.onAfterCreateRouter.call({
276
- ...routerLifecycleContext,
277
- router: serverRouter,
278
- serverSnapshot: routerServerSnapshot,
279
- runtimeContext,
280
- });
285
+ );
286
+ rawRequest.signal.throwIfAborted();
287
+ hooks.onAfterCreateRouter.call({
288
+ ...routerLifecycleContext,
289
+ router: serverRouter,
290
+ serverSnapshot: getRouterServerSnapshot(runtimeContext),
291
+ runtimeContext,
292
+ });
293
+ prepared = true;
294
+ } finally {
295
+ if (!prepared) {
296
+ try {
297
+ cleanup();
298
+ } catch {}
299
+ }
300
+ }
281
301
  });
282
302
 
283
303
  api.wrapRoot(App => {
@@ -32,6 +32,7 @@ import {
32
32
  applyRouterRuntimeState,
33
33
  type RouterLifecycleContext,
34
34
  } from './lifecycle';
35
+ import { createTanstackNavigation } from './navigation';
35
36
  import {
36
37
  createTanstackRouteObjects,
37
38
  getMergedRouterConfig,
@@ -259,10 +260,15 @@ export const tanstackRouterPlugin = (
259
260
  if (!router) {
260
261
  return App ? <App /> : null;
261
262
  }
263
+ const navigation = useMemo(
264
+ () => createTanstackNavigation(router),
265
+ [router],
266
+ );
262
267
  const runtimeState = applyRouterRuntimeState(runtimeContext, {
263
268
  framework: 'tanstack',
264
269
  basename: _basename,
265
270
  instance: router,
271
+ navigation,
266
272
  });
267
273
  const lifecycleContext: RouterLifecycleContext = {
268
274
  framework: 'tanstack',
@@ -1,11 +1,14 @@
1
1
  // @effect-diagnostics asyncFunction:off strictBooleanExpressions:off
2
2
 
3
- import { canonicaliseLocalisedRoutes } from '@modern-js/i18n-runtime-extensions';
4
3
  import { DefaultNotFound } from '@modern-js/runtime/context';
5
4
  import type { RouteObject } from '@modern-js/runtime-utils/router';
6
5
  import type { AnyRoute, AnyRouter } from '@tanstack/react-router';
7
6
  import { rootRouteId } from '@tanstack/react-router';
8
7
 
8
+ import {
9
+ describeRouteTree,
10
+ type RouteDescriptor,
11
+ } from '../../shared/routeDescriptor';
9
12
  import { createRouteStaticData } from '../../shared/routeStaticData';
10
13
  import { withModernRouteMatchContext } from '../outlet';
11
14
  import {
@@ -14,7 +17,6 @@ import {
14
17
  toRouteComponent,
15
18
  } from './components';
16
19
  import { createModernShouldReload, wrapRouteObjectLoader } from './loaders';
17
- import { isRouteObjectPathlessLayout, toTanstackPath } from './paths';
18
20
  import { mergeModernRouteHandle } from './staticData';
19
21
  import {
20
22
  createTanstackRootRoute,
@@ -24,25 +26,16 @@ import {
24
26
  import type {
25
27
  ModernRouteObject,
26
28
  ModernTanstackRootRoute,
27
- MutableTanstackRoute,
28
29
  RouteRevalidationState,
29
30
  RouteTreeOptions,
30
31
  TanstackRootRouteOptions,
31
32
  TanstackRouteOptions,
32
33
  } from './types';
33
34
 
34
- function createRouteFromRouteObject(opts: {
35
- options?: RouteTreeOptions;
36
- parent: AnyRoute;
37
- pathlessFallbackId?: string;
38
- routeObject: RouteObject;
39
- }): AnyRoute {
40
- const {
41
- options = {},
42
- parent,
43
- pathlessFallbackId = 'pathless',
44
- routeObject,
45
- } = opts;
35
+ function createRouteOptions(
36
+ routeObject: RouteObject,
37
+ options: RouteTreeOptions,
38
+ ): TanstackRouteOptions {
46
39
  const modernRouteObject = routeObject as ModernRouteObject;
47
40
  const revalidationState: RouteRevalidationState = {};
48
41
  const shouldRevalidate = modernRouteObject.shouldRevalidate;
@@ -50,13 +43,8 @@ function createRouteFromRouteObject(opts: {
50
43
  shouldRevalidate,
51
44
  revalidationState,
52
45
  );
53
-
54
- const stableFallbackId =
55
- routeObject.id || modernRouteObject.file || pathlessFallbackId;
56
-
57
46
  const component = toRouteComponent(routeObject);
58
47
  const base: TanstackRouteOptions = {
59
- getParentRoute: () => parent,
60
48
  component,
61
49
  pendingComponent: toPendingComponent(routeObject),
62
50
  errorComponent: toErrorComponent(routeObject),
@@ -87,117 +75,49 @@ function createRouteFromRouteObject(opts: {
87
75
  base.shouldReload = shouldReload;
88
76
  }
89
77
 
90
- if (isRouteObjectPathlessLayout(routeObject)) {
91
- base.id = stableFallbackId;
92
- } else {
93
- base.path = routeObject.index
94
- ? '/'
95
- : toTanstackPath((routeObject.path as string) || '');
96
- }
97
-
98
- const route = createTanstackRoute(base);
99
- wrapRouteComponentWithModernContext(route, component, routeObject.id);
78
+ return base;
79
+ }
100
80
 
101
- const children = routeObject.children;
102
- if (children && children.length > 0) {
103
- const childRoutes = children.map((child: RouteObject, index) =>
104
- createRouteFromRouteObject({
105
- options,
106
- parent: route,
107
- pathlessFallbackId: `pathless-${index}`,
108
- routeObject: child,
109
- }),
81
+ function createRouteFromDescriptor(
82
+ descriptor: RouteDescriptor<RouteObject>,
83
+ parent: AnyRoute,
84
+ options: RouteTreeOptions,
85
+ ): AnyRoute {
86
+ const { source, location, children } = descriptor;
87
+ const routeOptions = createRouteOptions(source, options);
88
+ const route = createTanstackRoute({
89
+ ...routeOptions,
90
+ ...location,
91
+ getParentRoute: () => parent,
92
+ });
93
+ wrapRouteComponentWithModernContext(route, routeOptions.component, source.id);
94
+ if (children.length) {
95
+ route.addChildren(
96
+ children.map(child => createRouteFromDescriptor(child, route, options)),
110
97
  );
111
- route.addChildren(childRoutes);
112
98
  }
113
-
114
99
  return route;
115
100
  }
116
101
 
117
- function getRootLikeRouteObject(routes: RouteObject[]) {
118
- return routes.find(route => route.path === '/' && !route.index);
119
- }
120
-
121
102
  export function createRouteTreeFromRouteObjects(
122
103
  routes: RouteObject[],
123
104
  options: RouteTreeOptions = {},
124
105
  ): ModernTanstackRootRoute {
125
- routes = canonicaliseLocalisedRoutes(routes);
126
- const rootLikeRoute = getRootLikeRouteObject(routes) as
127
- | ModernRouteObject
128
- | undefined;
129
- const rootRevalidationState: RouteRevalidationState = {};
130
- const rootShouldRevalidate = rootLikeRoute?.shouldRevalidate;
131
- const rootShouldReload = createModernShouldReload(
132
- rootShouldRevalidate,
133
- rootRevalidationState,
134
- );
135
-
136
- const rootComponent = rootLikeRoute
137
- ? toRouteComponent(rootLikeRoute)
138
- : undefined;
106
+ const { root, children } = describeRouteTree(routes);
139
107
  const rootRouteOptions: TanstackRootRouteOptions = {
140
- component: rootComponent,
141
- pendingComponent: rootLikeRoute
142
- ? toPendingComponent(rootLikeRoute)
143
- : undefined,
144
- errorComponent: rootLikeRoute ? toErrorComponent(rootLikeRoute) : undefined,
145
- validateSearch: rootLikeRoute?.validateSearch,
146
- loaderDeps: rootLikeRoute?.loaderDeps,
108
+ ...(root ? createRouteOptions(root, options) : {}),
147
109
  notFoundComponent: DefaultNotFound,
148
- staticData: createRouteStaticData({
149
- modernRouteId: rootLikeRoute?.id,
150
- modernRouteAction: rootLikeRoute?.action,
151
- modernRouteHandle: rootLikeRoute
152
- ? mergeModernRouteHandle(rootLikeRoute)
153
- : undefined,
154
- modernRouteHasAction:
155
- rootLikeRoute?.hasAction || Boolean(rootLikeRoute?.action),
156
- modernRouteHasClientLoader:
157
- rootLikeRoute?.hasClientLoader ||
158
- typeof rootLikeRoute?.clientData !== 'undefined',
159
- modernRouteHasLoader:
160
- rootLikeRoute?.hasLoader || typeof rootLikeRoute?.loader === 'function',
161
- modernRouteIsClientComponent: rootLikeRoute?.isClientComponent,
162
- modernRouteLoader: rootLikeRoute?.loader,
163
- modernRouteShouldRevalidate: rootShouldRevalidate,
164
- }),
165
- loader: rootLikeRoute
166
- ? wrapRouteObjectLoader(rootLikeRoute, rootRevalidationState, options)
167
- : undefined,
168
110
  };
169
- if (rootShouldReload) {
170
- rootRouteOptions.shouldReload = rootShouldReload;
171
- }
172
- if (rootLikeRoute?.inValidSSRRoute) {
173
- rootRouteOptions.ssr = false;
174
- }
175
-
176
111
  const rootRoute = createTanstackRootRoute(rootRouteOptions);
177
- if (rootComponent) {
112
+ if (rootRouteOptions.component) {
178
113
  rootRoute.options.component = withModernRouteMatchContext(
179
- rootComponent,
114
+ rootRouteOptions.component,
180
115
  rootRouteId,
181
116
  ) as typeof rootRoute.options.component;
182
117
  }
183
-
184
- const topLevel = rootLikeRoute
185
- ? [
186
- ...((rootLikeRoute.children as RouteObject[] | undefined) || []),
187
- ...routes.filter(route => route !== rootLikeRoute),
188
- ]
189
- : routes;
190
-
191
- const childRoutes = topLevel.map((routeObject, index) =>
192
- createRouteFromRouteObject({
193
- options,
194
- parent: rootRoute,
195
- pathlessFallbackId: `pathless-${index}`,
196
- routeObject,
197
- }),
118
+ rootRoute.addChildren(
119
+ children.map(child => createRouteFromDescriptor(child, rootRoute, options)),
198
120
  );
199
-
200
- rootRoute.addChildren(childRoutes);
201
121
  return rootRoute as unknown as ModernTanstackRootRoute;
202
122
  }
203
123
 
@@ -3,6 +3,7 @@
3
3
  import type { RouteObject } from '@modern-js/runtime-utils/router';
4
4
  import {
5
5
  getLoaderHref,
6
+ getLoaderSignal,
6
7
  handleModernLoaderError,
7
8
  handleModernLoaderResult,
8
9
  mapSplatParamsForModernLoader,
@@ -118,10 +119,7 @@ export function wrapRouteObjectLoader(
118
119
  } catch {}
119
120
  }
120
121
 
121
- const signal: AbortSignal =
122
- ctx?.abortController?.signal ||
123
- ctx?.signal ||
124
- new AbortController().signal;
122
+ const signal = getLoaderSignal(ctx);
125
123
  const baseRequest: Request | undefined =
126
124
  ctx?.context?.request instanceof Request
127
125
  ? ctx.context.request
@@ -1,7 +1,5 @@
1
1
  // @effect-diagnostics asyncFunction:off strictBooleanExpressions:off
2
2
 
3
- import type { RouteObject } from '@modern-js/runtime-utils/router';
4
-
5
3
  export function toTanstackPath(pathname: string): string {
6
4
  // TanStack Router uses `$param` and `$` (splat) style params.
7
5
  // Modern's conventional routing currently generates React Router style params (e.g. `:id`, `*`).
@@ -30,7 +28,3 @@ export function toTanstackPath(pathname: string): string {
30
28
  })
31
29
  .join('/');
32
30
  }
33
-
34
- export function isRouteObjectPathlessLayout(route: RouteObject) {
35
- return !route.path && !route.index;
36
- }
@@ -6,23 +6,6 @@ import type {
6
6
  TanstackRouterWithServerSsr,
7
7
  } from './ssrTypes';
8
8
 
9
- export async function waitForRouterSerialization(
10
- tanstackRouter: TanstackRouterWithServerSsr,
11
- ) {
12
- const serverSsr = tanstackRouter.serverSsr;
13
- if (
14
- !serverSsr ||
15
- typeof serverSsr.onSerializationFinished !== 'function' ||
16
- serverSsr.isSerializationFinished?.()
17
- ) {
18
- return;
19
- }
20
-
21
- await new Promise<void>(resolve => {
22
- serverSsr.onSerializationFinished?.(resolve);
23
- });
24
- }
25
-
26
9
  function htmlEscapeAttr(value: string) {
27
10
  return value.replace(/&/g, '&amp;').replace(/"/g, '&quot;');
28
11
  }
@@ -34,13 +34,6 @@ type RouterRouteWithOptions = {
34
34
 
35
35
  export type TanstackRouterWithServerSsr = AnyRouter & {
36
36
  routesById?: Record<string, RouterRouteWithOptions>;
37
- serverSsr?: {
38
- cleanup?: () => void;
39
- dehydrate?: () => Promise<void> | void;
40
- isSerializationFinished?: () => boolean;
41
- onSerializationFinished?: (listener: () => void) => void;
42
- takeBufferedScripts?: () => unknown;
43
- };
44
37
  state: AnyRouter['state'] & {
45
38
  matches?: unknown;
46
39
  };
@@ -55,6 +55,7 @@ function toTanstackRouteObject(
55
55
  if (route.type === 'nested') {
56
56
  return {
57
57
  modernLocalisedRoute: Reflect.get(route, 'modernLocalisedRoute'),
58
+ isRoot: Reflect.get(route, 'isRoot'),
58
59
  path: route.path,
59
60
  id: route.id,
60
61
  loader: route.loader,
@@ -0,0 +1,46 @@
1
+ import { toTanstackPath } from '../runtime/routeTree/paths';
2
+
3
+ type RouteShape = {
4
+ id?: string;
5
+ file?: string;
6
+ path?: string;
7
+ index?: boolean;
8
+ isRoot?: boolean;
9
+ children?: RouteShape[];
10
+ };
11
+
12
+ /** Browser-safe structure shared by the executable tree and concrete TS emitter. */
13
+ export type RouteDescriptor<T extends RouteShape> = {
14
+ source: T;
15
+ location: { id: string; path?: never } | { path: string; id?: never };
16
+ hasSplat: boolean;
17
+ children: RouteDescriptor<T>[];
18
+ };
19
+
20
+ export function describeRouteTree<T extends RouteShape>(routes: T[]) {
21
+ const root = routes.find(
22
+ route =>
23
+ route.isRoot === true || (route.path === '/' && route.index !== true),
24
+ );
25
+ const describe = (route: T, index: number): RouteDescriptor<T> => ({
26
+ source: route,
27
+ location:
28
+ (route.path ?? '').length === 0 && route.index !== true
29
+ ? { id: route.id || route.file || `pathless-${index}` }
30
+ : {
31
+ path: route.index === true ? '/' : toTanstackPath(route.path || ''),
32
+ },
33
+ hasSplat: Boolean(route.path?.includes('*')),
34
+ children: ((route.children ?? []) as T[]).map(describe),
35
+ });
36
+ return {
37
+ root,
38
+ children: (root !== undefined
39
+ ? [
40
+ ...((root.children ?? []) as T[]),
41
+ ...routes.filter(route => route !== root),
42
+ ]
43
+ : routes
44
+ ).map(describe),
45
+ };
46
+ }
@@ -0,0 +1,13 @@
1
+ import type { AnyRouter } from '@tanstack/react-router';
2
+
3
+ // Router-core navigates only on the client. Node tests perform the same
4
+ // transition a mounted RouterProvider runs: push the rewritten public URL,
5
+ // then load it.
6
+ export async function navigateOnClient(
7
+ router: AnyRouter,
8
+ options: Parameters<AnyRouter['buildLocation']>[0],
9
+ ) {
10
+ router.history.push(router.buildLocation(options).publicHref);
11
+ router.updateLatestLocation();
12
+ await router.load();
13
+ }