@tanstack/router-plugin 1.58.1 → 1.58.4
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 +162 -48
- package/dist/cjs/core/code-splitter/compilers.cjs.map +1 -1
- package/dist/cjs/core/router-code-splitter-plugin.cjs +2 -1
- package/dist/cjs/core/router-code-splitter-plugin.cjs.map +1 -1
- package/dist/esm/core/code-splitter/compilers.js +162 -48
- package/dist/esm/core/code-splitter/compilers.js.map +1 -1
- package/dist/esm/core/router-code-splitter-plugin.js +3 -2
- package/dist/esm/core/router-code-splitter-plugin.js.map +1 -1
- package/package.json +3 -3
- package/src/core/code-splitter/compilers.ts +213 -65
- package/src/core/router-code-splitter-plugin.ts +8 -3
|
@@ -98,6 +98,8 @@ export function compileCodeSplitReferenceRoute(opts: ParseAstOptions) {
|
|
|
98
98
|
if (prop.key.name === 'component') {
|
|
99
99
|
const value = prop.value
|
|
100
100
|
|
|
101
|
+
let shouldSplit = true
|
|
102
|
+
|
|
101
103
|
if (t.isIdentifier(value)) {
|
|
102
104
|
existingCompImportPath =
|
|
103
105
|
getImportSpecifierAndPathFromLocalName(
|
|
@@ -105,51 +107,63 @@ export function compileCodeSplitReferenceRoute(opts: ParseAstOptions) {
|
|
|
105
107
|
value.name,
|
|
106
108
|
).path
|
|
107
109
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
// to import it again
|
|
110
|
+
// exported identifiers should not be split
|
|
111
|
+
// since they are already being imported
|
|
112
|
+
// and need to be retained in the compiled file
|
|
113
|
+
const isExported = hasExport(ast, value)
|
|
114
|
+
shouldSplit = !isExported
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
)
|
|
119
|
-
) {
|
|
120
|
-
programPath.unshiftContainer('body', [
|
|
121
|
-
template.statement(
|
|
122
|
-
`import { lazyRouteComponent } from '@tanstack/react-router'`,
|
|
123
|
-
)(),
|
|
124
|
-
])
|
|
116
|
+
if (shouldSplit) {
|
|
117
|
+
removeIdentifierLiteral(path, value)
|
|
118
|
+
}
|
|
125
119
|
}
|
|
126
120
|
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
121
|
+
if (shouldSplit) {
|
|
122
|
+
// Prepend the import statement to the program along with the importer function
|
|
123
|
+
// Check to see if lazyRouteComponent is already imported before attempting
|
|
124
|
+
// to import it again
|
|
125
|
+
|
|
126
|
+
if (
|
|
127
|
+
!hasImportedOrDefinedIdentifier(
|
|
128
|
+
'lazyRouteComponent',
|
|
129
|
+
)
|
|
130
|
+
) {
|
|
131
|
+
programPath.unshiftContainer('body', [
|
|
132
|
+
template.statement(
|
|
133
|
+
`import { lazyRouteComponent } from '@tanstack/react-router'`,
|
|
134
|
+
)(),
|
|
135
|
+
])
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (
|
|
139
|
+
!hasImportedOrDefinedIdentifier(
|
|
140
|
+
'$$splitComponentImporter',
|
|
141
|
+
)
|
|
142
|
+
) {
|
|
143
|
+
programPath.unshiftContainer('body', [
|
|
144
|
+
template.statement(
|
|
145
|
+
`const $$splitComponentImporter = () => import('${splitUrl}')`,
|
|
146
|
+
)(),
|
|
147
|
+
])
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
prop.value = template.expression(
|
|
151
|
+
`lazyRouteComponent($$splitComponentImporter, 'component')`,
|
|
152
|
+
)()
|
|
153
|
+
|
|
154
|
+
programPath.pushContainer('body', [
|
|
133
155
|
template.statement(
|
|
134
|
-
`
|
|
156
|
+
`function DummyComponent() { return null }`,
|
|
135
157
|
)(),
|
|
136
158
|
])
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
prop.value = template.expression(
|
|
140
|
-
`lazyRouteComponent($$splitComponentImporter, 'component')`,
|
|
141
|
-
)()
|
|
142
|
-
|
|
143
|
-
programPath.pushContainer('body', [
|
|
144
|
-
template.statement(
|
|
145
|
-
`function DummyComponent() { return null }`,
|
|
146
|
-
)(),
|
|
147
|
-
])
|
|
148
159
|
|
|
149
|
-
|
|
160
|
+
found = true
|
|
161
|
+
}
|
|
150
162
|
} else if (prop.key.name === 'loader') {
|
|
151
163
|
const value = prop.value
|
|
152
164
|
|
|
165
|
+
let shouldSplit = true
|
|
166
|
+
|
|
153
167
|
if (t.isIdentifier(value)) {
|
|
154
168
|
existingLoaderImportPath =
|
|
155
169
|
getImportSpecifierAndPathFromLocalName(
|
|
@@ -157,36 +171,45 @@ export function compileCodeSplitReferenceRoute(opts: ParseAstOptions) {
|
|
|
157
171
|
value.name,
|
|
158
172
|
).path
|
|
159
173
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
174
|
+
// exported identifiers should not be split
|
|
175
|
+
// since they are already being imported
|
|
176
|
+
// and need to be retained in the compiled file
|
|
177
|
+
const isExported = hasExport(ast, value)
|
|
178
|
+
shouldSplit = !isExported
|
|
164
179
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
`import { lazyFn } from '@tanstack/react-router'`,
|
|
169
|
-
)() as t.Statement,
|
|
170
|
-
])
|
|
180
|
+
if (shouldSplit) {
|
|
181
|
+
removeIdentifierLiteral(path, value)
|
|
182
|
+
}
|
|
171
183
|
}
|
|
172
184
|
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
185
|
+
if (shouldSplit) {
|
|
186
|
+
// Prepend the import statement to the program along with the importer function
|
|
187
|
+
if (!hasImportedOrDefinedIdentifier('lazyFn')) {
|
|
188
|
+
programPath.unshiftContainer('body', [
|
|
189
|
+
template.smart(
|
|
190
|
+
`import { lazyFn } from '@tanstack/react-router'`,
|
|
191
|
+
)() as t.Statement,
|
|
192
|
+
])
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (
|
|
196
|
+
!hasImportedOrDefinedIdentifier(
|
|
197
|
+
'$$splitLoaderImporter',
|
|
198
|
+
)
|
|
199
|
+
) {
|
|
200
|
+
programPath.unshiftContainer('body', [
|
|
201
|
+
template.statement(
|
|
202
|
+
`const $$splitLoaderImporter = () => import('${splitUrl}')`,
|
|
203
|
+
)(),
|
|
204
|
+
])
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
prop.value = template.expression(
|
|
208
|
+
`lazyFn($$splitLoaderImporter, 'loader')`,
|
|
209
|
+
)()
|
|
210
|
+
|
|
211
|
+
found = true
|
|
183
212
|
}
|
|
184
|
-
|
|
185
|
-
prop.value = template.expression(
|
|
186
|
-
`lazyFn($$splitLoaderImporter, 'loader')`,
|
|
187
|
-
)()
|
|
188
|
-
|
|
189
|
-
found = true
|
|
190
213
|
}
|
|
191
214
|
}
|
|
192
215
|
}
|
|
@@ -251,6 +274,8 @@ export function compileCodeSplitVirtualRoute(opts: ParseAstOptions) {
|
|
|
251
274
|
)
|
|
252
275
|
}
|
|
253
276
|
|
|
277
|
+
const knownExportedIdents = new Set<string>()
|
|
278
|
+
|
|
254
279
|
babel.traverse(ast, {
|
|
255
280
|
Program: {
|
|
256
281
|
enter(programPath, programState) {
|
|
@@ -287,12 +312,31 @@ export function compileCodeSplitVirtualRoute(opts: ParseAstOptions) {
|
|
|
287
312
|
if (t.isObjectExpression(options)) {
|
|
288
313
|
options.properties.forEach((prop) => {
|
|
289
314
|
if (t.isObjectProperty(prop)) {
|
|
290
|
-
splitNodeTypes.forEach((
|
|
291
|
-
if (
|
|
292
|
-
|
|
293
|
-
|
|
315
|
+
splitNodeTypes.forEach((splitType) => {
|
|
316
|
+
if (
|
|
317
|
+
!t.isIdentifier(prop.key) ||
|
|
318
|
+
prop.key.name !== splitType
|
|
319
|
+
) {
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const value = prop.value
|
|
324
|
+
|
|
325
|
+
let isExported = false
|
|
326
|
+
if (t.isIdentifier(value)) {
|
|
327
|
+
isExported = hasExport(ast, value)
|
|
328
|
+
if (isExported) {
|
|
329
|
+
knownExportedIdents.add(value.name)
|
|
294
330
|
}
|
|
295
331
|
}
|
|
332
|
+
|
|
333
|
+
// If the node is exported, we need to remove
|
|
334
|
+
// the export from the split file
|
|
335
|
+
if (isExported && t.isIdentifier(value)) {
|
|
336
|
+
removeExports(ast, value)
|
|
337
|
+
} else {
|
|
338
|
+
splitNodesByType[splitType] = prop.value
|
|
339
|
+
}
|
|
296
340
|
})
|
|
297
341
|
}
|
|
298
342
|
})
|
|
@@ -448,6 +492,28 @@ export function compileCodeSplitVirtualRoute(opts: ParseAstOptions) {
|
|
|
448
492
|
|
|
449
493
|
deadCodeElimination(ast)
|
|
450
494
|
|
|
495
|
+
// if there are exported identifiers, then we need to add a warning
|
|
496
|
+
// to the file to let the user know that the exported identifiers
|
|
497
|
+
// will not in the split file but in the original file, therefore
|
|
498
|
+
// increasing the bundle size
|
|
499
|
+
if (knownExportedIdents.size > 0) {
|
|
500
|
+
const list = Array.from(knownExportedIdents).reduce((str, ident) => {
|
|
501
|
+
str += `\n- ${ident}`
|
|
502
|
+
return str
|
|
503
|
+
}, '')
|
|
504
|
+
|
|
505
|
+
const warningMessage = `These exports from "${opts.filename.replace('?' + splitPrefix, '')}" are not being code-split and will increase your bundle size: ${list}\nThese should either have their export statements removed or be imported from another file that is not a route.`
|
|
506
|
+
console.warn(warningMessage)
|
|
507
|
+
|
|
508
|
+
// append this warning to the file using a template
|
|
509
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
510
|
+
const warningTemplate = template.statement(
|
|
511
|
+
`console.warn(${JSON.stringify(warningMessage)})`,
|
|
512
|
+
)()
|
|
513
|
+
ast.program.body.unshift(warningTemplate)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
451
517
|
return generate(ast, {
|
|
452
518
|
sourceMaps: true,
|
|
453
519
|
})
|
|
@@ -515,3 +581,85 @@ function removeIdentifierLiteral(path: any, node: any) {
|
|
|
515
581
|
}
|
|
516
582
|
}
|
|
517
583
|
}
|
|
584
|
+
|
|
585
|
+
function hasExport(ast: t.File, node: t.Identifier): boolean {
|
|
586
|
+
let found = false
|
|
587
|
+
|
|
588
|
+
babel.traverse(ast, {
|
|
589
|
+
ExportNamedDeclaration(path) {
|
|
590
|
+
if (path.node.declaration) {
|
|
591
|
+
// declared as `const loaderFn = () => {}`
|
|
592
|
+
if (t.isVariableDeclaration(path.node.declaration)) {
|
|
593
|
+
path.node.declaration.declarations.forEach((decl) => {
|
|
594
|
+
if (t.isVariableDeclarator(decl)) {
|
|
595
|
+
if (t.isIdentifier(decl.id)) {
|
|
596
|
+
if (decl.id.name === node.name) {
|
|
597
|
+
found = true
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
})
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// declared as `function loaderFn() {}`
|
|
605
|
+
if (t.isFunctionDeclaration(path.node.declaration)) {
|
|
606
|
+
if (t.isIdentifier(path.node.declaration.id)) {
|
|
607
|
+
if (path.node.declaration.id.name === node.name) {
|
|
608
|
+
found = true
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
ExportDefaultDeclaration(path) {
|
|
615
|
+
if (t.isIdentifier(path.node.declaration)) {
|
|
616
|
+
if (path.node.declaration.name === node.name) {
|
|
617
|
+
found = true
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
return found
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function removeExports(ast: t.File, node: t.Identifier): boolean {
|
|
627
|
+
let removed = false
|
|
628
|
+
|
|
629
|
+
babel.traverse(ast, {
|
|
630
|
+
ExportNamedDeclaration(path) {
|
|
631
|
+
if (path.node.declaration) {
|
|
632
|
+
// declared as `const loaderFn = () => {}`
|
|
633
|
+
if (t.isVariableDeclaration(path.node.declaration)) {
|
|
634
|
+
path.node.declaration.declarations.forEach((decl) => {
|
|
635
|
+
if (t.isVariableDeclarator(decl)) {
|
|
636
|
+
if (t.isIdentifier(decl.id)) {
|
|
637
|
+
if (decl.id.name === node.name) {
|
|
638
|
+
path.remove()
|
|
639
|
+
removed = true
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
})
|
|
644
|
+
} else if (t.isFunctionDeclaration(path.node.declaration)) {
|
|
645
|
+
if (t.isIdentifier(path.node.declaration.id)) {
|
|
646
|
+
if (path.node.declaration.id.name === node.name) {
|
|
647
|
+
path.remove()
|
|
648
|
+
removed = true
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
ExportDefaultDeclaration(path) {
|
|
655
|
+
if (t.isIdentifier(path.node.declaration)) {
|
|
656
|
+
if (path.node.declaration.name === node.name) {
|
|
657
|
+
path.remove()
|
|
658
|
+
removed = true
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
return removed
|
|
665
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isAbsolute, join } from 'node:path'
|
|
1
|
+
import { isAbsolute, join, normalize } from 'node:path'
|
|
2
2
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
3
3
|
|
|
4
4
|
import { getConfig } from './config'
|
|
@@ -15,12 +15,17 @@ function capitalizeFirst(str: string): string {
|
|
|
15
15
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function fileIsInRoutesDirectory(
|
|
18
|
+
function fileIsInRoutesDirectory(
|
|
19
|
+
filePath: string,
|
|
20
|
+
routesDirectory: string,
|
|
21
|
+
): boolean {
|
|
19
22
|
const routesDirectoryPath = isAbsolute(routesDirectory)
|
|
20
23
|
? routesDirectory
|
|
21
24
|
: join(process.cwd(), routesDirectory)
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
const path = normalize(filePath)
|
|
27
|
+
|
|
28
|
+
return path.startsWith(routesDirectoryPath)
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
type BannedBeforeExternalPlugin = {
|