@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.11 → 3.2.0-ultramodern.110

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 (86) hide show
  1. package/dist/cjs/cli/index.js +21 -5
  2. package/dist/cjs/cli/routeSplitting.js +87 -0
  3. package/dist/cjs/cli/tanstackTypes.js +155 -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 +4 -1
  34. package/dist/esm/cli/routeSplitting.mjs +43 -0
  35. package/dist/esm/cli/tanstackTypes.mjs +146 -58
  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 +4 -1
  46. package/dist/esm-node/cli/routeSplitting.mjs +44 -0
  47. package/dist/esm-node/cli/tanstackTypes.mjs +146 -58
  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 +4 -0
  58. package/dist/types/cli/routeSplitting.d.ts +29 -0
  59. package/dist/types/runtime/hooks.d.ts +9 -24
  60. package/dist/types/runtime/hydrationBoundary.d.ts +2 -0
  61. package/dist/types/runtime/index.d.ts +5 -2
  62. package/dist/types/runtime/outlet.d.ts +2 -0
  63. package/dist/types/runtime/plugin.d.ts +1 -1
  64. package/dist/types/runtime/plugin.node.d.ts +1 -1
  65. package/dist/types/runtime/plugin.worker.d.ts +1 -0
  66. package/dist/types/runtime/types.d.ts +7 -0
  67. package/package.json +19 -19
  68. package/src/cli/index.ts +17 -0
  69. package/src/cli/routeSplitting.ts +81 -0
  70. package/src/cli/tanstackTypes.ts +216 -67
  71. package/src/runtime/hydrationBoundary.tsx +12 -0
  72. package/src/runtime/index.tsx +107 -2
  73. package/src/runtime/outlet.tsx +48 -0
  74. package/src/runtime/plugin.node.tsx +58 -8
  75. package/src/runtime/plugin.tsx +372 -157
  76. package/src/runtime/plugin.worker.tsx +4 -0
  77. package/src/runtime/prefetchLink.tsx +1 -1
  78. package/src/runtime/routeTree.ts +194 -23
  79. package/src/runtime/ssr-shim.d.ts +1 -3
  80. package/src/runtime/types.ts +13 -0
  81. package/tests/router/cli.test.ts +239 -0
  82. package/tests/router/fastDefaults.test.ts +25 -0
  83. package/tests/router/hydrationBoundary.test.tsx +23 -0
  84. package/tests/router/prefetchLink.test.tsx +43 -7
  85. package/tests/router/routeTree.test.ts +416 -1
  86. package/tests/router/tanstackTypes.test.ts +184 -0
@@ -11,8 +11,11 @@ import {
11
11
  createRoute,
12
12
  notFound,
13
13
  redirect,
14
+ rootRouteId,
14
15
  } from '@tanstack/react-router';
16
+ import { createElement, type ElementType } from 'react';
15
17
  import { DefaultNotFound } from './DefaultNotFound';
18
+ import { withModernRouteMatchContext } from './outlet';
16
19
  import {
17
20
  isTanstackRscPayloadNavigationEnabled,
18
21
  loadTanstackRscRouteData,
@@ -75,8 +78,10 @@ type ModernRouteObject = RouteObject & {
75
78
  isClientComponent?: boolean;
76
79
  lazyImport?: () => unknown;
77
80
  loader?: ModernLoader;
81
+ loaderDeps?: unknown;
78
82
  pendingComponent?: unknown;
79
83
  shouldRevalidate?: ModernShouldRevalidate;
84
+ validateSearch?: unknown;
80
85
  };
81
86
 
82
87
  type ModernGeneratedRoute = (NestedRoute | PageRoute) & {
@@ -101,14 +106,20 @@ type ModernGeneratedRoute = (NestedRoute | PageRoute) & {
101
106
  isRoot?: boolean;
102
107
  lazyImport?: () => unknown;
103
108
  loader?: ModernLoader;
109
+ loaderDeps?: unknown;
104
110
  loading?: unknown;
105
111
  pendingComponent?: unknown;
106
112
  path?: string;
107
113
  shouldRevalidate?: ModernShouldRevalidate;
114
+ validateSearch?: unknown;
108
115
  };
109
116
 
110
117
  type MutableTanstackRoute = AnyRoute & {
111
118
  addChildren: (children: AnyRoute[]) => void;
119
+ id?: string;
120
+ options: {
121
+ component?: unknown;
122
+ };
112
123
  };
113
124
 
114
125
  type TanstackRouteOptions = Record<string, unknown>;
@@ -118,6 +129,15 @@ type ModernDeferredDataLike = {
118
129
  __modern_deferred?: unknown;
119
130
  data?: unknown;
120
131
  };
132
+ type ModernRouteModule = {
133
+ Component?: unknown;
134
+ default?: unknown;
135
+ };
136
+ type PreloadableComponent = {
137
+ (props: Record<string, unknown>): ReturnType<typeof createElement>;
138
+ load?: () => Promise<unknown>;
139
+ preload?: () => Promise<unknown>;
140
+ };
121
141
  type RouteTreeOptions = {
122
142
  rscPayloadRouter?: boolean;
123
143
  };
@@ -134,6 +154,20 @@ function createTanstackRootRoute(
134
154
  return createRootRoute(options as never) as unknown as MutableTanstackRoute;
135
155
  }
136
156
 
157
+ function wrapRouteComponentWithModernContext(
158
+ route: MutableTanstackRoute,
159
+ component: unknown,
160
+ routeId?: string,
161
+ ) {
162
+ const routeMatchId = routeId || route.id;
163
+ if (component && routeMatchId) {
164
+ route.options.component = withModernRouteMatchContext(
165
+ component,
166
+ routeMatchId,
167
+ ) as typeof route.options.component;
168
+ }
169
+ }
170
+
137
171
  function toTanstackPath(pathname: string): string {
138
172
  // TanStack Router uses `$param` and `$` (splat) style params.
139
173
  // Modern's conventional routing currently generates React Router style params (e.g. `:id`, `*`).
@@ -219,6 +253,76 @@ function normalizeModernLoaderResponse(result: unknown): unknown {
219
253
  return normalizeModernLoaderResult(result);
220
254
  }
221
255
 
256
+ function pickRouteModuleComponent(
257
+ routeModule: unknown,
258
+ seen: Set<unknown> = new Set(),
259
+ ): ElementType<Record<string, unknown>> | undefined {
260
+ if (
261
+ typeof routeModule === 'function' ||
262
+ (routeModule &&
263
+ typeof routeModule === 'object' &&
264
+ '$$typeof' in routeModule)
265
+ ) {
266
+ return routeModule as ElementType<Record<string, unknown>>;
267
+ }
268
+
269
+ if (!routeModule || typeof routeModule !== 'object') {
270
+ return undefined;
271
+ }
272
+ if (seen.has(routeModule)) {
273
+ return undefined;
274
+ }
275
+ seen.add(routeModule);
276
+
277
+ const module = routeModule as ModernRouteModule;
278
+ for (const candidate of [module.default, module.Component]) {
279
+ const component = pickRouteModuleComponent(candidate, seen);
280
+ if (component) {
281
+ return component;
282
+ }
283
+ }
284
+
285
+ return undefined;
286
+ }
287
+
288
+ function createServerLazyImportComponent(
289
+ lazyImport: () => unknown,
290
+ fallbackComponent?: unknown,
291
+ ): PreloadableComponent | unknown {
292
+ if (typeof document !== 'undefined') {
293
+ return fallbackComponent;
294
+ }
295
+
296
+ let resolvedComponent: ElementType<Record<string, unknown>> | undefined;
297
+ let pendingLoad: Promise<unknown> | undefined;
298
+
299
+ const load = async () => {
300
+ if (resolvedComponent) {
301
+ return resolvedComponent;
302
+ }
303
+
304
+ const routeModule = await lazyImport();
305
+ const component = pickRouteModuleComponent(routeModule);
306
+ if (component) {
307
+ resolvedComponent = component;
308
+ }
309
+ return resolvedComponent;
310
+ };
311
+
312
+ const Component: PreloadableComponent = props => {
313
+ if (resolvedComponent) {
314
+ return createElement(resolvedComponent, props);
315
+ }
316
+
317
+ pendingLoad ||= load();
318
+ throw pendingLoad;
319
+ };
320
+ Component.load = load;
321
+ Component.preload = load;
322
+
323
+ return Component;
324
+ }
325
+
222
326
  function isAbsoluteUrl(value: string) {
223
327
  try {
224
328
  void new URL(value);
@@ -352,9 +456,10 @@ function wrapModernLoader(
352
456
  ctx?.location?.url?.href ||
353
457
  '';
354
458
 
355
- const request = baseRequest
356
- ? new Request(baseRequest, { signal })
357
- : createModernRequest(href, signal);
459
+ const request =
460
+ baseRequest !== undefined
461
+ ? new Request(baseRequest, { signal })
462
+ : createModernRequest(href, signal);
358
463
  const params = mapParamsForModernLoader({
359
464
  modernRoute,
360
465
  params: ctx.params || {},
@@ -468,9 +573,10 @@ function wrapRouteObjectLoader(
468
573
  ctx?.location?.url?.href ||
469
574
  '';
470
575
 
471
- const request = baseRequest
472
- ? new Request(baseRequest, { signal })
473
- : createModernRequest(href, signal);
576
+ const request =
577
+ baseRequest !== undefined
578
+ ? new Request(baseRequest, { signal })
579
+ : createModernRequest(href, signal);
474
580
 
475
581
  const params = mapParamsForRouteObjectLoader({
476
582
  route,
@@ -519,6 +625,18 @@ function wrapRouteObjectLoader(
519
625
 
520
626
  function toRouteComponent(routeObject: RouteObject): unknown {
521
627
  const route = routeObject as ModernRouteObject;
628
+ const lazyImport =
629
+ typeof route.lazyImport === 'function' ? route.lazyImport : undefined;
630
+ const fallbackComponent = route.Component
631
+ ? route.Component
632
+ : route.element
633
+ ? () => route.element
634
+ : undefined;
635
+
636
+ if (lazyImport && fallbackComponent) {
637
+ return createServerLazyImportComponent(lazyImport, fallbackComponent);
638
+ }
639
+
522
640
  if (route.Component) {
523
641
  return route.Component;
524
642
  }
@@ -529,6 +647,15 @@ function toRouteComponent(routeObject: RouteObject): unknown {
529
647
  return undefined;
530
648
  }
531
649
 
650
+ function toModernRouteComponent(route: ModernGeneratedRoute): unknown {
651
+ const component = route.component || undefined;
652
+ if (typeof route.lazyImport === 'function' && component) {
653
+ return createServerLazyImportComponent(route.lazyImport, component);
654
+ }
655
+
656
+ return component;
657
+ }
658
+
532
659
  function toErrorComponent(routeObject: RouteObject): unknown {
533
660
  const route = routeObject as ModernRouteObject;
534
661
  if (route.ErrorBoundary) {
@@ -627,12 +754,14 @@ function createRouteFromRouteObject(opts: {
627
754
  const stableFallbackId =
628
755
  routeObject.id || modernRouteObject.file || routeObject.path || 'pathless';
629
756
 
757
+ const component = toRouteComponent(routeObject);
630
758
  const base: TanstackRouteOptions = {
631
759
  getParentRoute: () => parent,
632
- component: toRouteComponent(routeObject),
760
+ component,
633
761
  pendingComponent: toPendingComponent(routeObject),
634
762
  errorComponent: toErrorComponent(routeObject),
635
- wrapInSuspense: true,
763
+ validateSearch: modernRouteObject.validateSearch,
764
+ loaderDeps: modernRouteObject.loaderDeps,
636
765
  staticData: createRouteStaticData({
637
766
  modernRouteId: routeObject.id,
638
767
  modernRouteAction: modernRouteObject.action,
@@ -667,6 +796,7 @@ function createRouteFromRouteObject(opts: {
667
796
  }
668
797
 
669
798
  const route = createTanstackRoute(base);
799
+ wrapRouteComponentWithModernContext(route, component, routeObject.id);
670
800
 
671
801
  const children = routeObject.children;
672
802
  if (children && children.length > 0) {
@@ -702,7 +832,7 @@ function createRouteFromModernRoute(opts: {
702
832
 
703
833
  const pendingComponent = route.loading || route.pendingComponent;
704
834
  const errorComponent = route.error || route.errorComponent;
705
- const component = route.component;
835
+ const component = toModernRouteComponent(route);
706
836
  const modernLoader = route.loader;
707
837
  const modernAction = route.action;
708
838
  const modernShouldRevalidate = route.shouldRevalidate;
@@ -724,7 +854,8 @@ function createRouteFromModernRoute(opts: {
724
854
  component: component || undefined,
725
855
  pendingComponent: pendingComponent || undefined,
726
856
  errorComponent: errorComponent || undefined,
727
- wrapInSuspense: true,
857
+ validateSearch: route.validateSearch,
858
+ loaderDeps: route.loaderDeps,
728
859
  staticData: createRouteStaticData({
729
860
  modernRouteId: modernId,
730
861
  modernRouteAction: modernAction,
@@ -761,6 +892,7 @@ function createRouteFromModernRoute(opts: {
761
892
  }
762
893
 
763
894
  const tanstackRoute = createTanstackRoute(base);
895
+ wrapRouteComponentWithModernContext(tanstackRoute, component, modernId);
764
896
 
765
897
  const children = route.children as Array<NestedRoute | PageRoute> | undefined;
766
898
  if (children && children.length > 0) {
@@ -788,7 +920,9 @@ export function createRouteTreeFromModernRoutes(
788
920
  (r as ModernGeneratedRoute).isRoot,
789
921
  ) as ModernGeneratedRoute | undefined;
790
922
 
791
- const rootComponent = rootModern?.component;
923
+ const rootComponent = rootModern
924
+ ? toModernRouteComponent(rootModern)
925
+ : undefined;
792
926
  const pendingComponent = rootModern?.loading;
793
927
  const errorComponent = rootModern?.error;
794
928
  const rootLoader = rootModern?.loader;
@@ -805,7 +939,8 @@ export function createRouteTreeFromModernRoutes(
805
939
  component: rootComponent || undefined,
806
940
  pendingComponent: pendingComponent || undefined,
807
941
  errorComponent: errorComponent || undefined,
808
- wrapInSuspense: true,
942
+ validateSearch: rootModern?.validateSearch,
943
+ loaderDeps: rootModern?.loaderDeps,
809
944
  notFoundComponent: DefaultNotFound,
810
945
  staticData: createRouteStaticData({
811
946
  modernRouteId: rootModernId,
@@ -835,6 +970,12 @@ export function createRouteTreeFromModernRoutes(
835
970
  }
836
971
 
837
972
  const rootRoute = createTanstackRootRoute(rootRouteOptions);
973
+ if (rootComponent) {
974
+ rootRoute.options.component = withModernRouteMatchContext(
975
+ rootComponent,
976
+ rootRouteId,
977
+ ) as typeof rootRoute.options.component;
978
+ }
838
979
 
839
980
  const topLevel = rootModern
840
981
  ? (rootModern.children as Array<NestedRoute | PageRoute>) || []
@@ -870,13 +1011,17 @@ export function createRouteTreeFromRouteObjects(
870
1011
  rootRevalidationState,
871
1012
  );
872
1013
 
1014
+ const rootComponent = rootLikeRoute
1015
+ ? toRouteComponent(rootLikeRoute)
1016
+ : undefined;
873
1017
  const rootRouteOptions: TanstackRootRouteOptions = {
874
- component: rootLikeRoute ? toRouteComponent(rootLikeRoute) : undefined,
1018
+ component: rootComponent,
875
1019
  pendingComponent: rootLikeRoute
876
1020
  ? toPendingComponent(rootLikeRoute)
877
1021
  : undefined,
878
1022
  errorComponent: rootLikeRoute ? toErrorComponent(rootLikeRoute) : undefined,
879
- wrapInSuspense: true,
1023
+ validateSearch: rootLikeRoute?.validateSearch,
1024
+ loaderDeps: rootLikeRoute?.loaderDeps,
880
1025
  notFoundComponent: DefaultNotFound,
881
1026
  staticData: createRouteStaticData({
882
1027
  modernRouteId: rootLikeRoute?.id,
@@ -907,6 +1052,12 @@ export function createRouteTreeFromRouteObjects(
907
1052
  }
908
1053
 
909
1054
  const rootRoute = createTanstackRootRoute(rootRouteOptions);
1055
+ if (rootComponent) {
1056
+ rootRoute.options.component = withModernRouteMatchContext(
1057
+ rootComponent,
1058
+ rootRouteId,
1059
+ ) as typeof rootRoute.options.component;
1060
+ }
910
1061
 
911
1062
  const topLevel = rootLikeRoute
912
1063
  ? [
@@ -925,18 +1076,38 @@ export function createRouteTreeFromRouteObjects(
925
1076
 
926
1077
  export function getModernRouteIdsFromMatches(router: AnyRouter): string[] {
927
1078
  const matches = router.state.matches || [];
1079
+ const routesById = (
1080
+ router as AnyRouter & {
1081
+ routesById?: Record<
1082
+ string,
1083
+ {
1084
+ options?: {
1085
+ staticData?: { modernRouteId?: unknown };
1086
+ };
1087
+ }
1088
+ >;
1089
+ }
1090
+ ).routesById;
928
1091
  const ids = matches
929
1092
  .map(match => {
930
- const route = (
931
- match as {
932
- route?: {
933
- options?: {
934
- staticData?: { modernRouteId?: unknown };
935
- };
1093
+ const normalizedMatch = match as {
1094
+ route?: {
1095
+ options?: {
1096
+ staticData?: { modernRouteId?: unknown };
936
1097
  };
937
- }
938
- ).route;
939
- return route?.options?.staticData?.modernRouteId;
1098
+ };
1099
+ routeId?: unknown;
1100
+ };
1101
+ const routeId =
1102
+ typeof normalizedMatch.routeId === 'string'
1103
+ ? normalizedMatch.routeId
1104
+ : undefined;
1105
+ return (
1106
+ normalizedMatch.route?.options?.staticData?.modernRouteId ??
1107
+ (routeId
1108
+ ? routesById?.[routeId]?.options?.staticData?.modernRouteId
1109
+ : undefined)
1110
+ );
940
1111
  })
941
1112
  .filter((id): id is string => typeof id === 'string');
942
1113
  return Array.from(new Set(ids));
@@ -1,7 +1,5 @@
1
1
  declare module '@tanstack/react-router/ssr/client' {
2
- export function RouterClient(props: {
3
- router: unknown;
4
- }): import('react').JSX.Element;
2
+ export function hydrate(router: unknown): Promise<unknown>;
5
3
  }
6
4
 
7
5
  declare module '@tanstack/react-router/ssr/server' {
@@ -20,9 +20,22 @@ export type RouterConfig = {
20
20
  future?: Partial<{
21
21
  v7_startTransition: boolean;
22
22
  }>;
23
+ defaultStructuralSharing?: boolean;
23
24
  unstable_reloadOnURLMismatch?: boolean;
24
25
  };
25
26
 
27
+ export const modernTanstackRouterFastDefaults = {
28
+ defaultStructuralSharing: true,
29
+ } as const;
30
+
31
+ export const getModernTanstackRouterFastDefaults = (
32
+ config: Partial<Pick<RouterConfig, 'defaultStructuralSharing'>> = {},
33
+ ) => ({
34
+ defaultStructuralSharing:
35
+ config.defaultStructuralSharing ??
36
+ modernTanstackRouterFastDefaults.defaultStructuralSharing,
37
+ });
38
+
26
39
  export interface RouterRouteMatchSnapshot {
27
40
  routeId: string;
28
41
  assetRouteId?: string;
@@ -1,9 +1,12 @@
1
1
  import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
2
2
  import { tmpdir } from 'node:os';
3
3
  import path from 'node:path';
4
+ import { mergeConfig } from '@modern-js/plugin/cli';
4
5
  import type { Entrypoint } from '@modern-js/types';
5
6
  import { fs, NESTED_ROUTE_SPEC_FILE } from '@modern-js/utils';
6
7
  import {
8
+ createTanstackRsbuildRouteSplittingProfile,
9
+ isTanstackStartRouteModuleSource,
7
10
  tanstackRouterPlugin,
8
11
  writeTanstackRegisterFile,
9
12
  writeTanstackRouterTypesForEntries,
@@ -193,6 +196,12 @@ describe('tanstack router cli plugin', () => {
193
196
  },
194
197
  ]);
195
198
 
199
+ expect(taps.config()).toMatchObject({
200
+ output: {
201
+ splitRouteChunks: true,
202
+ },
203
+ });
204
+
196
205
  const specPath = path.join(distDirectory, NESTED_ROUTE_SPEC_FILE);
197
206
  await fs.outputJSON(specPath, {
198
207
  existing: [{ id: 'keep-me' }],
@@ -383,4 +392,234 @@ describe('tanstack router cli plugin', () => {
383
392
  }),
384
393
  );
385
394
  });
395
+
396
+ test('can opt out of Modern-owned route code splitting', async () => {
397
+ const taps: Record<string, any> = {};
398
+ const api = {
399
+ getAppContext: () => ({
400
+ srcDirectory: '/tmp/app/src',
401
+ serverRoutes: [],
402
+ }),
403
+ _internalRuntimePlugins: () => {},
404
+ checkEntryPoint: () => {},
405
+ config: (tap: any) => {
406
+ taps.config = tap;
407
+ },
408
+ modifyEntrypoints: () => {},
409
+ generateEntryCode: () => {},
410
+ onFileChanged: () => {},
411
+ modifyFileSystemRoutes: () => {},
412
+ onBeforeGenerateRoutes: () => {},
413
+ };
414
+
415
+ tanstackRouterPlugin({ routeCodeSplitting: false }).setup!(api as any);
416
+
417
+ expect(taps.config()).toMatchObject({
418
+ output: {
419
+ splitRouteChunks: false,
420
+ },
421
+ });
422
+ });
423
+
424
+ test('documents why TanStack Start Rspack splitter is not registered for Modern routes', () => {
425
+ const profile = createTanstackRsbuildRouteSplittingProfile({});
426
+
427
+ expect(profile).toMatchObject({
428
+ defaultConfig: {
429
+ output: {
430
+ splitRouteChunks: true,
431
+ },
432
+ },
433
+ modernRouteChunks: {
434
+ enabled: true,
435
+ owner: 'modern',
436
+ },
437
+ builderChunkSplit: {
438
+ owner: 'modern-rsbuild',
439
+ preserved: true,
440
+ },
441
+ tanstackStartRspackSplitter: {
442
+ compatible: false,
443
+ clientDeleteNodes: ['ssr', 'server', 'headers'],
444
+ },
445
+ });
446
+ expect(
447
+ isTanstackStartRouteModuleSource(
448
+ "export const Route = createFileRoute('/dashboard')({ component })",
449
+ ),
450
+ ).toBe(true);
451
+ expect(
452
+ isTanstackStartRouteModuleSource(
453
+ 'export const route = createRoute({ getParentRoute, path })',
454
+ ),
455
+ ).toBe(false);
456
+ });
457
+
458
+ test('preserves user-selected route and builder chunk splitting modes', () => {
459
+ const pluginDefaults = createTanstackRsbuildRouteSplittingProfile(
460
+ {},
461
+ ).defaultConfig;
462
+ const chunkSplits = [
463
+ { strategy: 'split-by-module' },
464
+ { strategy: 'split-by-experience' },
465
+ { strategy: 'all-in-one' },
466
+ { strategy: 'single-vendor' },
467
+ { strategy: 'split-by-size', minSize: 10_000, maxSize: 60_000 },
468
+ {
469
+ strategy: 'custom',
470
+ splitChunks: {
471
+ chunks: 'all',
472
+ cacheGroups: {
473
+ tractors: {
474
+ name: 'tractors',
475
+ test: /tractors/u,
476
+ },
477
+ },
478
+ },
479
+ },
480
+ ];
481
+
482
+ for (const chunkSplit of chunkSplits) {
483
+ expect(
484
+ mergeConfig([
485
+ pluginDefaults,
486
+ {
487
+ output: {
488
+ splitRouteChunks: false,
489
+ },
490
+ performance: {
491
+ chunkSplit,
492
+ },
493
+ splitChunks: false,
494
+ },
495
+ ]),
496
+ ).toMatchObject({
497
+ output: {
498
+ splitRouteChunks: false,
499
+ },
500
+ performance: {
501
+ chunkSplit,
502
+ },
503
+ splitChunks: false,
504
+ });
505
+ }
506
+
507
+ const pageSplitWithManualAsyncChunks = mergeConfig([
508
+ pluginDefaults,
509
+ {
510
+ performance: {
511
+ chunkSplit: {
512
+ strategy: 'custom',
513
+ splitChunks: {
514
+ chunks: 'async',
515
+ },
516
+ },
517
+ },
518
+ },
519
+ ]);
520
+
521
+ expect(pageSplitWithManualAsyncChunks).toMatchObject({
522
+ output: {
523
+ splitRouteChunks: true,
524
+ },
525
+ performance: {
526
+ chunkSplit: {
527
+ strategy: 'custom',
528
+ splitChunks: {
529
+ chunks: 'async',
530
+ },
531
+ },
532
+ },
533
+ });
534
+ });
535
+
536
+ test('keeps custom cache group details intact', () => {
537
+ const pluginDefaults = createTanstackRsbuildRouteSplittingProfile(
538
+ {},
539
+ ).defaultConfig;
540
+
541
+ const mergedConfig = mergeConfig([
542
+ pluginDefaults,
543
+ {
544
+ performance: {
545
+ chunkSplit: {
546
+ strategy: 'custom',
547
+ splitChunks: {
548
+ chunks: 'all',
549
+ cacheGroups: {
550
+ tractors: {
551
+ name: 'tractors',
552
+ test: /tractors/u,
553
+ },
554
+ },
555
+ },
556
+ },
557
+ },
558
+ },
559
+ ]);
560
+
561
+ expect(
562
+ (
563
+ mergedConfig as {
564
+ performance?: {
565
+ chunkSplit?: {
566
+ splitChunks?: {
567
+ cacheGroups?: {
568
+ tractors?: {
569
+ test?: RegExp;
570
+ };
571
+ };
572
+ };
573
+ };
574
+ };
575
+ }
576
+ ).performance?.chunkSplit?.splitChunks?.cacheGroups?.tractors?.test,
577
+ ).toEqual(/tractors/u);
578
+ expect(mergedConfig).toMatchObject({
579
+ output: {
580
+ splitRouteChunks: true,
581
+ },
582
+ performance: {
583
+ chunkSplit: {
584
+ strategy: 'custom',
585
+ splitChunks: {
586
+ chunks: 'all',
587
+ cacheGroups: {
588
+ tractors: {
589
+ name: 'tractors',
590
+ },
591
+ },
592
+ },
593
+ },
594
+ },
595
+ });
596
+ });
597
+
598
+ test('plugin opt-out can still combine with manual builder chunking', () => {
599
+ const pluginDefaults = createTanstackRsbuildRouteSplittingProfile({
600
+ routeCodeSplitting: false,
601
+ }).defaultConfig;
602
+
603
+ expect(
604
+ mergeConfig([
605
+ pluginDefaults,
606
+ {
607
+ performance: {
608
+ chunkSplit: {
609
+ strategy: 'single-vendor',
610
+ },
611
+ },
612
+ },
613
+ ]),
614
+ ).toMatchObject({
615
+ output: {
616
+ splitRouteChunks: false,
617
+ },
618
+ performance: {
619
+ chunkSplit: {
620
+ strategy: 'single-vendor',
621
+ },
622
+ },
623
+ });
624
+ });
386
625
  });
@@ -0,0 +1,25 @@
1
+ import {
2
+ getModernTanstackRouterFastDefaults,
3
+ modernTanstackRouterFastDefaults,
4
+ } from '../../src/runtime/types';
5
+
6
+ describe('tanstack router fast defaults', () => {
7
+ test('enables structural sharing by default', () => {
8
+ expect(modernTanstackRouterFastDefaults).toEqual({
9
+ defaultStructuralSharing: true,
10
+ });
11
+ expect(getModernTanstackRouterFastDefaults()).toEqual({
12
+ defaultStructuralSharing: true,
13
+ });
14
+ });
15
+
16
+ test('allows explicit structural sharing override', () => {
17
+ expect(
18
+ getModernTanstackRouterFastDefaults({
19
+ defaultStructuralSharing: false,
20
+ }),
21
+ ).toEqual({
22
+ defaultStructuralSharing: false,
23
+ });
24
+ });
25
+ });