@tanstack/router-plugin 1.168.6 → 1.168.8

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 (39) hide show
  1. package/dist/cjs/core/code-splitter/compilers.cjs +32 -233
  2. package/dist/cjs/core/code-splitter/compilers.cjs.map +1 -1
  3. package/dist/cjs/core/code-splitter/compilers.d.cts +2 -57
  4. package/dist/cjs/core/code-splitter/plugins.d.cts +1 -0
  5. package/dist/cjs/core/config.cjs +1 -1
  6. package/dist/cjs/core/config.cjs.map +1 -1
  7. package/dist/cjs/core/config.d.cts +44 -163
  8. package/dist/cjs/core/router-code-splitter-plugin.cjs +5 -6
  9. package/dist/cjs/core/router-code-splitter-plugin.cjs.map +1 -1
  10. package/dist/cjs/esbuild.d.cts +26 -26
  11. package/dist/cjs/index.cjs +2 -0
  12. package/dist/cjs/index.d.cts +2 -0
  13. package/dist/cjs/vite.d.cts +26 -26
  14. package/dist/esm/core/code-splitter/compilers.d.ts +2 -57
  15. package/dist/esm/core/code-splitter/compilers.js +15 -216
  16. package/dist/esm/core/code-splitter/compilers.js.map +1 -1
  17. package/dist/esm/core/code-splitter/plugins.d.ts +1 -0
  18. package/dist/esm/core/config.d.ts +44 -163
  19. package/dist/esm/core/config.js +1 -1
  20. package/dist/esm/core/config.js.map +1 -1
  21. package/dist/esm/core/router-code-splitter-plugin.js +5 -6
  22. package/dist/esm/core/router-code-splitter-plugin.js.map +1 -1
  23. package/dist/esm/esbuild.d.ts +26 -26
  24. package/dist/esm/index.d.ts +2 -0
  25. package/dist/esm/index.js +2 -1
  26. package/dist/esm/vite.d.ts +26 -26
  27. package/package.json +7 -7
  28. package/src/core/code-splitter/compilers.ts +51 -411
  29. package/src/core/code-splitter/plugins.ts +3 -0
  30. package/src/core/config.ts +12 -1
  31. package/src/core/router-code-splitter-plugin.ts +11 -9
  32. package/src/index.ts +5 -0
  33. package/dist/cjs/core/code-splitter/path-ids.cjs +0 -32
  34. package/dist/cjs/core/code-splitter/path-ids.cjs.map +0 -1
  35. package/dist/cjs/core/code-splitter/path-ids.d.cts +0 -2
  36. package/dist/esm/core/code-splitter/path-ids.d.ts +0 -2
  37. package/dist/esm/core/code-splitter/path-ids.js +0 -31
  38. package/dist/esm/core/code-splitter/path-ids.js.map +0 -1
  39. package/src/core/code-splitter/path-ids.ts +0 -39
@@ -31,7 +31,7 @@ declare const tanStackRouterCodeSplitter: (options?: RouterPluginOptions, router
31
31
  * ```
32
32
  */
33
33
  declare const tanstackRouter: (options?: Partial<{
34
- target: "react" | "solid" | "vue";
34
+ target: "vue" | "react" | "solid";
35
35
  routeFileIgnorePrefix: string;
36
36
  routesDirectory: string;
37
37
  quoteStyle: "single" | "double";
@@ -52,21 +52,11 @@ declare const tanstackRouter: (options?: Partial<{
52
52
  enableRouteTreeFormatting: boolean;
53
53
  tmpDir: string;
54
54
  importRoutesUsingAbsolutePaths: boolean;
55
- enableRouteGeneration?: boolean | undefined;
56
- codeSplittingOptions?: CodeSplittingOptions | undefined;
57
- plugin?: {
58
- vite?: {
59
- environmentName?: string | undefined;
60
- } | undefined;
61
- hmr?: {
62
- style?: "vite" | "webpack" | undefined;
63
- } | undefined;
64
- } | undefined;
65
55
  virtualRouteConfig?: string | import('@tanstack/virtual-file-routes').VirtualRootRoute | undefined;
66
56
  routeFilePrefix?: string | undefined;
67
57
  routeFileIgnorePattern?: string | undefined;
68
- pathParamsAllowedCharacters?: (";" | ":" | "@" | "&" | "=" | "+" | "$" | ",")[] | undefined;
69
- routeTreeFileFooter?: string[] | ((...args: unknown[]) => string[]) | undefined;
58
+ pathParamsAllowedCharacters?: (":" | "$" | ";" | "@" | "&" | "=" | "+" | ",")[] | undefined;
59
+ routeTreeFileFooter?: string[] | (() => Array<string>) | undefined;
70
60
  autoCodeSplitting?: boolean | undefined;
71
61
  customScaffolding?: {
72
62
  routeTemplate?: string | undefined;
@@ -76,12 +66,22 @@ declare const tanstackRouter: (options?: Partial<{
76
66
  enableCodeSplitting?: boolean | undefined;
77
67
  } | undefined;
78
68
  plugins?: import('@tanstack/router-generator').GeneratorPlugin[] | undefined;
69
+ enableRouteGeneration?: boolean | undefined;
70
+ codeSplittingOptions?: CodeSplittingOptions | undefined;
71
+ plugin?: {
72
+ hmr?: {
73
+ style?: "vite" | "webpack" | undefined;
74
+ } | undefined;
75
+ vite?: {
76
+ environmentName?: string | undefined;
77
+ } | undefined;
78
+ } | undefined;
79
79
  } | (() => Config)> | undefined) => import('vite').Plugin<any> | import('vite').Plugin<any>[];
80
80
  /**
81
81
  * @deprecated Use `tanstackRouter` instead.
82
82
  */
83
83
  declare const TanStackRouterVite: (options?: Partial<{
84
- target: "react" | "solid" | "vue";
84
+ target: "vue" | "react" | "solid";
85
85
  routeFileIgnorePrefix: string;
86
86
  routesDirectory: string;
87
87
  quoteStyle: "single" | "double";
@@ -102,21 +102,11 @@ declare const TanStackRouterVite: (options?: Partial<{
102
102
  enableRouteTreeFormatting: boolean;
103
103
  tmpDir: string;
104
104
  importRoutesUsingAbsolutePaths: boolean;
105
- enableRouteGeneration?: boolean | undefined;
106
- codeSplittingOptions?: CodeSplittingOptions | undefined;
107
- plugin?: {
108
- vite?: {
109
- environmentName?: string | undefined;
110
- } | undefined;
111
- hmr?: {
112
- style?: "vite" | "webpack" | undefined;
113
- } | undefined;
114
- } | undefined;
115
105
  virtualRouteConfig?: string | import('@tanstack/virtual-file-routes').VirtualRootRoute | undefined;
116
106
  routeFilePrefix?: string | undefined;
117
107
  routeFileIgnorePattern?: string | undefined;
118
- pathParamsAllowedCharacters?: (";" | ":" | "@" | "&" | "=" | "+" | "$" | ",")[] | undefined;
119
- routeTreeFileFooter?: string[] | ((...args: unknown[]) => string[]) | undefined;
108
+ pathParamsAllowedCharacters?: (":" | "$" | ";" | "@" | "&" | "=" | "+" | ",")[] | undefined;
109
+ routeTreeFileFooter?: string[] | (() => Array<string>) | undefined;
120
110
  autoCodeSplitting?: boolean | undefined;
121
111
  customScaffolding?: {
122
112
  routeTemplate?: string | undefined;
@@ -126,6 +116,16 @@ declare const TanStackRouterVite: (options?: Partial<{
126
116
  enableCodeSplitting?: boolean | undefined;
127
117
  } | undefined;
128
118
  plugins?: import('@tanstack/router-generator').GeneratorPlugin[] | undefined;
119
+ enableRouteGeneration?: boolean | undefined;
120
+ codeSplittingOptions?: CodeSplittingOptions | undefined;
121
+ plugin?: {
122
+ hmr?: {
123
+ style?: "vite" | "webpack" | undefined;
124
+ } | undefined;
125
+ vite?: {
126
+ environmentName?: string | undefined;
127
+ } | undefined;
128
+ } | undefined;
129
129
  } | (() => Config)> | undefined) => import('vite').Plugin<any> | import('vite').Plugin<any>[];
130
130
  export default tanstackRouter;
131
131
  export { configSchema, getConfig, tanStackRouterCodeSplitter, tanstackRouterGenerator, TanStackRouterVite, tanstackRouter, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/router-plugin",
3
- "version": "1.168.6",
3
+ "version": "1.168.8",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -106,12 +106,12 @@
106
106
  "@babel/template": "^7.27.2",
107
107
  "@babel/traverse": "^7.28.5",
108
108
  "@babel/types": "^7.28.5",
109
- "chokidar": "^3.6.0",
109
+ "chokidar": "^5.0.0",
110
110
  "unplugin": "^3.0.0",
111
- "zod": "^3.24.2",
112
- "@tanstack/router-core": "1.171.2",
113
- "@tanstack/router-generator": "1.167.5",
114
- "@tanstack/router-utils": "1.162.0",
111
+ "zod": "^4.4.3",
112
+ "@tanstack/router-core": "1.171.4",
113
+ "@tanstack/router-generator": "1.167.7",
114
+ "@tanstack/router-utils": "1.162.1",
115
115
  "@tanstack/virtual-file-routes": "1.162.0"
116
116
  },
117
117
  "devDependencies": {
@@ -125,7 +125,7 @@
125
125
  "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0",
126
126
  "vite-plugin-solid": "^2.11.10 || ^3.0.0-0",
127
127
  "webpack": ">=5.92.0",
128
- "@tanstack/react-router": "^1.170.4"
128
+ "@tanstack/react-router": "^1.170.6"
129
129
  },
130
130
  "peerDependenciesMeta": {
131
131
  "@rsbuild/core": {
@@ -2,15 +2,27 @@ import * as t from '@babel/types'
2
2
  import * as babel from '@babel/core'
3
3
  import * as template from '@babel/template'
4
4
  import {
5
+ buildDeclarationMap,
6
+ buildDependencyGraph,
7
+ collectIdentifiersFromPattern,
8
+ collectLocalBindingsFromStatement,
9
+ collectModuleLevelRefsFromNode,
10
+ createIdentifier,
5
11
  deadCodeElimination,
12
+ expandDestructuredDeclarations,
13
+ expandSharedDestructuredDeclarators,
14
+ expandTransitively,
6
15
  findReferencedIdentifiers,
7
16
  generateFromAst,
8
17
  parseAst,
18
+ removeBindingsTransitivelyDependingOn,
19
+ retainModuleLevelDeclarations,
20
+ stripUnreferencedTopLevelExpressionStatements,
21
+ unwrapExportedDeclarations,
9
22
  } from '@tanstack/router-utils'
10
23
  import { tsrShared, tsrSplit } from '../constants'
11
24
  import { createRouteHmrStatement } from '../hmr'
12
25
  import { getObjectPropertyKeyName } from '../utils'
13
- import { createIdentifier } from './path-ids'
14
26
  import { getFrameworkOptions } from './framework-options'
15
27
  import type {
16
28
  CompileCodeSplitReferenceRouteOptions,
@@ -20,6 +32,25 @@ import type { GeneratorResult, ParseAstOptions } from '@tanstack/router-utils'
20
32
  import type { CodeSplitGroupings, SplitRouteIdentNodes } from '../constants'
21
33
  import type { SplitNodeMeta } from './types'
22
34
 
35
+ export {
36
+ buildDeclarationMap,
37
+ buildDependencyGraph,
38
+ collectIdentifiersFromNode,
39
+ collectLocalBindingsFromStatement,
40
+ collectModuleLevelRefsFromNode,
41
+ expandDestructuredDeclarations,
42
+ expandSharedDestructuredDeclarators,
43
+ expandTransitively,
44
+ removeBindingsTransitivelyDependingOn,
45
+ } from '@tanstack/router-utils'
46
+
47
+ export function removeBindingsDependingOnRoute(
48
+ bindings: Set<string>,
49
+ dependencyGraph: Map<string, Set<string>>,
50
+ ) {
51
+ removeBindingsTransitivelyDependingOn(bindings, dependencyGraph, ['Route'])
52
+ }
53
+
23
54
  const SPLIT_NODES_CONFIG = new Map<SplitRouteIdentNodes, SplitNodeMeta>([
24
55
  [
25
56
  'loader',
@@ -108,124 +139,6 @@ const allCreateRouteFns = [
108
139
  ...unsplittableCreateRouteFns,
109
140
  ]
110
141
 
111
- /**
112
- * Recursively walk an AST node and collect referenced identifier-like names.
113
- * Much cheaper than babel.traverse — no path/scope overhead.
114
- *
115
- * Notes:
116
- * - Uses @babel/types `isReferenced` to avoid collecting non-references like
117
- * object keys, member expression properties, or binding identifiers.
118
- * - Also handles JSX identifiers for component references.
119
- */
120
- export function collectIdentifiersFromNode(node: t.Node): Set<string> {
121
- const ids = new Set<string>()
122
-
123
- ;(function walk(
124
- n: t.Node | null | undefined,
125
- parent?: t.Node,
126
- grandparent?: t.Node,
127
- parentKey?: string,
128
- ) {
129
- if (!n) return
130
-
131
- if (t.isIdentifier(n)) {
132
- // When we don't have parent info (node passed in isolation), treat as referenced.
133
- if (!parent || t.isReferenced(n, parent, grandparent)) {
134
- ids.add(n.name)
135
- }
136
- return
137
- }
138
-
139
- if (t.isJSXIdentifier(n)) {
140
- // Skip attribute names: <div data-testid="x" />
141
- if (parent && t.isJSXAttribute(parent) && parentKey === 'name') {
142
- return
143
- }
144
-
145
- // Skip member properties: <Foo.Bar /> should count Foo, not Bar
146
- if (
147
- parent &&
148
- t.isJSXMemberExpression(parent) &&
149
- parentKey === 'property'
150
- ) {
151
- return
152
- }
153
-
154
- // Intrinsic elements (lowercase) are not identifiers
155
- const first = n.name[0]
156
- if (first && first === first.toLowerCase()) {
157
- return
158
- }
159
-
160
- ids.add(n.name)
161
- return
162
- }
163
-
164
- for (const key of t.VISITOR_KEYS[n.type] || []) {
165
- const child = (n as any)[key]
166
- if (Array.isArray(child)) {
167
- for (const c of child) {
168
- if (c && typeof c.type === 'string') {
169
- walk(c, n, parent, key)
170
- }
171
- }
172
- } else if (child && typeof child.type === 'string') {
173
- walk(child, n, parent, key)
174
- }
175
- }
176
- })(node)
177
-
178
- return ids
179
- }
180
-
181
- /**
182
- * Build a map from binding name → declaration AST node for all
183
- * locally-declared module-level bindings. Built once, O(1) lookup.
184
- */
185
- export function buildDeclarationMap(ast: t.File): Map<string, t.Node> {
186
- const map = new Map<string, t.Node>()
187
- for (const stmt of ast.program.body) {
188
- const decl =
189
- t.isExportNamedDeclaration(stmt) && stmt.declaration
190
- ? stmt.declaration
191
- : stmt
192
-
193
- if (t.isVariableDeclaration(decl)) {
194
- for (const declarator of decl.declarations) {
195
- for (const name of collectIdentifiersFromPattern(declarator.id)) {
196
- map.set(name, declarator)
197
- }
198
- }
199
- } else if (t.isFunctionDeclaration(decl) && decl.id) {
200
- map.set(decl.id.name, decl)
201
- } else if (t.isClassDeclaration(decl) && decl.id) {
202
- map.set(decl.id.name, decl)
203
- }
204
- }
205
- return map
206
- }
207
-
208
- /**
209
- * Build a dependency graph: for each local binding, the set of other local
210
- * bindings its declaration references. Built once via simple node walking.
211
- */
212
- export function buildDependencyGraph(
213
- declMap: Map<string, t.Node>,
214
- localBindings: Set<string>,
215
- ): Map<string, Set<string>> {
216
- const graph = new Map<string, Set<string>>()
217
- for (const [name, declNode] of declMap) {
218
- if (!localBindings.has(name)) continue
219
- const allIds = collectIdentifiersFromNode(declNode)
220
- const deps = new Set<string>()
221
- for (const id of allIds) {
222
- if (id !== name && localBindings.has(id)) deps.add(id)
223
- }
224
- graph.set(name, deps)
225
- }
226
- return graph
227
- }
228
-
229
142
  /**
230
143
  * Computes module-level bindings that are shared between split and non-split
231
144
  * route properties. These bindings need to be extracted into a shared virtual
@@ -381,199 +294,11 @@ export function computeSharedBindings(opts: {
381
294
  // Remove shared bindings that transitively depend on `Route`.
382
295
  // The Route singleton must stay in the reference file; extracting a
383
296
  // binding that references it would duplicate Route in the shared module.
384
- removeBindingsDependingOnRoute(shared, fullDepGraph)
297
+ removeBindingsTransitivelyDependingOn(shared, fullDepGraph, ['Route'])
385
298
 
386
299
  return shared
387
300
  }
388
301
 
389
- /**
390
- * If bindings from the same destructured declarator are referenced by
391
- * different groups, mark all bindings from that declarator as shared.
392
- */
393
- export function expandSharedDestructuredDeclarators(
394
- ast: t.File,
395
- refsByGroup: Map<string, Set<number>>,
396
- shared: Set<string>,
397
- ) {
398
- for (const stmt of ast.program.body) {
399
- const decl =
400
- t.isExportNamedDeclaration(stmt) && stmt.declaration
401
- ? stmt.declaration
402
- : stmt
403
-
404
- if (!t.isVariableDeclaration(decl)) continue
405
-
406
- for (const declarator of decl.declarations) {
407
- if (!t.isObjectPattern(declarator.id) && !t.isArrayPattern(declarator.id))
408
- continue
409
-
410
- const names = collectIdentifiersFromPattern(declarator.id)
411
-
412
- const usedGroups = new Set<number>()
413
- for (const name of names) {
414
- const groups = refsByGroup.get(name)
415
- if (!groups) continue
416
- for (const g of groups) usedGroups.add(g)
417
- }
418
-
419
- if (usedGroups.size >= 2) {
420
- for (const name of names) {
421
- shared.add(name)
422
- }
423
- }
424
- }
425
- }
426
- }
427
-
428
- /**
429
- * Collect locally-declared module-level binding names from a statement.
430
- * Pure node inspection, no traversal.
431
- */
432
- export function collectLocalBindingsFromStatement(
433
- node: t.Statement | t.ModuleDeclaration,
434
- bindings: Set<string>,
435
- ) {
436
- const decl =
437
- t.isExportNamedDeclaration(node) && node.declaration
438
- ? node.declaration
439
- : node
440
-
441
- if (t.isVariableDeclaration(decl)) {
442
- for (const declarator of decl.declarations) {
443
- for (const name of collectIdentifiersFromPattern(declarator.id)) {
444
- bindings.add(name)
445
- }
446
- }
447
- } else if (t.isFunctionDeclaration(decl) && decl.id) {
448
- bindings.add(decl.id.name)
449
- } else if (t.isClassDeclaration(decl) && decl.id) {
450
- bindings.add(decl.id.name)
451
- }
452
- }
453
-
454
- /**
455
- * Collect direct module-level binding names referenced from a given AST node.
456
- * Uses a simple recursive walk instead of babel.traverse.
457
- */
458
- export function collectModuleLevelRefsFromNode(
459
- node: t.Node,
460
- localModuleLevelBindings: Set<string>,
461
- ): Set<string> {
462
- const allIds = collectIdentifiersFromNode(node)
463
- const refs = new Set<string>()
464
- for (const name of allIds) {
465
- if (localModuleLevelBindings.has(name)) refs.add(name)
466
- }
467
- return refs
468
- }
469
-
470
- /**
471
- * Expand the shared set transitively using a prebuilt dependency graph.
472
- * No AST traversals — pure graph BFS.
473
- */
474
- export function expandTransitively(
475
- shared: Set<string>,
476
- depGraph: Map<string, Set<string>>,
477
- ) {
478
- const queue = [...shared]
479
- const visited = new Set<string>()
480
-
481
- while (queue.length > 0) {
482
- const name = queue.pop()!
483
- if (visited.has(name)) continue
484
- visited.add(name)
485
-
486
- const deps = depGraph.get(name)
487
- if (!deps) continue
488
-
489
- for (const dep of deps) {
490
- if (!shared.has(dep)) {
491
- shared.add(dep)
492
- queue.push(dep)
493
- }
494
- }
495
- }
496
- }
497
-
498
- /**
499
- * Remove any bindings from `shared` that transitively depend on `Route`.
500
- * The Route singleton must remain in the reference file; if a shared binding
501
- * references it (directly or transitively), extracting that binding would
502
- * duplicate Route in the shared module.
503
- *
504
- * Uses `depGraph` which must include `Route` as a node so the dependency
505
- * chain is visible.
506
- */
507
- export function removeBindingsDependingOnRoute(
508
- shared: Set<string>,
509
- depGraph: Map<string, Set<string>>,
510
- ) {
511
- const reverseGraph = new Map<string, Set<string>>()
512
- for (const [name, deps] of depGraph) {
513
- for (const dep of deps) {
514
- let parents = reverseGraph.get(dep)
515
- if (!parents) {
516
- parents = new Set<string>()
517
- reverseGraph.set(dep, parents)
518
- }
519
- parents.add(name)
520
- }
521
- }
522
-
523
- // Walk backwards from Route to find all bindings that can reach it.
524
- const visited = new Set<string>()
525
- const queue = ['Route']
526
- while (queue.length > 0) {
527
- const cur = queue.pop()!
528
- if (visited.has(cur)) continue
529
- visited.add(cur)
530
-
531
- const parents = reverseGraph.get(cur)
532
- if (!parents) continue
533
- for (const parent of parents) {
534
- if (!visited.has(parent)) queue.push(parent)
535
- }
536
- }
537
-
538
- for (const name of [...shared]) {
539
- if (visited.has(name)) {
540
- shared.delete(name)
541
- }
542
- }
543
- }
544
-
545
- /**
546
- * If any binding from a destructured declaration is shared,
547
- * ensure all bindings from that same declaration are also shared.
548
- * Pure node inspection of program.body, no traversal.
549
- */
550
- export function expandDestructuredDeclarations(
551
- ast: t.File,
552
- shared: Set<string>,
553
- ) {
554
- for (const stmt of ast.program.body) {
555
- const decl =
556
- t.isExportNamedDeclaration(stmt) && stmt.declaration
557
- ? stmt.declaration
558
- : stmt
559
-
560
- if (!t.isVariableDeclaration(decl)) continue
561
-
562
- for (const declarator of decl.declarations) {
563
- if (!t.isObjectPattern(declarator.id) && !t.isArrayPattern(declarator.id))
564
- continue
565
-
566
- const names = collectIdentifiersFromPattern(declarator.id)
567
- const hasShared = names.some((n) => shared.has(n))
568
- if (hasShared) {
569
- for (const n of names) {
570
- shared.add(n)
571
- }
572
- }
573
- }
574
- }
575
- }
576
-
577
302
  /**
578
303
  * Find which shared bindings are user-exported in the original source.
579
304
  * These need to be re-exported from the shared module.
@@ -740,6 +465,21 @@ export function compileCodeSplitReferenceRoute(
740
465
  if (t.isObjectExpression(routeOptions)) {
741
466
  const insertionPath = path.getStatementParent() ?? path
742
467
 
468
+ opts.compilerPlugins?.forEach((plugin) => {
469
+ const pluginResult = plugin.onRouteOptions?.({
470
+ programPath,
471
+ callExpressionPath: path,
472
+ insertionPath,
473
+ routeOptions,
474
+ createRouteFn,
475
+ opts: opts as CompileCodeSplitReferenceRouteOptions,
476
+ })
477
+
478
+ if (pluginResult?.modified) {
479
+ modified = true
480
+ }
481
+ })
482
+
743
483
  if (opts.deleteNodes && opts.deleteNodes.size > 0) {
744
484
  routeOptions.properties = routeOptions.properties.filter(
745
485
  (prop) => {
@@ -1525,22 +1265,7 @@ export function compileCodeSplitVirtualRoute(
1525
1265
  })
1526
1266
 
1527
1267
  deadCodeElimination(ast, refIdents)
1528
-
1529
- // Strip top-level expression statements that reference no locally-bound names.
1530
- // DCE only removes unused declarations; bare side-effect statements like
1531
- // `console.log(...)` survive even when the virtual file has no exports.
1532
- {
1533
- const locallyBound = new Set<string>()
1534
- for (const stmt of ast.program.body) {
1535
- collectLocalBindingsFromStatement(stmt, locallyBound)
1536
- }
1537
- ast.program.body = ast.program.body.filter((stmt) => {
1538
- if (!t.isExpressionStatement(stmt)) return true
1539
- const refs = collectIdentifiersFromNode(stmt)
1540
- // Keep if it references at least one locally-bound identifier
1541
- return [...refs].some((name) => locallyBound.has(name))
1542
- })
1543
- }
1268
+ stripUnreferencedTopLevelExpressionStatements(ast)
1544
1269
 
1545
1270
  // If the body is empty after DCE, strip directive prologues too.
1546
1271
  // A file containing only `'use client'` with no real code is useless.
@@ -1595,49 +1320,8 @@ export function compileCodeSplitSharedRoute(
1595
1320
  keepBindings.delete('Route')
1596
1321
  expandTransitively(keepBindings, depGraph)
1597
1322
 
1598
- // Remove all statements except:
1599
- // - Import declarations (needed for deps; DCE will clean unused ones)
1600
- // - Declarations of bindings in keepBindings
1601
- ast.program.body = ast.program.body.filter((stmt) => {
1602
- // Always keep imports — DCE will remove unused ones
1603
- if (t.isImportDeclaration(stmt)) return true
1604
-
1605
- const decl =
1606
- t.isExportNamedDeclaration(stmt) && stmt.declaration
1607
- ? stmt.declaration
1608
- : stmt
1609
-
1610
- if (t.isVariableDeclaration(decl)) {
1611
- // Keep declarators where at least one binding is in keepBindings
1612
- decl.declarations = decl.declarations.filter((declarator) => {
1613
- const names = collectIdentifiersFromPattern(declarator.id)
1614
- return names.some((n) => keepBindings.has(n))
1615
- })
1616
- if (decl.declarations.length === 0) return false
1617
-
1618
- // Strip the `export` wrapper — shared module controls its own exports
1619
- if (t.isExportNamedDeclaration(stmt) && stmt.declaration) {
1620
- return true // keep for now, we'll convert below
1621
- }
1622
- return true
1623
- } else if (t.isFunctionDeclaration(decl) && decl.id) {
1624
- return keepBindings.has(decl.id.name)
1625
- } else if (t.isClassDeclaration(decl) && decl.id) {
1626
- return keepBindings.has(decl.id.name)
1627
- }
1628
-
1629
- // Remove everything else (expression statements, other exports, etc.)
1630
- return false
1631
- })
1632
-
1633
- // Convert `export const/function/class` to plain declarations
1634
- // (we'll add our own export statement at the end)
1635
- ast.program.body = ast.program.body.map((stmt) => {
1636
- if (t.isExportNamedDeclaration(stmt) && stmt.declaration) {
1637
- return stmt.declaration
1638
- }
1639
- return stmt
1640
- })
1323
+ retainModuleLevelDeclarations(ast, keepBindings)
1324
+ unwrapExportedDeclarations(ast)
1641
1325
 
1642
1326
  // Export all shared bindings (sorted for deterministic output)
1643
1327
  const exportNames = [...opts.sharedBindings].sort((a, b) =>
@@ -1837,50 +1521,6 @@ function getImportSpecifierAndPathFromLocalName(
1837
1521
  return { specifier, path }
1838
1522
  }
1839
1523
 
1840
- /**
1841
- * Recursively collects all identifier names from a destructuring pattern
1842
- * (ObjectPattern, ArrayPattern, AssignmentPattern, RestElement).
1843
- */
1844
- function collectIdentifiersFromPattern(
1845
- node: t.LVal | t.Node | null | undefined,
1846
- ): Array<string> {
1847
- if (!node) {
1848
- return []
1849
- }
1850
-
1851
- if (t.isIdentifier(node)) {
1852
- return [node.name]
1853
- }
1854
-
1855
- if (t.isAssignmentPattern(node)) {
1856
- return collectIdentifiersFromPattern(node.left)
1857
- }
1858
-
1859
- if (t.isRestElement(node)) {
1860
- return collectIdentifiersFromPattern(node.argument)
1861
- }
1862
-
1863
- if (t.isObjectPattern(node)) {
1864
- return node.properties.flatMap((prop) => {
1865
- if (t.isObjectProperty(prop)) {
1866
- return collectIdentifiersFromPattern(prop.value as t.LVal)
1867
- }
1868
- if (t.isRestElement(prop)) {
1869
- return collectIdentifiersFromPattern(prop.argument)
1870
- }
1871
- return []
1872
- })
1873
- }
1874
-
1875
- if (t.isArrayPattern(node)) {
1876
- return node.elements.flatMap((element) =>
1877
- collectIdentifiersFromPattern(element),
1878
- )
1879
- }
1880
-
1881
- return []
1882
- }
1883
-
1884
1524
  // Reusable function to get literal value or resolve variable to literal
1885
1525
  function resolveIdentifier(path: any, node: any): t.Node | undefined {
1886
1526
  if (t.isIdentifier(node)) {
@@ -43,6 +43,9 @@ export type ReferenceRouteCompilerPluginResult = {
43
43
  export type ReferenceRouteCompilerPlugin = {
44
44
  name: string
45
45
  getStableRouteOptionKeys?: () => Array<string>
46
+ onRouteOptions?: (
47
+ ctx: ReferenceRouteCompilerPluginContext,
48
+ ) => void | ReferenceRouteCompilerPluginResult
46
49
  onAddHmr?: (
47
50
  ctx: ReferenceRouteCompilerPluginContext,
48
51
  ) => void | ReferenceRouteCompilerPluginResult
@@ -9,6 +9,7 @@ import type {
9
9
  RouteIds,
10
10
  } from '@tanstack/router-core'
11
11
  import type { CodeSplitGroupings } from './constants'
12
+ import type { ReferenceRouteCompilerPlugin } from './code-splitter/plugins'
12
13
 
13
14
  export const splitGroupingsSchema = z
14
15
  .array(
@@ -70,6 +71,12 @@ export type CodeSplittingOptions = {
70
71
  * @default true
71
72
  */
72
73
  addHmr?: boolean
74
+
75
+ /**
76
+ * Internal compiler plugins used by framework integrations.
77
+ * @internal
78
+ */
79
+ compilerPlugins?: Array<ReferenceRouteCompilerPlugin>
73
80
  }
74
81
 
75
82
  export type HmrStyle = 'vite' | 'webpack'
@@ -86,7 +93,11 @@ export type HmrOptions = {
86
93
  }
87
94
 
88
95
  const codeSplittingOptionsSchema = z.object({
89
- splitBehavior: z.function().optional(),
96
+ splitBehavior: z
97
+ .custom<
98
+ CodeSplittingOptions['splitBehavior']
99
+ >((value) => typeof value === 'function')
100
+ .optional(),
90
101
  defaultBehavior: splitGroupingsSchema.optional(),
91
102
  deleteNodes: z.array(z.string()).optional(),
92
103
  addHmr: z.boolean().optional().default(true),