@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.89 → 3.2.0-ultramodern.90

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 (43) hide show
  1. package/dist/cjs/cli/index.js +12 -0
  2. package/dist/cjs/cli/routeSplitting.js +83 -0
  3. package/dist/cjs/cli/tanstackTypes.js +45 -7
  4. package/dist/cjs/runtime/index.js +12 -0
  5. package/dist/cjs/runtime/plugin.js +2 -0
  6. package/dist/cjs/runtime/plugin.node.js +2 -0
  7. package/dist/cjs/runtime/routeTree.js +8 -0
  8. package/dist/cjs/runtime/types.js +27 -1
  9. package/dist/esm/cli/index.mjs +4 -1
  10. package/dist/esm/cli/routeSplitting.mjs +43 -0
  11. package/dist/esm/cli/tanstackTypes.mjs +45 -7
  12. package/dist/esm/runtime/index.mjs +1 -0
  13. package/dist/esm/runtime/plugin.mjs +2 -0
  14. package/dist/esm/runtime/plugin.node.mjs +2 -0
  15. package/dist/esm/runtime/routeTree.mjs +8 -0
  16. package/dist/esm/runtime/types.mjs +7 -0
  17. package/dist/esm-node/cli/index.mjs +4 -1
  18. package/dist/esm-node/cli/routeSplitting.mjs +44 -0
  19. package/dist/esm-node/cli/tanstackTypes.mjs +45 -7
  20. package/dist/esm-node/runtime/index.mjs +1 -0
  21. package/dist/esm-node/runtime/plugin.mjs +2 -0
  22. package/dist/esm-node/runtime/plugin.node.mjs +2 -0
  23. package/dist/esm-node/runtime/routeTree.mjs +8 -0
  24. package/dist/esm-node/runtime/types.mjs +7 -0
  25. package/dist/types/cli/index.d.ts +4 -0
  26. package/dist/types/cli/routeSplitting.d.ts +29 -0
  27. package/dist/types/runtime/index.d.ts +2 -0
  28. package/dist/types/runtime/plugin.d.ts +1 -1
  29. package/dist/types/runtime/plugin.node.d.ts +1 -1
  30. package/dist/types/runtime/types.d.ts +7 -0
  31. package/package.json +10 -10
  32. package/src/cli/index.ts +17 -0
  33. package/src/cli/routeSplitting.ts +81 -0
  34. package/src/cli/tanstackTypes.ts +102 -13
  35. package/src/runtime/index.tsx +5 -0
  36. package/src/runtime/plugin.node.tsx +6 -1
  37. package/src/runtime/plugin.tsx +5 -1
  38. package/src/runtime/routeTree.ts +12 -0
  39. package/src/runtime/types.ts +13 -0
  40. package/tests/router/cli.test.ts +239 -0
  41. package/tests/router/fastDefaults.test.ts +25 -0
  42. package/tests/router/routeTree.test.ts +87 -0
  43. package/tests/router/tanstackTypes.test.ts +120 -0
@@ -86,6 +86,17 @@ function pickModernLoaderModule(route: NestedRouteForCli | PageRoute) {
86
86
  return { loaderPath, inline };
87
87
  }
88
88
 
89
+ function pickRouteSearchContractModules(route: NestedRouteForCli | PageRoute) {
90
+ const validateSearchPath = (route as any).validateSearch;
91
+ const loaderDepsPath = (route as any).loaderDeps;
92
+
93
+ return {
94
+ validateSearchPath:
95
+ typeof validateSearchPath === 'string' ? validateSearchPath : null,
96
+ loaderDepsPath: typeof loaderDepsPath === 'string' ? loaderDepsPath : null,
97
+ };
98
+ }
99
+
89
100
  function isPathlessLayout(route: NestedRouteForCli | PageRoute) {
90
101
  return (
91
102
  (route as any).type === 'nested' &&
@@ -182,10 +193,29 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
182
193
  const statements: string[] = [];
183
194
 
184
195
  const loaderImportMap = new Map<string, string>();
196
+ const searchContractImportMap = new Map<string, string>();
185
197
  const usedRouteVarNames = new Set<string>();
186
198
  let loaderIndex = 0;
199
+ let validateSearchIndex = 0;
200
+ let loaderDepsIndex = 0;
187
201
  let routeIndex = 0;
188
202
 
203
+ const resolveRouteModuleNoExt = async (aliasedNoExtPath: string) => {
204
+ const prefix = `${appContext.internalSrcAlias}/`;
205
+ let absNoExt: string;
206
+ if (aliasedNoExtPath.startsWith(prefix)) {
207
+ const rel = aliasedNoExtPath.slice(prefix.length);
208
+ absNoExt = path.join(appContext.srcDirectory, rel);
209
+ } else if (path.isAbsolute(aliasedNoExtPath)) {
210
+ absNoExt = aliasedNoExtPath;
211
+ } else {
212
+ // Unknown format; treat as already relative to src.
213
+ absNoExt = path.join(appContext.srcDirectory, aliasedNoExtPath);
214
+ }
215
+
216
+ return resolveFileNoExt(absNoExt);
217
+ };
218
+
189
219
  const getImportNamesForLoader = async (
190
220
  aliasedNoExtPath: string,
191
221
  inline: boolean,
@@ -202,19 +232,7 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
202
232
  };
203
233
  }
204
234
 
205
- const prefix = `${appContext.internalSrcAlias}/`;
206
- let absNoExt: string;
207
- if (aliasedNoExtPath.startsWith(prefix)) {
208
- const rel = aliasedNoExtPath.slice(prefix.length);
209
- absNoExt = path.join(appContext.srcDirectory, rel);
210
- } else if (path.isAbsolute(aliasedNoExtPath)) {
211
- absNoExt = aliasedNoExtPath;
212
- } else {
213
- // Unknown format; treat as already relative to src.
214
- absNoExt = path.join(appContext.srcDirectory, aliasedNoExtPath);
215
- }
216
-
217
- const resolvedNoExt = await resolveFileNoExt(absNoExt);
235
+ const resolvedNoExt = await resolveRouteModuleNoExt(aliasedNoExtPath);
218
236
  if (!resolvedNoExt) {
219
237
  return null;
220
238
  }
@@ -243,6 +261,35 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
243
261
  return { loaderName: importName, actionName };
244
262
  };
245
263
 
264
+ const getImportNameForSearchContract = async (
265
+ aliasedNoExtPath: string,
266
+ exportName: 'validateSearch' | 'loaderDeps',
267
+ ) => {
268
+ const key = `${exportName}:${aliasedNoExtPath}`;
269
+ const existing = searchContractImportMap.get(key);
270
+ if (existing) {
271
+ return existing;
272
+ }
273
+
274
+ const resolvedNoExt = await resolveRouteModuleNoExt(aliasedNoExtPath);
275
+ if (!resolvedNoExt) {
276
+ return null;
277
+ }
278
+
279
+ const relImport = normalizeRelativeImport(
280
+ path.relative(outDir, resolvedNoExt),
281
+ );
282
+ const importName =
283
+ exportName === 'validateSearch'
284
+ ? `validateSearch_${validateSearchIndex++}`
285
+ : `loaderDeps_${loaderDepsIndex++}`;
286
+ imports.push(
287
+ `import { ${exportName} as ${importName} } from ${quote(relImport)};`,
288
+ );
289
+ searchContractImportMap.set(key, importName);
290
+ return importName;
291
+ };
292
+
246
293
  const reserveRouteVarName = (preferred: string) => {
247
294
  let candidate = preferred;
248
295
  let suffix = 1;
@@ -278,6 +325,19 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
278
325
  : null;
279
326
  const loaderName = loaderImports?.loaderName || null;
280
327
  const actionName = loaderImports?.actionName || null;
328
+ const searchContractInfo = pickRouteSearchContractModules(route);
329
+ const validateSearchName = searchContractInfo.validateSearchPath
330
+ ? await getImportNameForSearchContract(
331
+ searchContractInfo.validateSearchPath,
332
+ 'validateSearch',
333
+ )
334
+ : null;
335
+ const loaderDepsName = searchContractInfo.loaderDepsPath
336
+ ? await getImportNameForSearchContract(
337
+ searchContractInfo.loaderDepsPath,
338
+ 'loaderDeps',
339
+ )
340
+ : null;
281
341
 
282
342
  const rawPath = (route as any).path as string | undefined;
283
343
  const hasSplat = typeof rawPath === 'string' && rawPath.includes('*');
@@ -297,6 +357,12 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
297
357
  `loader: modernLoaderToTanstack({ hasSplat: ${hasSplat} }, ${loaderName}),`,
298
358
  );
299
359
  }
360
+ if (validateSearchName) {
361
+ routeOpts.push(`validateSearch: ${validateSearchName},`);
362
+ }
363
+ if (loaderDepsName) {
364
+ routeOpts.push(`loaderDeps: ${loaderDepsName},`);
365
+ }
300
366
 
301
367
  const staticDataSnippet = createRouteStaticDataSnippet({
302
368
  modernRouteId: (route as any).id as string | undefined,
@@ -346,6 +412,21 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
346
412
  : null;
347
413
  const rootLoaderName = rootLoaderImports?.loaderName || null;
348
414
  const rootActionName = rootLoaderImports?.actionName || null;
415
+ const rootSearchContractInfo = rootModern
416
+ ? pickRouteSearchContractModules(rootModern)
417
+ : null;
418
+ const rootValidateSearchName = rootSearchContractInfo?.validateSearchPath
419
+ ? await getImportNameForSearchContract(
420
+ rootSearchContractInfo.validateSearchPath,
421
+ 'validateSearch',
422
+ )
423
+ : null;
424
+ const rootLoaderDepsName = rootSearchContractInfo?.loaderDepsPath
425
+ ? await getImportNameForSearchContract(
426
+ rootSearchContractInfo.loaderDepsPath,
427
+ 'loaderDeps',
428
+ )
429
+ : null;
349
430
 
350
431
  const topLevelVars = await Promise.all(
351
432
  topLevel.map(route => buildRoute({ parentVar: 'rootRoute', route })),
@@ -357,12 +438,19 @@ export async function generateTanstackRouterTypesSourceForEntry(opts: {
357
438
  `loader: modernLoaderToTanstack({ hasSplat: false }, ${rootLoaderName}),`,
358
439
  );
359
440
  }
441
+ if (rootValidateSearchName) {
442
+ rootOpts.push(`validateSearch: ${rootValidateSearchName},`);
443
+ }
444
+ if (rootLoaderDepsName) {
445
+ rootOpts.push(`loaderDeps: ${rootLoaderDepsName},`);
446
+ }
360
447
 
361
448
  const routerGenTs = `/* eslint-disable */
362
449
  // This file is auto-generated by Modern.js. Do not edit manually.
363
450
 
364
451
  import {
365
452
  createMemoryHistory,
453
+ modernTanstackRouterFastDefaults,
366
454
  createRootRouteWithContext,
367
455
  createRoute,
368
456
  createRouter,
@@ -552,6 +640,7 @@ ${statements.join('\n\n')}
552
640
  export const routeTree = rootRoute.addChildren([${topLevelVars.join(', ')}]);
553
641
 
554
642
  export const router = createRouter({
643
+ ...modernTanstackRouterFastDefaults,
555
644
  routeTree,
556
645
  history: createMemoryHistory({
557
646
  initialEntries: ['/'],
@@ -35,3 +35,8 @@ export type {
35
35
  CompositeComponentProps,
36
36
  } from './rsc/client';
37
37
  export { CompositeComponent } from './rsc/client';
38
+ export type { RouterConfig } from './types';
39
+ export {
40
+ getModernTanstackRouterFastDefaults,
41
+ modernTanstackRouterFastDefaults,
42
+ } from './types';
@@ -52,7 +52,11 @@ import {
52
52
  createTanstackRscServerPayload,
53
53
  handleTanstackRscRedirect,
54
54
  } from './rsc/payloadRouter';
55
- import type { InternalRouterServerSnapshot, RouterConfig } from './types';
55
+ import {
56
+ getModernTanstackRouterFastDefaults,
57
+ type InternalRouterServerSnapshot,
58
+ type RouterConfig,
59
+ } from './types';
56
60
  import { createRouteObjectsFromConfig, urlJoin } from './utils';
57
61
 
58
62
  type ModernTanstackRouterContext = {
@@ -439,6 +443,7 @@ export const tanstackRouterPlugin = (
439
443
  hooks.onBeforeCreateRouter.call(routerLifecycleContext);
440
444
 
441
445
  const tanstackRouter = createRouter({
446
+ ...getModernTanstackRouterFastDefaults(mergedConfig),
442
447
  routeTree,
443
448
  history,
444
449
  basepath: '/',
@@ -43,7 +43,10 @@ import {
43
43
  import { Link } from './prefetchLink';
44
44
  import { createRouteTreeFromRouteObjects } from './routeTree';
45
45
  import { getTanstackRscSerializationAdapters } from './rsc/client';
46
- import type { RouterConfig } from './types';
46
+ import {
47
+ getModernTanstackRouterFastDefaults,
48
+ type RouterConfig,
49
+ } from './types';
47
50
  import { createRouteObjectsFromConfig, urlJoin } from './utils';
48
51
 
49
52
  const BLOCKING_SUBSCRIBE_SYMBOL = Symbol.for(
@@ -322,6 +325,7 @@ export const tanstackRouterPlugin = (
322
325
  : undefined;
323
326
 
324
327
  cachedRouter = createRouter({
328
+ ...getModernTanstackRouterFastDefaults(mergedConfig),
325
329
  routeTree,
326
330
  basepath: '/',
327
331
  rewrite,
@@ -76,8 +76,10 @@ type ModernRouteObject = RouteObject & {
76
76
  isClientComponent?: boolean;
77
77
  lazyImport?: () => unknown;
78
78
  loader?: ModernLoader;
79
+ loaderDeps?: unknown;
79
80
  pendingComponent?: unknown;
80
81
  shouldRevalidate?: ModernShouldRevalidate;
82
+ validateSearch?: unknown;
81
83
  };
82
84
 
83
85
  type ModernGeneratedRoute = (NestedRoute | PageRoute) & {
@@ -102,10 +104,12 @@ type ModernGeneratedRoute = (NestedRoute | PageRoute) & {
102
104
  isRoot?: boolean;
103
105
  lazyImport?: () => unknown;
104
106
  loader?: ModernLoader;
107
+ loaderDeps?: unknown;
105
108
  loading?: unknown;
106
109
  pendingComponent?: unknown;
107
110
  path?: string;
108
111
  shouldRevalidate?: ModernShouldRevalidate;
112
+ validateSearch?: unknown;
109
113
  };
110
114
 
111
115
  type MutableTanstackRoute = AnyRoute & {
@@ -735,6 +739,8 @@ function createRouteFromRouteObject(opts: {
735
739
  component: toRouteComponent(routeObject),
736
740
  pendingComponent: toPendingComponent(routeObject),
737
741
  errorComponent: toErrorComponent(routeObject),
742
+ validateSearch: modernRouteObject.validateSearch,
743
+ loaderDeps: modernRouteObject.loaderDeps,
738
744
  wrapInSuspense: true,
739
745
  staticData: createRouteStaticData({
740
746
  modernRouteId: routeObject.id,
@@ -827,6 +833,8 @@ function createRouteFromModernRoute(opts: {
827
833
  component: component || undefined,
828
834
  pendingComponent: pendingComponent || undefined,
829
835
  errorComponent: errorComponent || undefined,
836
+ validateSearch: route.validateSearch,
837
+ loaderDeps: route.loaderDeps,
830
838
  wrapInSuspense: true,
831
839
  staticData: createRouteStaticData({
832
840
  modernRouteId: modernId,
@@ -910,6 +918,8 @@ export function createRouteTreeFromModernRoutes(
910
918
  component: rootComponent || undefined,
911
919
  pendingComponent: pendingComponent || undefined,
912
920
  errorComponent: errorComponent || undefined,
921
+ validateSearch: rootModern?.validateSearch,
922
+ loaderDeps: rootModern?.loaderDeps,
913
923
  wrapInSuspense: true,
914
924
  notFoundComponent: DefaultNotFound,
915
925
  staticData: createRouteStaticData({
@@ -981,6 +991,8 @@ export function createRouteTreeFromRouteObjects(
981
991
  ? toPendingComponent(rootLikeRoute)
982
992
  : undefined,
983
993
  errorComponent: rootLikeRoute ? toErrorComponent(rootLikeRoute) : undefined,
994
+ validateSearch: rootLikeRoute?.validateSearch,
995
+ loaderDeps: rootLikeRoute?.loaderDeps,
984
996
  wrapInSuspense: true,
985
997
  notFoundComponent: DefaultNotFound,
986
998
  staticData: createRouteStaticData({
@@ -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
+ });