@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.
@@ -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
- removeIdentifierLiteral(path, value)
109
- }
110
-
111
- // Prepend the import statement to the program along with the importer function
112
- // Check to see if lazyRouteComponent is already imported before attempting
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
- if (
116
- !hasImportedOrDefinedIdentifier(
117
- 'lazyRouteComponent',
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
- !hasImportedOrDefinedIdentifier(
129
- '$$splitComponentImporter',
130
- )
131
- ) {
132
- programPath.unshiftContainer('body', [
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
- `const $$splitComponentImporter = () => import('${splitUrl}')`,
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
- found = true
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
- removeIdentifierLiteral(path, value)
161
- }
162
-
163
- // Prepend the import statement to the program along with the importer function
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
- if (!hasImportedOrDefinedIdentifier('lazyFn')) {
166
- programPath.unshiftContainer('body', [
167
- template.smart(
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
- !hasImportedOrDefinedIdentifier(
175
- '$$splitLoaderImporter',
176
- )
177
- ) {
178
- programPath.unshiftContainer('body', [
179
- template.statement(
180
- `const $$splitLoaderImporter = () => import('${splitUrl}')`,
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((type) => {
291
- if (t.isIdentifier(prop.key)) {
292
- if (prop.key.name === type) {
293
- splitNodesByType[type] = prop.value
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(filePath: string, routesDirectory: string) {
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
- return filePath.startsWith(routesDirectoryPath)
26
+ const path = normalize(filePath)
27
+
28
+ return path.startsWith(routesDirectoryPath)
24
29
  }
25
30
 
26
31
  type BannedBeforeExternalPlugin = {