@tanstack/router-plugin 1.168.5 → 1.168.7
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.
- package/dist/cjs/core/code-splitter/compilers.cjs +32 -233
- package/dist/cjs/core/code-splitter/compilers.cjs.map +1 -1
- package/dist/cjs/core/code-splitter/compilers.d.cts +2 -57
- package/dist/cjs/core/code-splitter/plugins.d.cts +1 -0
- package/dist/cjs/core/config.cjs +1 -1
- package/dist/cjs/core/config.cjs.map +1 -1
- package/dist/cjs/core/config.d.cts +44 -163
- package/dist/cjs/core/hmr/handle-route-update.cjs +17 -18
- package/dist/cjs/core/hmr/handle-route-update.cjs.map +1 -1
- package/dist/cjs/core/router-code-splitter-plugin.cjs +5 -6
- package/dist/cjs/core/router-code-splitter-plugin.cjs.map +1 -1
- package/dist/cjs/esbuild.d.cts +26 -26
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.d.cts +2 -0
- package/dist/cjs/vite.d.cts +26 -26
- package/dist/esm/core/code-splitter/compilers.d.ts +2 -57
- package/dist/esm/core/code-splitter/compilers.js +15 -216
- package/dist/esm/core/code-splitter/compilers.js.map +1 -1
- package/dist/esm/core/code-splitter/plugins.d.ts +1 -0
- package/dist/esm/core/config.d.ts +44 -163
- package/dist/esm/core/config.js +1 -1
- package/dist/esm/core/config.js.map +1 -1
- package/dist/esm/core/hmr/handle-route-update.js +17 -18
- package/dist/esm/core/hmr/handle-route-update.js.map +1 -1
- package/dist/esm/core/router-code-splitter-plugin.js +5 -6
- package/dist/esm/core/router-code-splitter-plugin.js.map +1 -1
- package/dist/esm/esbuild.d.ts +26 -26
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/vite.d.ts +26 -26
- package/package.json +7 -7
- package/src/core/code-splitter/compilers.ts +51 -411
- package/src/core/code-splitter/plugins.ts +3 -0
- package/src/core/config.ts +12 -1
- package/src/core/hmr/handle-route-update.ts +30 -35
- package/src/core/router-code-splitter-plugin.ts +11 -9
- package/src/index.ts +5 -0
- package/dist/cjs/core/code-splitter/path-ids.cjs +0 -32
- package/dist/cjs/core/code-splitter/path-ids.cjs.map +0 -1
- package/dist/cjs/core/code-splitter/path-ids.d.cts +0 -2
- package/dist/esm/core/code-splitter/path-ids.d.ts +0 -2
- package/dist/esm/core/code-splitter/path-ids.js +0 -31
- package/dist/esm/core/code-splitter/path-ids.js.map +0 -1
- package/src/core/code-splitter/path-ids.ts +0 -39
|
@@ -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
|
-
|
|
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
|
-
|
|
1599
|
-
|
|
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
|
package/src/core/config.ts
CHANGED
|
@@ -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
|
|
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),
|
|
@@ -18,7 +18,8 @@ type AnyRouteWithPrivateProps = AnyRoute & {
|
|
|
18
18
|
|
|
19
19
|
type AnyRouterWithPrivateMaps = AnyRouter & {
|
|
20
20
|
routesById: Record<string, AnyRoute>
|
|
21
|
-
|
|
21
|
+
buildRouteTree: () => Parameters<AnyRouter['setRoutes']>[0]
|
|
22
|
+
setRoutes: AnyRouter['setRoutes']
|
|
22
23
|
stores: AnyRouter['stores'] & {
|
|
23
24
|
cachedMatchStores: Map<
|
|
24
25
|
string,
|
|
@@ -54,18 +55,19 @@ function handleRouteUpdate(
|
|
|
54
55
|
return
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
// Generated route-tree options are not present on the freshly imported route
|
|
59
|
+
// module, but they must stay on the live route before rebuilding indexes.
|
|
60
|
+
const generatedRouteOptionKeys = new Set(['id', 'path', 'getParentRoute'])
|
|
61
|
+
const generatedRouteOptions: Record<string, unknown> = {}
|
|
62
|
+
generatedRouteOptionKeys.forEach((key) => {
|
|
63
|
+
if (key in oldRoute.options) {
|
|
64
|
+
generatedRouteOptions[key] = oldRoute.options[key]
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
66
68
|
const removedKeys = new Set<string>()
|
|
67
69
|
Object.keys(oldRoute.options).forEach((key) => {
|
|
68
|
-
if (!(key in newRoute.options)) {
|
|
70
|
+
if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) {
|
|
69
71
|
removedKeys.add(key)
|
|
70
72
|
delete oldRoute.options[key]
|
|
71
73
|
}
|
|
@@ -76,6 +78,15 @@ function handleRouteUpdate(
|
|
|
76
78
|
const preserveComponentIdentity =
|
|
77
79
|
oldHasShellComponent === newHasShellComponent
|
|
78
80
|
|
|
81
|
+
// Keys whose identity must remain stable to prevent React from
|
|
82
|
+
// unmounting/remounting the component tree. React Fast Refresh already
|
|
83
|
+
// handles hot-updating the function bodies of these components — our job
|
|
84
|
+
// is only to update non-component route options (loader, head, etc.).
|
|
85
|
+
// For code-split (splittable) routes, the lazyRouteComponent wrapper is
|
|
86
|
+
// already cached in the bundler hot data so its identity is stable.
|
|
87
|
+
// For unsplittable routes (e.g. root routes), the component is a plain
|
|
88
|
+
// function reference that gets recreated on every module re-execution,
|
|
89
|
+
// so we must explicitly preserve the old reference.
|
|
79
90
|
// Preserve component identity so React doesn't remount.
|
|
80
91
|
// React Fast Refresh patches the function bodies in-place.
|
|
81
92
|
const componentKeys = '__TSR_COMPONENT_TYPES__' as unknown as Array<string>
|
|
@@ -87,19 +98,18 @@ function handleRouteUpdate(
|
|
|
87
98
|
})
|
|
88
99
|
}
|
|
89
100
|
|
|
90
|
-
|
|
91
|
-
|
|
101
|
+
const nextOptions = {
|
|
102
|
+
...newRoute.options,
|
|
103
|
+
...generatedRouteOptions,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
oldRoute.options = nextOptions
|
|
107
|
+
oldRoute.update(nextOptions)
|
|
92
108
|
oldRoute._componentsPromise = undefined
|
|
93
109
|
oldRoute._lazyPromise = undefined
|
|
94
110
|
|
|
95
|
-
router.
|
|
96
|
-
router.routesByPath[oldRoute.fullPath] = oldRoute
|
|
97
|
-
|
|
98
|
-
router.processedTree.matchCache.clear()
|
|
99
|
-
router.processedTree.flatCache?.clear()
|
|
100
|
-
router.processedTree.singleCache.clear()
|
|
111
|
+
router.setRoutes(router.buildRouteTree())
|
|
101
112
|
router.resolvePathCache.clear()
|
|
102
|
-
walkReplaceSegmentTree(oldRoute, router.processedTree.segmentTree)
|
|
103
113
|
|
|
104
114
|
const filter = (m: AnyRouteMatch) => m.routeId === oldRoute.id
|
|
105
115
|
const activeMatch = router.stores.matches.get().find(filter)
|
|
@@ -149,21 +159,6 @@ function handleRouteUpdate(
|
|
|
149
159
|
router.invalidate({ filter, sync: true })
|
|
150
160
|
}
|
|
151
161
|
|
|
152
|
-
function walkReplaceSegmentTree(
|
|
153
|
-
route: AnyRouteWithPrivateProps,
|
|
154
|
-
node: AnyRouter['processedTree']['segmentTree'],
|
|
155
|
-
) {
|
|
156
|
-
if (node.route?.id === route.id) node.route = route
|
|
157
|
-
if (node.index) walkReplaceSegmentTree(route, node.index)
|
|
158
|
-
node.static?.forEach((child) => walkReplaceSegmentTree(route, child))
|
|
159
|
-
node.staticInsensitive?.forEach((child) =>
|
|
160
|
-
walkReplaceSegmentTree(route, child),
|
|
161
|
-
)
|
|
162
|
-
node.dynamic?.forEach((child) => walkReplaceSegmentTree(route, child))
|
|
163
|
-
node.optional?.forEach((child) => walkReplaceSegmentTree(route, child))
|
|
164
|
-
node.wildcard?.forEach((child) => walkReplaceSegmentTree(route, child))
|
|
165
|
-
}
|
|
166
|
-
|
|
167
162
|
function getStoreMatch(matchId: string) {
|
|
168
163
|
return (
|
|
169
164
|
router.stores.pendingMatchStores.get(matchId)?.get() ||
|