@tanstack/router-plugin 1.39.11 → 1.39.13

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/src/compilers.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import * as t from '@babel/types'
2
+ import babel from '@babel/core'
3
+ import generate from '@babel/generator'
2
4
  import * as template from '@babel/template'
3
5
  import { splitPrefix } from './constants'
4
6
  import { eliminateUnreferencedIdentifiers } from './eliminateUnreferencedIdentifiers'
5
- import type * as babel from '@babel/core'
6
7
  import type { CompileAstFn } from './ast'
7
8
 
8
9
  type SplitModulesById = Record<
@@ -40,134 +41,160 @@ export async function compileFile(opts: {
40
41
  enter(programPath: babel.NodePath<t.Program>, state: State) {
41
42
  const splitUrl = `${splitPrefix}:${opts.filename}?${splitPrefix}`
42
43
 
44
+ /**
45
+ * If the component for the route is being imported from
46
+ * another file, this is to track the path to that file
47
+ * the path itself doesn't matter, we just need to keep
48
+ * track of it so that we can remove it from the imports
49
+ * list if it's not being used like:
50
+ *
51
+ * `import '../shared/imported'`
52
+ */
53
+ let existingCompImportPath: string | null = null
54
+ let existingLoaderImportPath: string | null = null
55
+
43
56
  programPath.traverse(
44
57
  {
45
58
  CallExpression: (path) => {
46
- if (path.node.callee.type === 'Identifier') {
47
- if (
59
+ if (!t.isIdentifier(path.node.callee)) {
60
+ return
61
+ }
62
+
63
+ if (
64
+ !(
48
65
  path.node.callee.name === 'createRoute' ||
49
66
  path.node.callee.name === 'createFileRoute'
50
- ) {
51
- if (
52
- path.parentPath.node.type === 'CallExpression'
53
- ) {
54
- const options = resolveIdentifier(
55
- path,
56
- path.parentPath.node.arguments[0],
57
- )
58
-
59
- let found = false
60
-
61
- const hasImportedOrDefinedIdentifier = (
62
- name: string,
63
- ) => {
64
- return programPath.scope.hasBinding(name)
65
- }
67
+ )
68
+ ) {
69
+ return
70
+ }
71
+
72
+ if (t.isCallExpression(path.parentPath.node)) {
73
+ const options = resolveIdentifier(
74
+ path,
75
+ path.parentPath.node.arguments[0],
76
+ )
66
77
 
67
- if (t.isObjectExpression(options)) {
68
- options.properties.forEach((prop) => {
69
- if (t.isObjectProperty(prop)) {
70
- if (t.isIdentifier(prop.key)) {
71
- if (prop.key.name === 'component') {
72
- const value = prop.value
73
-
74
- if (t.isIdentifier(value)) {
75
- removeIdentifierLiteral(path, value)
76
- }
77
-
78
- // Prepend the import statement to the program along with the importer function
79
- // Check to see if lazyRouteComponent is already imported before attempting
80
- // to import it again
81
-
82
- if (
83
- !hasImportedOrDefinedIdentifier(
84
- 'lazyRouteComponent',
85
- )
86
- ) {
87
- programPath.unshiftContainer('body', [
88
- template.smart(
89
- `import { lazyRouteComponent } from '@tanstack/react-router'`,
90
- )() as t.Statement,
91
- ])
92
- }
93
-
94
- if (
95
- !hasImportedOrDefinedIdentifier(
96
- '$$splitComponentImporter',
97
- )
98
- ) {
99
- programPath.unshiftContainer('body', [
100
- template.smart(
101
- `const $$splitComponentImporter = () => import('${splitUrl}')`,
102
- )() as t.Statement,
103
- ])
104
- }
105
-
106
- prop.value = template.expression(
107
- `lazyRouteComponent($$splitComponentImporter, 'component')`,
108
- )() as any
109
-
110
- programPath.pushContainer('body', [
111
- template.smart(
112
- `function DummyComponent() { return null }`,
113
- )() as t.Statement,
114
- ])
115
-
116
- found = true
117
- } else if (prop.key.name === 'loader') {
118
- const value = prop.value
119
-
120
- if (t.isIdentifier(value)) {
121
- removeIdentifierLiteral(path, value)
122
- }
123
-
124
- // Prepend the import statement to the program along with the importer function
125
-
126
- if (
127
- !hasImportedOrDefinedIdentifier(
128
- 'lazyFn',
129
- )
130
- ) {
131
- programPath.unshiftContainer('body', [
132
- template.smart(
133
- `import { lazyFn } from '@tanstack/react-router'`,
134
- )() as t.Statement,
135
- ])
136
- }
137
-
138
- if (
139
- !hasImportedOrDefinedIdentifier(
140
- '$$splitLoaderImporter',
141
- )
142
- ) {
143
- programPath.unshiftContainer('body', [
144
- template.smart(
145
- `const $$splitLoaderImporter = () => import('${splitUrl}')`,
146
- )() as t.Statement,
147
- ])
148
- }
149
-
150
- prop.value = template.expression(
151
- `lazyFn($$splitLoaderImporter, 'loader')`,
152
- )() as any
153
-
154
- found = true
155
- }
78
+ let found = false
79
+
80
+ const hasImportedOrDefinedIdentifier = (
81
+ name: string,
82
+ ) => {
83
+ return programPath.scope.hasBinding(name)
84
+ }
85
+
86
+ if (t.isObjectExpression(options)) {
87
+ options.properties.forEach((prop) => {
88
+ if (t.isObjectProperty(prop)) {
89
+ if (t.isIdentifier(prop.key)) {
90
+ if (prop.key.name === 'component') {
91
+ const value = prop.value
92
+
93
+ if (t.isIdentifier(value)) {
94
+ existingCompImportPath =
95
+ getImportSpecifierAndPathFromLocalName(
96
+ programPath,
97
+ value.name,
98
+ ).path
99
+
100
+ removeIdentifierLiteral(path, value)
156
101
  }
157
- }
158
102
 
159
- programPath.scope.crawl()
160
- })
161
- }
103
+ // Prepend the import statement to the program along with the importer function
104
+ // Check to see if lazyRouteComponent is already imported before attempting
105
+ // to import it again
106
+
107
+ if (
108
+ !hasImportedOrDefinedIdentifier(
109
+ 'lazyRouteComponent',
110
+ )
111
+ ) {
112
+ programPath.unshiftContainer('body', [
113
+ template.statement(
114
+ `import { lazyRouteComponent } from '@tanstack/react-router'`,
115
+ )(),
116
+ ])
117
+ }
118
+
119
+ if (
120
+ !hasImportedOrDefinedIdentifier(
121
+ '$$splitComponentImporter',
122
+ )
123
+ ) {
124
+ programPath.unshiftContainer('body', [
125
+ template.statement(
126
+ `const $$splitComponentImporter = () => import('${splitUrl}')`,
127
+ )(),
128
+ ])
129
+ }
130
+
131
+ prop.value = template.expression(
132
+ `lazyRouteComponent($$splitComponentImporter, 'component')`,
133
+ )()
134
+
135
+ programPath.pushContainer('body', [
136
+ template.statement(
137
+ `function DummyComponent() { return null }`,
138
+ )(),
139
+ ])
140
+
141
+ found = true
142
+ } else if (prop.key.name === 'loader') {
143
+ const value = prop.value
144
+
145
+ if (t.isIdentifier(value)) {
146
+ existingLoaderImportPath =
147
+ getImportSpecifierAndPathFromLocalName(
148
+ programPath,
149
+ value.name,
150
+ ).path
151
+
152
+ removeIdentifierLiteral(path, value)
153
+ }
154
+
155
+ // Prepend the import statement to the program along with the importer function
162
156
 
163
- if (found as boolean) {
164
- programPath.pushContainer('body', [
165
- template.smart(
166
- `function TSR_Dummy_Component() {}`,
167
- )() as t.Statement,
168
- ])
157
+ if (
158
+ !hasImportedOrDefinedIdentifier('lazyFn')
159
+ ) {
160
+ programPath.unshiftContainer('body', [
161
+ template.smart(
162
+ `import { lazyFn } from '@tanstack/react-router'`,
163
+ )() as t.Statement,
164
+ ])
165
+ }
166
+
167
+ if (
168
+ !hasImportedOrDefinedIdentifier(
169
+ '$$splitLoaderImporter',
170
+ )
171
+ ) {
172
+ programPath.unshiftContainer('body', [
173
+ template.statement(
174
+ `const $$splitLoaderImporter = () => import('${splitUrl}')`,
175
+ )(),
176
+ ])
177
+ }
178
+
179
+ prop.value = template.expression(
180
+ `lazyFn($$splitLoaderImporter, 'loader')`,
181
+ )()
182
+
183
+ found = true
184
+ }
185
+ }
169
186
  }
170
- }
187
+
188
+ programPath.scope.crawl()
189
+ })
190
+ }
191
+
192
+ if (found as boolean) {
193
+ programPath.pushContainer('body', [
194
+ template.statement(
195
+ `function TSR_Dummy_Component() {}`,
196
+ )(),
197
+ ])
171
198
  }
172
199
  }
173
200
  },
@@ -176,6 +203,29 @@ export async function compileFile(opts: {
176
203
  )
177
204
 
178
205
  eliminateUnreferencedIdentifiers(programPath)
206
+
207
+ /**
208
+ * If the component for the route is being imported,
209
+ * and it's not being used, remove the import statement
210
+ * from the program, by checking that the import has no
211
+ * specifiers
212
+ */
213
+ if (
214
+ (existingCompImportPath as string | null) ||
215
+ (existingLoaderImportPath as string | null)
216
+ ) {
217
+ programPath.traverse({
218
+ ImportDeclaration(path) {
219
+ if (path.node.specifiers.length > 0) return
220
+ if (
221
+ path.node.source.value === existingCompImportPath ||
222
+ path.node.source.value === existingLoaderImportPath
223
+ ) {
224
+ path.remove()
225
+ }
226
+ },
227
+ })
228
+ }
179
229
  },
180
230
  },
181
231
  },
@@ -190,6 +240,39 @@ export async function compileFile(opts: {
190
240
  })
191
241
  }
192
242
 
243
+ function getImportSpecifierAndPathFromLocalName(
244
+ programPath: babel.NodePath<t.Program>,
245
+ name: string,
246
+ ): {
247
+ specifier:
248
+ | t.ImportSpecifier
249
+ | t.ImportDefaultSpecifier
250
+ | t.ImportNamespaceSpecifier
251
+ | null
252
+ path: string | null
253
+ } {
254
+ let specifier:
255
+ | t.ImportSpecifier
256
+ | t.ImportDefaultSpecifier
257
+ | t.ImportNamespaceSpecifier
258
+ | null = null
259
+ let path: string | null = null
260
+
261
+ programPath.traverse({
262
+ ImportDeclaration(importPath) {
263
+ const found = importPath.node.specifiers.find(
264
+ (targetSpecifier) => targetSpecifier.local.name === name,
265
+ )
266
+ if (found) {
267
+ specifier = found
268
+ path = importPath.node.source.value
269
+ }
270
+ },
271
+ })
272
+
273
+ return { specifier, path }
274
+ }
275
+
193
276
  // Reusable function to get literal value or resolve variable to literal
194
277
  function resolveIdentifier(path: any, node: any) {
195
278
  if (t.isIdentifier(node)) {
@@ -250,36 +333,40 @@ export async function splitFile(opts: {
250
333
  programPath.traverse(
251
334
  {
252
335
  CallExpression: (path) => {
253
- if (path.node.callee.type === 'Identifier') {
254
- if (
336
+ if (!t.isIdentifier(path.node.callee)) {
337
+ return
338
+ }
339
+
340
+ if (
341
+ !(
255
342
  path.node.callee.name === 'createRoute' ||
256
343
  path.node.callee.name === 'createFileRoute'
257
- ) {
258
- if (
259
- path.parentPath.node.type === 'CallExpression'
260
- ) {
261
- const options = resolveIdentifier(
262
- path,
263
- path.parentPath.node.arguments[0],
264
- )
265
-
266
- if (t.isObjectExpression(options)) {
267
- options.properties.forEach((prop) => {
268
- if (t.isObjectProperty(prop)) {
269
- splitNodeTypes.forEach((type) => {
270
- if (t.isIdentifier(prop.key)) {
271
- if (prop.key.name === type) {
272
- splitNodesByType[type] = prop.value
273
- }
274
- }
275
- })
344
+ )
345
+ ) {
346
+ return
347
+ }
348
+
349
+ if (t.isCallExpression(path.parentPath.node)) {
350
+ const options = resolveIdentifier(
351
+ path,
352
+ path.parentPath.node.arguments[0],
353
+ )
354
+
355
+ if (t.isObjectExpression(options)) {
356
+ options.properties.forEach((prop) => {
357
+ if (t.isObjectProperty(prop)) {
358
+ splitNodeTypes.forEach((type) => {
359
+ if (t.isIdentifier(prop.key)) {
360
+ if (prop.key.name === type) {
361
+ splitNodesByType[type] = prop.value
362
+ }
276
363
  }
277
364
  })
278
-
279
- // Remove all of the options
280
- options.properties = []
281
365
  }
282
- }
366
+ })
367
+
368
+ // Remove all of the options
369
+ options.properties = []
283
370
  }
284
371
  }
285
372
  },
@@ -345,8 +432,42 @@ export async function splitFile(opts: {
345
432
  ),
346
433
  ]),
347
434
  )
435
+ } else if (t.isCallExpression(splitNode)) {
436
+ const outputSplitNodeCode = generate(splitNode).code
437
+ const splitNodeAst = babel.parse(outputSplitNodeCode)
438
+
439
+ if (!splitNodeAst) {
440
+ throw new Error(
441
+ `Failed to parse the generated code for "${splitType}" in the node type "${splitNode.type}"`,
442
+ )
443
+ }
444
+
445
+ const statement = splitNodeAst.program.body[0]
446
+
447
+ if (!statement) {
448
+ throw new Error(
449
+ `Failed to parse the generated code for "${splitType}" in the node type "${splitNode.type}" as no statement was found in the program body`,
450
+ )
451
+ }
452
+
453
+ if (t.isExpressionStatement(statement)) {
454
+ const expression = statement.expression
455
+ programPath.pushContainer(
456
+ 'body',
457
+ t.variableDeclaration('const', [
458
+ t.variableDeclarator(
459
+ t.identifier(splitType),
460
+ expression,
461
+ ),
462
+ ]),
463
+ )
464
+ } else {
465
+ throw new Error(
466
+ `Unexpected expression type encounter for "${splitType}" in the node type "${splitNode.type}"`,
467
+ )
468
+ }
348
469
  } else {
349
- console.info(splitNode)
470
+ console.info('Unexpected splitNode type:', splitNode)
350
471
  throw new Error(
351
472
  `Unexpected splitNode type ☝️: ${splitNode.type}`,
352
473
  )