@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,6 +1,7 @@
1
1
  // @effect-diagnostics asyncFunction:off strictBooleanExpressions:off
2
+
3
+ import { DefaultNotFound } from '@modern-js/runtime/context';
2
4
  import type { RouteObject } from '@modern-js/runtime-utils/router';
3
- import type { NestedRoute, PageRoute } from '@modern-js/types';
4
5
  import type {
5
6
  AnyRoute,
6
7
  AnyRouter,
@@ -10,9 +11,16 @@ import {
10
11
  createRootRoute,
11
12
  createRoute,
12
13
  notFound,
13
- redirect,
14
+ rootRouteId,
14
15
  } from '@tanstack/react-router';
15
- import { DefaultNotFound } from './DefaultNotFound';
16
+ import { createElement, type ElementType } from 'react';
17
+ import {
18
+ isRedirectResponse,
19
+ isResponse,
20
+ isTanstackRedirect,
21
+ throwTanstackRedirect,
22
+ } from './loaderBridge';
23
+ import { withModernRouteMatchContext } from './outlet';
16
24
  import {
17
25
  isTanstackRscPayloadNavigationEnabled,
18
26
  loadTanstackRscRouteData,
@@ -75,40 +83,18 @@ type ModernRouteObject = RouteObject & {
75
83
  isClientComponent?: boolean;
76
84
  lazyImport?: () => unknown;
77
85
  loader?: ModernLoader;
86
+ loaderDeps?: unknown;
78
87
  pendingComponent?: unknown;
79
88
  shouldRevalidate?: ModernShouldRevalidate;
80
- };
81
-
82
- type ModernGeneratedRoute = (NestedRoute | PageRoute) & {
83
- _component?: string;
84
- action?: unknown;
85
- children?: ModernGeneratedRoute[];
86
- component?: unknown;
87
- config?: { handle?: Record<string, unknown> } | unknown;
88
- clientData?: unknown;
89
- data?: string;
90
- error?: unknown;
91
- errorComponent?: unknown;
92
- filename?: string;
93
- handle?: Record<string, unknown>;
94
- hasAction?: boolean;
95
- hasClientLoader?: boolean;
96
- hasLoader?: boolean;
97
- inValidSSRRoute?: boolean;
98
- id?: string;
99
- index?: boolean;
100
- isClientComponent?: boolean;
101
- isRoot?: boolean;
102
- lazyImport?: () => unknown;
103
- loader?: ModernLoader;
104
- loading?: unknown;
105
- pendingComponent?: unknown;
106
- path?: string;
107
- shouldRevalidate?: ModernShouldRevalidate;
89
+ validateSearch?: unknown;
108
90
  };
109
91
 
110
92
  type MutableTanstackRoute = AnyRoute & {
111
93
  addChildren: (children: AnyRoute[]) => void;
94
+ id?: string;
95
+ options: {
96
+ component?: unknown;
97
+ };
112
98
  };
113
99
 
114
100
  type TanstackRouteOptions = Record<string, unknown>;
@@ -118,6 +104,15 @@ type ModernDeferredDataLike = {
118
104
  __modern_deferred?: unknown;
119
105
  data?: unknown;
120
106
  };
107
+ type ModernRouteModule = {
108
+ Component?: unknown;
109
+ default?: unknown;
110
+ };
111
+ type PreloadableComponent = {
112
+ (props: Record<string, unknown>): ReturnType<typeof createElement>;
113
+ load?: () => Promise<unknown>;
114
+ preload?: () => Promise<unknown>;
115
+ };
121
116
  type RouteTreeOptions = {
122
117
  rscPayloadRouter?: boolean;
123
118
  };
@@ -134,6 +129,20 @@ function createTanstackRootRoute(
134
129
  return createRootRoute(options as never) as unknown as MutableTanstackRoute;
135
130
  }
136
131
 
132
+ function wrapRouteComponentWithModernContext(
133
+ route: MutableTanstackRoute,
134
+ component: unknown,
135
+ routeId?: string,
136
+ ) {
137
+ const routeMatchId = routeId || route.id;
138
+ if (component && routeMatchId) {
139
+ route.options.component = withModernRouteMatchContext(
140
+ component,
141
+ routeMatchId,
142
+ ) as typeof route.options.component;
143
+ }
144
+ }
145
+
137
146
  function toTanstackPath(pathname: string): string {
138
147
  // TanStack Router uses `$param` and `$` (splat) style params.
139
148
  // Modern's conventional routing currently generates React Router style params (e.g. `:id`, `*`).
@@ -163,28 +172,6 @@ function toTanstackPath(pathname: string): string {
163
172
  .join('/');
164
173
  }
165
174
 
166
- function isResponse(value: unknown): value is Response {
167
- const record = value as { headers?: unknown; status?: unknown } | null;
168
- return (
169
- record != null &&
170
- typeof record === 'object' &&
171
- typeof record.status === 'number' &&
172
- typeof record.headers === 'object'
173
- );
174
- }
175
-
176
- function isTanstackRedirect(value: unknown): boolean {
177
- return (
178
- isResponse(value) &&
179
- typeof (value as { options?: unknown }).options === 'object'
180
- );
181
- }
182
-
183
- const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
184
- function isRedirectResponse(res: Response) {
185
- return redirectStatusCodes.has(res.status);
186
- }
187
-
188
175
  function isModernDeferredData(
189
176
  value: unknown,
190
177
  ): value is ModernDeferredDataLike & { data: Record<string, unknown> } {
@@ -219,44 +206,74 @@ function normalizeModernLoaderResponse(result: unknown): unknown {
219
206
  return normalizeModernLoaderResult(result);
220
207
  }
221
208
 
222
- function isAbsoluteUrl(value: string) {
223
- try {
224
- void new URL(value);
225
- return true;
226
- } catch {
227
- return false;
209
+ function pickRouteModuleComponent(
210
+ routeModule: unknown,
211
+ seen: Set<unknown> = new Set(),
212
+ ): ElementType<Record<string, unknown>> | undefined {
213
+ if (
214
+ typeof routeModule === 'function' ||
215
+ (routeModule &&
216
+ typeof routeModule === 'object' &&
217
+ '$$typeof' in routeModule)
218
+ ) {
219
+ return routeModule as ElementType<Record<string, unknown>>;
228
220
  }
229
- }
230
221
 
231
- function throwTanstackRedirect(location: string) {
232
- const target = location || '/';
233
- // Prefer `to` for internal/relative redirects so basepath can be applied.
234
- // Use `href` for absolute redirects (external).
235
- if (isAbsoluteUrl(target)) {
236
- throw redirect({ href: target });
222
+ if (!routeModule || typeof routeModule !== 'object') {
223
+ return undefined;
224
+ }
225
+ if (seen.has(routeModule)) {
226
+ return undefined;
237
227
  }
228
+ seen.add(routeModule);
238
229
 
239
- throw redirect({ to: target });
230
+ const module = routeModule as ModernRouteModule;
231
+ for (const candidate of [module.default, module.Component]) {
232
+ const component = pickRouteModuleComponent(candidate, seen);
233
+ if (component) {
234
+ return component;
235
+ }
236
+ }
237
+
238
+ return undefined;
240
239
  }
241
240
 
242
- function mapParamsForModernLoader({
243
- modernRoute,
244
- params,
245
- }: {
246
- modernRoute: NestedRoute | PageRoute;
247
- params: RouteParams;
248
- }) {
249
- // React Router uses `*` for splat params, TanStack Router uses `_splat`.
250
- if (modernRoute.type === 'nested' && modernRoute.path?.includes('*')) {
251
- const { _splat, ...rest } = params as RouteParams & {
252
- _splat?: string;
253
- };
254
- if (typeof _splat !== 'undefined') {
255
- return { ...rest, '*': _splat };
256
- }
257
- return rest;
241
+ function createServerLazyImportComponent(
242
+ lazyImport: () => unknown,
243
+ fallbackComponent?: unknown,
244
+ ): PreloadableComponent | unknown {
245
+ if (typeof document !== 'undefined') {
246
+ return fallbackComponent;
258
247
  }
259
- return params;
248
+
249
+ let resolvedComponent: ElementType<Record<string, unknown>> | undefined;
250
+ let pendingLoad: Promise<unknown> | undefined;
251
+
252
+ const load = async () => {
253
+ if (resolvedComponent) {
254
+ return resolvedComponent;
255
+ }
256
+
257
+ const routeModule = await lazyImport();
258
+ const component = pickRouteModuleComponent(routeModule);
259
+ if (component) {
260
+ resolvedComponent = component;
261
+ }
262
+ return resolvedComponent;
263
+ };
264
+
265
+ const Component: PreloadableComponent = props => {
266
+ if (resolvedComponent) {
267
+ return createElement(resolvedComponent, props);
268
+ }
269
+
270
+ pendingLoad ||= load();
271
+ throw pendingLoad;
272
+ };
273
+ Component.load = load;
274
+ Component.preload = load;
275
+
276
+ return Component;
260
277
  }
261
278
 
262
279
  function createModernRequest(input: string, signal: AbortSignal) {
@@ -316,91 +333,6 @@ function createModernShouldReload(
316
333
  };
317
334
  }
318
335
 
319
- function wrapModernLoader(
320
- modernRoute: NestedRoute | PageRoute,
321
- modernLoader: ModernLoader | undefined,
322
- revalidationState?: RouteRevalidationState,
323
- options: RouteTreeOptions = {},
324
- ) {
325
- const route = modernRoute as ModernGeneratedRoute;
326
- return async (ctx: TanstackLoaderContext) => {
327
- try {
328
- if (revalidationState) {
329
- rememberRouteLocation(revalidationState, ctx);
330
- }
331
-
332
- if (typeof route.lazyImport === 'function') {
333
- try {
334
- await route.lazyImport();
335
- } catch {}
336
- }
337
-
338
- const signal: AbortSignal =
339
- ctx?.abortController?.signal ||
340
- ctx?.signal ||
341
- new AbortController().signal;
342
- const baseRequest: Request | undefined =
343
- ctx?.context?.request instanceof Request
344
- ? ctx.context.request
345
- : undefined;
346
-
347
- const href =
348
- typeof ctx?.location === 'string'
349
- ? ctx.location
350
- : ctx?.location?.publicHref ||
351
- ctx?.location?.href ||
352
- ctx?.location?.url?.href ||
353
- '';
354
-
355
- const request = baseRequest
356
- ? new Request(baseRequest, { signal })
357
- : createModernRequest(href, signal);
358
- const params = mapParamsForModernLoader({
359
- modernRoute,
360
- params: ctx.params || {},
361
- });
362
-
363
- const loadModernData = async () => {
364
- const result = modernLoader
365
- ? await modernLoader({
366
- request,
367
- params,
368
- context: ctx?.context?.requestContext,
369
- })
370
- : null;
371
-
372
- return normalizeModernLoaderResponse(result);
373
- };
374
-
375
- if (options.rscPayloadRouter && isTanstackRscPayloadNavigationEnabled()) {
376
- return loadTanstackRscRouteData({
377
- hasClientLoader:
378
- route.hasClientLoader || typeof route.clientData !== 'undefined',
379
- loadClientData: loadModernData,
380
- request,
381
- routeId: ctx.route?.id,
382
- });
383
- }
384
-
385
- return loadModernData();
386
- } catch (err) {
387
- if (isResponse(err)) {
388
- if (isTanstackRedirect(err)) {
389
- throw err;
390
- }
391
- if (isRedirectResponse(err)) {
392
- const location = err.headers.get('Location') || '/';
393
- throwTanstackRedirect(location);
394
- }
395
- if (err.status === 404) {
396
- throw notFound();
397
- }
398
- }
399
- throw err;
400
- }
401
- };
402
- }
403
-
404
336
  function isRouteObjectPathlessLayout(route: RouteObject) {
405
337
  return !route.path && !route.index;
406
338
  }
@@ -468,9 +400,10 @@ function wrapRouteObjectLoader(
468
400
  ctx?.location?.url?.href ||
469
401
  '';
470
402
 
471
- const request = baseRequest
472
- ? new Request(baseRequest, { signal })
473
- : createModernRequest(href, signal);
403
+ const request =
404
+ baseRequest !== undefined
405
+ ? new Request(baseRequest, { signal })
406
+ : createModernRequest(href, signal);
474
407
 
475
408
  const params = mapParamsForRouteObjectLoader({
476
409
  route,
@@ -519,6 +452,18 @@ function wrapRouteObjectLoader(
519
452
 
520
453
  function toRouteComponent(routeObject: RouteObject): unknown {
521
454
  const route = routeObject as ModernRouteObject;
455
+ const lazyImport =
456
+ typeof route.lazyImport === 'function' ? route.lazyImport : undefined;
457
+ const fallbackComponent = route.Component
458
+ ? route.Component
459
+ : route.element
460
+ ? () => route.element
461
+ : undefined;
462
+
463
+ if (lazyImport && fallbackComponent) {
464
+ return createServerLazyImportComponent(lazyImport, fallbackComponent);
465
+ }
466
+
522
467
  if (route.Component) {
523
468
  return route.Component;
524
469
  }
@@ -627,12 +572,14 @@ function createRouteFromRouteObject(opts: {
627
572
  const stableFallbackId =
628
573
  routeObject.id || modernRouteObject.file || routeObject.path || 'pathless';
629
574
 
575
+ const component = toRouteComponent(routeObject);
630
576
  const base: TanstackRouteOptions = {
631
577
  getParentRoute: () => parent,
632
- component: toRouteComponent(routeObject),
578
+ component,
633
579
  pendingComponent: toPendingComponent(routeObject),
634
580
  errorComponent: toErrorComponent(routeObject),
635
- wrapInSuspense: true,
581
+ validateSearch: modernRouteObject.validateSearch,
582
+ loaderDeps: modernRouteObject.loaderDeps,
636
583
  staticData: createRouteStaticData({
637
584
  modernRouteId: routeObject.id,
638
585
  modernRouteAction: modernRouteObject.action,
@@ -667,6 +614,7 @@ function createRouteFromRouteObject(opts: {
667
614
  }
668
615
 
669
616
  const route = createTanstackRoute(base);
617
+ wrapRouteComponentWithModernContext(route, component, routeObject.id);
670
618
 
671
619
  const children = routeObject.children;
672
620
  if (children && children.length > 0) {
@@ -683,175 +631,6 @@ function createRouteFromRouteObject(opts: {
683
631
  return route;
684
632
  }
685
633
 
686
- function createRouteFromModernRoute(opts: {
687
- options?: RouteTreeOptions;
688
- parent: AnyRoute;
689
- modernRoute: NestedRoute | PageRoute;
690
- }): AnyRoute {
691
- const { options = {}, parent, modernRoute } = opts;
692
- const route = modernRoute as ModernGeneratedRoute;
693
- const revalidationState: RouteRevalidationState = {};
694
-
695
- const modernId = route.id;
696
- const stableFallbackId =
697
- modernId ||
698
- route._component ||
699
- route.filename ||
700
- route.data ||
701
- (typeof route.loader === 'function' ? route.id : undefined);
702
-
703
- const pendingComponent = route.loading || route.pendingComponent;
704
- const errorComponent = route.error || route.errorComponent;
705
- const component = route.component;
706
- const modernLoader = route.loader;
707
- const modernAction = route.action;
708
- const modernShouldRevalidate = route.shouldRevalidate;
709
- const shouldReload = createModernShouldReload(
710
- modernShouldRevalidate,
711
- revalidationState,
712
- );
713
-
714
- // Pathless layout: no path segment, but must remain in the tree.
715
- const isPathlessLayout =
716
- route.type === 'nested' &&
717
- typeof route.index !== 'boolean' &&
718
- typeof route.path === 'undefined';
719
-
720
- const isIndexRoute = route.type === 'nested' && Boolean(route.index);
721
-
722
- const base: TanstackRouteOptions = {
723
- getParentRoute: () => parent,
724
- component: component || undefined,
725
- pendingComponent: pendingComponent || undefined,
726
- errorComponent: errorComponent || undefined,
727
- wrapInSuspense: true,
728
- staticData: createRouteStaticData({
729
- modernRouteId: modernId,
730
- modernRouteAction: modernAction,
731
- modernRouteHandle: mergeModernRouteHandle(route),
732
- modernRouteHasAction: route.hasAction || Boolean(modernAction),
733
- modernRouteHasClientLoader:
734
- route.hasClientLoader || typeof route.clientData !== 'undefined',
735
- modernRouteHasLoader:
736
- route.hasLoader || typeof modernLoader === 'function',
737
- modernRouteIsClientComponent: route.isClientComponent,
738
- modernRouteLoader: modernLoader,
739
- modernRouteShouldRevalidate: modernShouldRevalidate,
740
- }),
741
- loader: wrapModernLoader(
742
- modernRoute,
743
- modernLoader,
744
- revalidationState,
745
- options,
746
- ),
747
- };
748
- if (route.inValidSSRRoute) {
749
- base.ssr = false;
750
- }
751
- if (shouldReload) {
752
- base.shouldReload = shouldReload;
753
- }
754
-
755
- if (isPathlessLayout) {
756
- // Use a stable custom id for pathless layouts to avoid hydration mismatch.
757
- base.id = stableFallbackId || 'pathless';
758
- } else {
759
- const rawPath = route.path;
760
- base.path = isIndexRoute ? '/' : toTanstackPath(rawPath || '');
761
- }
762
-
763
- const tanstackRoute = createTanstackRoute(base);
764
-
765
- const children = route.children as Array<NestedRoute | PageRoute> | undefined;
766
- if (children && children.length > 0) {
767
- const childRoutes = children.map(child =>
768
- createRouteFromModernRoute({
769
- options,
770
- parent: tanstackRoute,
771
- modernRoute: child,
772
- }),
773
- );
774
- tanstackRoute.addChildren(childRoutes);
775
- }
776
-
777
- return tanstackRoute;
778
- }
779
-
780
- export function createRouteTreeFromModernRoutes(
781
- routes: Array<NestedRoute | PageRoute>,
782
- options: RouteTreeOptions = {},
783
- ): ModernTanstackRootRoute {
784
- const rootModern = routes.find(
785
- r =>
786
- r &&
787
- (r as ModernGeneratedRoute).type === 'nested' &&
788
- (r as ModernGeneratedRoute).isRoot,
789
- ) as ModernGeneratedRoute | undefined;
790
-
791
- const rootComponent = rootModern?.component;
792
- const pendingComponent = rootModern?.loading;
793
- const errorComponent = rootModern?.error;
794
- const rootLoader = rootModern?.loader;
795
- const rootAction = rootModern?.action;
796
- const rootModernId = rootModern?.id;
797
- const rootShouldRevalidate = rootModern?.shouldRevalidate;
798
- const rootRevalidationState: RouteRevalidationState = {};
799
- const rootShouldReload = createModernShouldReload(
800
- rootShouldRevalidate,
801
- rootRevalidationState,
802
- );
803
-
804
- const rootRouteOptions: TanstackRootRouteOptions = {
805
- component: rootComponent || undefined,
806
- pendingComponent: pendingComponent || undefined,
807
- errorComponent: errorComponent || undefined,
808
- wrapInSuspense: true,
809
- notFoundComponent: DefaultNotFound,
810
- staticData: createRouteStaticData({
811
- modernRouteId: rootModernId,
812
- modernRouteAction: rootAction,
813
- modernRouteHandle: rootModern
814
- ? mergeModernRouteHandle(rootModern)
815
- : undefined,
816
- modernRouteHasAction: rootModern?.hasAction || Boolean(rootAction),
817
- modernRouteHasClientLoader:
818
- rootModern?.hasClientLoader ||
819
- typeof rootModern?.clientData !== 'undefined',
820
- modernRouteHasLoader:
821
- rootModern?.hasLoader || typeof rootLoader === 'function',
822
- modernRouteIsClientComponent: rootModern?.isClientComponent,
823
- modernRouteLoader: rootLoader,
824
- modernRouteShouldRevalidate: rootShouldRevalidate,
825
- }),
826
- loader: rootModern
827
- ? wrapModernLoader(rootModern, rootLoader, rootRevalidationState, options)
828
- : undefined,
829
- };
830
- if (rootShouldReload) {
831
- rootRouteOptions.shouldReload = rootShouldReload;
832
- }
833
- if (rootModern?.inValidSSRRoute) {
834
- rootRouteOptions.ssr = false;
835
- }
836
-
837
- const rootRoute = createTanstackRootRoute(rootRouteOptions);
838
-
839
- const topLevel = rootModern
840
- ? (rootModern.children as Array<NestedRoute | PageRoute>) || []
841
- : routes;
842
-
843
- const childRoutes = topLevel.map(child =>
844
- createRouteFromModernRoute({
845
- options,
846
- parent: rootRoute,
847
- modernRoute: child,
848
- }),
849
- );
850
-
851
- rootRoute.addChildren(childRoutes);
852
- return rootRoute as unknown as ModernTanstackRootRoute;
853
- }
854
-
855
634
  function getRootLikeRouteObject(routes: RouteObject[]) {
856
635
  return routes.find(route => route.path === '/' && !route.index);
857
636
  }
@@ -870,13 +649,17 @@ export function createRouteTreeFromRouteObjects(
870
649
  rootRevalidationState,
871
650
  );
872
651
 
652
+ const rootComponent = rootLikeRoute
653
+ ? toRouteComponent(rootLikeRoute)
654
+ : undefined;
873
655
  const rootRouteOptions: TanstackRootRouteOptions = {
874
- component: rootLikeRoute ? toRouteComponent(rootLikeRoute) : undefined,
656
+ component: rootComponent,
875
657
  pendingComponent: rootLikeRoute
876
658
  ? toPendingComponent(rootLikeRoute)
877
659
  : undefined,
878
660
  errorComponent: rootLikeRoute ? toErrorComponent(rootLikeRoute) : undefined,
879
- wrapInSuspense: true,
661
+ validateSearch: rootLikeRoute?.validateSearch,
662
+ loaderDeps: rootLikeRoute?.loaderDeps,
880
663
  notFoundComponent: DefaultNotFound,
881
664
  staticData: createRouteStaticData({
882
665
  modernRouteId: rootLikeRoute?.id,
@@ -907,6 +690,12 @@ export function createRouteTreeFromRouteObjects(
907
690
  }
908
691
 
909
692
  const rootRoute = createTanstackRootRoute(rootRouteOptions);
693
+ if (rootComponent) {
694
+ rootRoute.options.component = withModernRouteMatchContext(
695
+ rootComponent,
696
+ rootRouteId,
697
+ ) as typeof rootRoute.options.component;
698
+ }
910
699
 
911
700
  const topLevel = rootLikeRoute
912
701
  ? [
@@ -925,18 +714,38 @@ export function createRouteTreeFromRouteObjects(
925
714
 
926
715
  export function getModernRouteIdsFromMatches(router: AnyRouter): string[] {
927
716
  const matches = router.state.matches || [];
717
+ const routesById = (
718
+ router as AnyRouter & {
719
+ routesById?: Record<
720
+ string,
721
+ {
722
+ options?: {
723
+ staticData?: { modernRouteId?: unknown };
724
+ };
725
+ }
726
+ >;
727
+ }
728
+ ).routesById;
928
729
  const ids = matches
929
730
  .map(match => {
930
- const route = (
931
- match as {
932
- route?: {
933
- options?: {
934
- staticData?: { modernRouteId?: unknown };
935
- };
731
+ const normalizedMatch = match as {
732
+ route?: {
733
+ options?: {
734
+ staticData?: { modernRouteId?: unknown };
936
735
  };
937
- }
938
- ).route;
939
- return route?.options?.staticData?.modernRouteId;
736
+ };
737
+ routeId?: unknown;
738
+ };
739
+ const routeId =
740
+ typeof normalizedMatch.routeId === 'string'
741
+ ? normalizedMatch.routeId
742
+ : undefined;
743
+ return (
744
+ normalizedMatch.route?.options?.staticData?.modernRouteId ??
745
+ (routeId
746
+ ? routesById?.[routeId]?.options?.staticData?.modernRouteId
747
+ : undefined)
748
+ );
940
749
  })
941
750
  .filter((id): id is string => typeof id === 'string');
942
751
  return Array.from(new Set(ids));
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Entry-injected router module for entrypoints that are NOT TanStack
3
+ * file-route entrypoints (e.g. apps configuring
4
+ * `router: { framework: 'tanstack', createRoutes }` in modern.runtime.ts).
5
+ *
6
+ * The CLI plugin injects `{ name: 'router', path: '<pkg>/runtime/router' }`
7
+ * for those entries, so the generated entry code value-imports `routerPlugin`
8
+ * from here. That single import both installs the framework-resolving router
9
+ * plugin of @modern-js/runtime AND registers the TanStack router provider
10
+ * (the './register' side effect) — the registration can therefore never be
11
+ * tree-shaken away from the entry that needs it.
12
+ */
13
+ import './register';
14
+
15
+ export { routerPlugin } from '@modern-js/runtime/router/internal';