@tanstack/router-cli 0.0.1-beta.69 → 1.0.1

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.
@@ -1,860 +0,0 @@
1
- import * as babel from '@babel/core'
2
- import * as t from '@babel/types'
3
- // @ts-ignore
4
- import syntaxTS from '@babel/plugin-syntax-typescript'
5
- import {
6
- IsolatedExport,
7
- removeExt,
8
- rootRouteName,
9
- rootRouteClientName,
10
- RouteNode,
11
- } from './generator'
12
- import path from 'path'
13
-
14
- export const isolatedProperties = [
15
- 'component',
16
- 'errorComponent',
17
- 'pendingComponent',
18
- ] as const
19
-
20
- export type IsolatedProperty = typeof isolatedProperties[number]
21
-
22
- type Opts = {
23
- isolate: IsolatedProperty
24
- }
25
-
26
- const getBasePlugins = () => [
27
- [
28
- syntaxTS,
29
- {
30
- isTSX: true,
31
- },
32
- ],
33
- ]
34
-
35
- export async function ensureBoilerplate(node: RouteNode, code: string) {
36
- const relativeImportPath = path.relative(node.fullDir, node.genPathNoExt)
37
-
38
- const originalFile = await babel.transformAsync(code, {
39
- configFile: false,
40
- babelrc: false,
41
- plugins: [...getBasePlugins()],
42
- })
43
-
44
- const file = await babel.transformAsync(code, {
45
- configFile: false,
46
- babelrc: false,
47
- plugins: [
48
- ...getBasePlugins(),
49
- {
50
- visitor: {
51
- Program: {
52
- enter(programPath) {
53
- // Remove all properties except for our isolated one
54
- if (node.isRoot) {
55
- let foundImport = false
56
-
57
- programPath.traverse({
58
- ImportSpecifier(importPath) {
59
- if (
60
- t.isIdentifier(importPath.node.imported) &&
61
- importPath.node.imported.name === 'Route'
62
- ) {
63
- foundImport = true
64
- }
65
- },
66
- })
67
-
68
- if (!foundImport) {
69
- programPath.node.body.unshift(
70
- babel.template.statement(
71
- `import { Route } from '@tanstack/react-router'`,
72
- )(),
73
- )
74
- }
75
- } else {
76
- let foundImport = false
77
- programPath.traverse({
78
- ImportSpecifier(importPath) {
79
- if (
80
- t.isIdentifier(importPath.node.imported) &&
81
- importPath.node.imported.name === 'route'
82
- ) {
83
- foundImport = true
84
- if (t.isImportDeclaration(importPath.parentPath.node)) {
85
- if (
86
- importPath.parentPath.node.source.value !==
87
- relativeImportPath
88
- ) {
89
- importPath.parentPath.node.source.value =
90
- relativeImportPath
91
- }
92
- }
93
- }
94
- },
95
- })
96
- if (!foundImport) {
97
- programPath.node.body.unshift(
98
- babel.template.statement(
99
- `import { Route } from '${relativeImportPath.replace(
100
- /\\/gi,
101
- '/',
102
- )}'`,
103
- )(),
104
- )
105
- }
106
- }
107
- },
108
- },
109
- },
110
- },
111
- ],
112
- })
113
-
114
- const separator = 'new Route('
115
-
116
- if (!originalFile?.code) {
117
- return `${file?.code}\n\n${separator}{\n\n})`
118
- }
119
-
120
- const originalHead = originalFile?.code?.substring(
121
- 0,
122
- originalFile?.code?.indexOf(separator),
123
- )
124
-
125
- const generatedHead = file?.code?.substring(0, file?.code?.indexOf(separator))
126
-
127
- if (originalHead !== generatedHead) {
128
- return `${generatedHead}\n\n${originalFile?.code?.substring(
129
- originalFile?.code?.indexOf(separator),
130
- )}`
131
- }
132
-
133
- return
134
- }
135
-
136
- export async function isolateOptionToExport(
137
- node: RouteNode,
138
- code: string,
139
- opts: Opts,
140
- ) {
141
- return (
142
- await babel.transformAsync(code, {
143
- configFile: false,
144
- babelrc: false,
145
- plugins: [...getBasePlugins(), plugin()],
146
- ast: true,
147
- })
148
- )?.code
149
-
150
- function plugin(): babel.PluginItem {
151
- return {
152
- visitor: {
153
- Program: {
154
- enter(programPath, state) {
155
- // If we're the root, handle things a bit differently
156
- if (node.isRoot) {
157
- programPath.traverse({
158
- Identifier(path) {
159
- if (
160
- path.node.name === 'Route' &&
161
- t.isCallExpression(path.parentPath.node)
162
- ) {
163
- const options = getRouteOptions(path)
164
-
165
- if (options) {
166
- const property = options.properties.find((property) => {
167
- return (
168
- t.isObjectProperty(property) &&
169
- t.isIdentifier(property.key) &&
170
- property.key.name === opts.isolate
171
- )
172
- })
173
-
174
- if (t.isObjectProperty(property)) {
175
- const program = path.findParent((d) => d.isProgram())
176
-
177
- if (program?.isProgram()) {
178
- program.node.body.push(
179
- babel.template.statement(
180
- `export const ${opts.isolate} = $VAR`,
181
- )({
182
- $VAR: property.value,
183
- }),
184
- )
185
- }
186
- }
187
-
188
- path
189
- .findParent((d) => d.isExpressionStatement())
190
- ?.remove()
191
- }
192
- }
193
- },
194
- })
195
- }
196
-
197
- // We're not in the root, handle things normally
198
- if (!node.isRoot) {
199
- // Remove all properties except for our isolated one
200
- programPath.traverse({
201
- Identifier(path) {
202
- if (path.node.name === 'generate') {
203
- const options = getRouteConfigGenerateOptions(path)
204
-
205
- if (options) {
206
- const property = options.properties.find((property) => {
207
- return (
208
- t.isObjectProperty(property) &&
209
- t.isIdentifier(property.key) &&
210
- property.key.name === opts.isolate
211
- )
212
- })
213
-
214
- if (
215
- t.isObjectProperty(property) &&
216
- t.isIdentifier(property.key)
217
- ) {
218
- if (property.key.name === opts.isolate) {
219
- const program = path.findParent((d) => d.isProgram())
220
-
221
- if (program?.isProgram()) {
222
- program.node.body.push(
223
- babel.template.statement(
224
- `export const ${opts.isolate} = $VAR`,
225
- )({
226
- $VAR: property.value,
227
- }),
228
- )
229
- }
230
- }
231
- }
232
-
233
- path
234
- .findParent((d) => d.isExpressionStatement())
235
- ?.remove()
236
- }
237
- }
238
- },
239
- })
240
- }
241
-
242
- cleanUnusedCode(programPath, state, [opts.isolate])
243
- },
244
- },
245
- },
246
- }
247
- }
248
- }
249
-
250
- export async function detectExports(code: string) {
251
- let exported: string[] = []
252
-
253
- // try {
254
- await babel.transformAsync(code, {
255
- configFile: false,
256
- babelrc: false,
257
- plugins: [
258
- ...getBasePlugins(),
259
- {
260
- visitor: {
261
- ExportNamedDeclaration(path) {
262
- if (t.isVariableDeclaration(path.node.declaration)) {
263
- if (
264
- t.isVariableDeclarator(path.node.declaration.declarations?.[0])
265
- ) {
266
- if (t.isIdentifier(path.node.declaration.declarations[0].id)) {
267
- exported.push(path.node.declaration.declarations[0].id.name)
268
- }
269
- }
270
- }
271
- },
272
- },
273
- },
274
- ],
275
- ast: true,
276
- })
277
-
278
- return exported
279
- }
280
-
281
- export async function generateRouteConfig(
282
- node: RouteNode,
283
- routeCode: string,
284
- imports: IsolatedExport[],
285
- clientOnly: boolean,
286
- ) {
287
- const relativeParentRoutePath = clientOnly
288
- ? node.parent
289
- ? removeExt(
290
- path.relative(
291
- node.genDir,
292
- path.resolve(node.parent?.genDir, node.parent?.clientFilename),
293
- ),
294
- )
295
- : `./${rootRouteClientName}`
296
- : node.parent
297
- ? removeExt(
298
- path.relative(
299
- node.genDir,
300
- path.resolve(node.parent?.genDir, node.parent?.filename),
301
- ),
302
- )
303
- : `./${rootRouteName}`
304
-
305
- const pathName = node.isRoot
306
- ? undefined
307
- : node.fileNameNoExt.startsWith('__')
308
- ? undefined
309
- : node.fileNameNoExt === 'index'
310
- ? '/'
311
- : node.fileNameNoExt
312
-
313
- const routeId = node.isRoot ? undefined : node.fileNameNoExt
314
-
315
- function plugin(): babel.PluginItem {
316
- return {
317
- visitor: {
318
- Program: {
319
- enter(programPath, state) {
320
- // Remove all of the isolated import properties from the config
321
- programPath.traverse({
322
- ImportSpecifier(path) {
323
- if (t.isIdentifier(path.node.imported)) {
324
- if (!node.isRoot) {
325
- if (path.node.imported.name === 'route') {
326
- path.parentPath.remove()
327
-
328
- const program = path.findParent((d) => d.isProgram())
329
-
330
- if (program?.isProgram()) {
331
- program.node.body.unshift(
332
- babel.template.statement(
333
- `import { route as parentRoute } from '$IMPORT'`,
334
- )({
335
- $IMPORT: relativeParentRoutePath,
336
- }),
337
- )
338
- }
339
- }
340
- }
341
- }
342
- },
343
- Identifier(iPath) {
344
- let options
345
-
346
- if (node.isRoot) {
347
- if (iPath.node.name === 'Route') {
348
- if (t.isCallExpression(iPath.parentPath.node)) {
349
- if (
350
- t.isExpressionStatement(
351
- iPath.parentPath.parentPath?.node,
352
- )
353
- ) {
354
- iPath.parentPath.parentPath?.replaceWith(
355
- t.variableDeclaration('const', [
356
- t.variableDeclarator(
357
- t.identifier('route'),
358
- iPath.parentPath.node,
359
- ),
360
- ]) as any,
361
- )
362
- }
363
- }
364
- }
365
- } else {
366
- if (iPath.node.name === 'generate') {
367
- if (t.isMemberExpression(iPath.parentPath.node)) {
368
- if (t.isIdentifier(iPath.parentPath.node.object)) {
369
- iPath.node.name = 'Route'
370
- iPath.parentPath.node.object.name = 'parentRoute'
371
-
372
- options = getRouteConfigGenerateOptions(iPath)
373
- }
374
- }
375
- }
376
- }
377
-
378
- if (options) {
379
- options.properties = [
380
- ...(pathName
381
- ? [
382
- t.objectProperty(
383
- t.identifier('path'),
384
- t.stringLiteral(pathName),
385
- ),
386
- ]
387
- : routeId
388
- ? [
389
- t.objectProperty(
390
- t.identifier('id'),
391
- t.stringLiteral(routeId),
392
- ),
393
- ]
394
- : []),
395
- ...options.properties.map((property) => {
396
- if (
397
- t.isObjectProperty(property) &&
398
- t.isIdentifier(property.key) &&
399
- isolatedProperties.includes(
400
- property.key.name as IsolatedProperty,
401
- )
402
- ) {
403
- const key = property.key.name
404
-
405
- if (key === 'loader') {
406
- if (clientOnly) {
407
- return t.objectProperty(
408
- t.identifier('loader'),
409
- t.tSAsExpression(
410
- t.booleanLiteral(true),
411
- t.tsAnyKeyword(),
412
- ),
413
- )
414
- }
415
- return t.objectProperty(
416
- t.identifier(key),
417
- babel.template.expression(
418
- `(...args) => import('./${path.relative(
419
- node.genDir,
420
- node.genPathNoExt,
421
- )}-${key}').then(d => d.${key}.apply(d.${key}, (args as any)))`,
422
- {
423
- plugins: ['typescript'],
424
- },
425
- )({}),
426
- )
427
- }
428
-
429
- if (key === 'action') {
430
- if (clientOnly) {
431
- return t.objectProperty(
432
- t.identifier('action'),
433
- t.tSAsExpression(
434
- t.booleanLiteral(true),
435
- t.tSAnyKeyword(),
436
- ),
437
- )
438
- }
439
- return t.objectProperty(
440
- t.identifier(key),
441
- babel.template.expression(
442
- `(...payload: Parameters<typeof import('./${path.relative(
443
- node.genDir,
444
- node.genPathNoExt,
445
- )}-${key}').action>) => import('./${path.relative(
446
- node.genDir,
447
- node.genPathNoExt,
448
- )}-${key}').then(d => d.${key}.apply(d.${key}, (payload as any)))`,
449
- {
450
- plugins: ['typescript'],
451
- },
452
- )({}),
453
- )
454
- }
455
-
456
- if (clientOnly) {
457
- return t.objectProperty(
458
- t.identifier(key),
459
- babel.template.expression(`
460
- lazy(() => import('./${path.relative(
461
- node.genDir,
462
- node.genPathNoExt,
463
- )}-${key}').then(d => ({ default: d.${key} }) ))`)(),
464
- )
465
- }
466
-
467
- return t.objectProperty(
468
- t.identifier(key),
469
- property.value as any,
470
- )
471
- }
472
-
473
- return property
474
- }),
475
- ]
476
-
477
- const program = iPath.findParent((d) => d.isProgram())
478
-
479
- if (program?.isProgram() && options) {
480
- const index = program.node.body.findIndex(
481
- (d) =>
482
- d.start === iPath.parentPath.parentPath?.node.start,
483
- )
484
-
485
- if (node.isRoot) {
486
- program.node.body[index] = babel.template.statement(
487
- `const route = new Route(
488
- $OPTIONS
489
- )`,
490
- )({
491
- $OPTIONS: options,
492
- })
493
- } else {
494
- program.node.body[index] = babel.template.statement(
495
- `const route = new Route(
496
- $OPTIONS
497
- )`,
498
- )({
499
- $OPTIONS: options,
500
- })
501
- }
502
- }
503
- }
504
- },
505
- })
506
-
507
- programPath.node.body.unshift(
508
- babel.template.statement(
509
- `import { lazy } from '@tanstack/react-router'`,
510
- )(),
511
- )
512
-
513
- // Add the route exports
514
- programPath.node.body.push(
515
- babel.template.statement(
516
- clientOnly
517
- ? `export { route, route as ${node.variable}Route }`
518
- : `export { route }`,
519
- )(),
520
- )
521
-
522
- cleanUnusedCode(programPath, state, [
523
- 'route',
524
- `${node.variable}Route`,
525
- ])
526
- },
527
- },
528
- },
529
- }
530
- }
531
-
532
- const code = (
533
- await babel.transformAsync(routeCode, {
534
- configFile: false,
535
- babelrc: false,
536
- plugins: [...getBasePlugins(), plugin()],
537
- ast: true,
538
- })
539
- )?.code
540
-
541
- if (!code) {
542
- throw new Error('Error while generating a route file!')
543
- }
544
-
545
- return code
546
- }
547
-
548
- function getIdentifier(path: any) {
549
- const parentPath = path.parentPath
550
- if (parentPath.type === 'VariableDeclarator') {
551
- const pp = parentPath
552
- const name = pp.get('id')
553
- return name.node.type === 'Identifier' ? name : null
554
- }
555
- if (parentPath.type === 'AssignmentExpression') {
556
- const pp = parentPath
557
- const name = pp.get('left')
558
- return name.node.type === 'Identifier' ? name : null
559
- }
560
- if (path.node.type === 'ArrowFunctionExpression') {
561
- return null
562
- }
563
- return path.node.id && path.node.id.type === 'Identifier'
564
- ? path.get('id')
565
- : null
566
- }
567
-
568
- function isIdentifierReferenced(ident: any) {
569
- const b = ident.scope.getBinding(ident.node.name)
570
- if (b && b.referenced) {
571
- if (b.path.type === 'FunctionDeclaration') {
572
- return !b.constantViolations
573
- .concat(b.referencePaths)
574
- .every((ref: any) => ref.findParent((p: any) => p === b.path))
575
- }
576
- return true
577
- }
578
- return false
579
- }
580
- function markFunction(path: any, state: any) {
581
- const ident = getIdentifier(path)
582
- if (ident && ident.node && isIdentifierReferenced(ident)) {
583
- state.refs.add(ident)
584
- }
585
- }
586
- function markImport(path: any, state: any) {
587
- const local = path.get('local')
588
- if (isIdentifierReferenced(local)) {
589
- state.refs.add(local)
590
- }
591
- }
592
-
593
- function getRouteConfigGenerateOptions(path: any): t.ObjectExpression | void {
594
- const tryOptions = (node: any): t.ObjectExpression | void => {
595
- if (t.isIdentifier(node)) {
596
- const initNode = path.scope.getBinding(node.name)?.path.node
597
- if (t.isVariableDeclarator(initNode)) {
598
- return tryOptions(initNode.init)
599
- }
600
- } else if (t.isObjectExpression(node)) {
601
- return node
602
- }
603
-
604
- return
605
- }
606
-
607
- if (
608
- t.isMemberExpression(path.parentPath.node) &&
609
- t.isCallExpression(path.parentPath.parentPath?.node)
610
- ) {
611
- const options = path.parentPath.parentPath?.node.arguments[0]
612
-
613
- return tryOptions(options)
614
- }
615
- }
616
-
617
- function getRouteOptions(path: any): t.ObjectExpression | void {
618
- const tryOptions = (node: any): t.ObjectExpression | void => {
619
- if (t.isIdentifier(node)) {
620
- const initNode = path.scope.getBinding(node.name)?.path.node
621
- if (t.isVariableDeclarator(initNode)) {
622
- return tryOptions(initNode.init)
623
- }
624
- } else if (t.isObjectExpression(node)) {
625
- return node
626
- }
627
-
628
- return
629
- }
630
-
631
- if (t.isCallExpression(path.parentPath?.node)) {
632
- const options = path.parentPath?.node.arguments[0]
633
-
634
- return tryOptions(options)
635
- }
636
- }
637
-
638
- // All credit for this amazing function goes to the Next.js team
639
- // (and the Solid.js team for their derivative work).
640
- // https://github.com/vercel/next.js/blob/canary/packages/next/build/babel/plugins/next-ssg-transform.ts
641
- // https://github.com/solidjs/solid-start/blob/main/packages/start/server/routeData.js
642
-
643
- function cleanUnusedCode(
644
- programPath: babel.NodePath<babel.types.Program>,
645
- state: any,
646
- keepExports: string[] = [],
647
- ) {
648
- state.refs = new Set()
649
- state.done = false
650
-
651
- function markVariable(variablePath: any, variableState: any) {
652
- if (variablePath.node.id.type === 'Identifier') {
653
- const local = variablePath.get('id')
654
- if (isIdentifierReferenced(local)) {
655
- variableState.refs.add(local)
656
- }
657
- } else if (variablePath.node.id.type === 'ObjectPattern') {
658
- const pattern = variablePath.get('id')
659
- const properties: any = pattern.get('properties')
660
- properties.forEach((p: any) => {
661
- const local = p.get(
662
- p.node.type === 'ObjectProperty'
663
- ? 'value'
664
- : p.node.type === 'RestElement'
665
- ? 'argument'
666
- : (function () {
667
- throw new Error('invariant')
668
- })(),
669
- )
670
- if (isIdentifierReferenced(local)) {
671
- variableState.refs.add(local)
672
- }
673
- })
674
- } else if (variablePath.node.id.type === 'ArrayPattern') {
675
- const pattern = variablePath.get('id')
676
- const elements: any = pattern.get('elements')
677
- elements.forEach((e: any) => {
678
- let local
679
- if (e.node && e.node.type === 'Identifier') {
680
- local = e
681
- } else if (e.node && e.node.type === 'RestElement') {
682
- local = e.get('argument')
683
- } else {
684
- return
685
- }
686
- if (isIdentifierReferenced(local)) {
687
- variableState.refs.add(local)
688
- }
689
- })
690
- }
691
- }
692
-
693
- // Mark all variables and functions if used
694
- programPath.traverse(
695
- {
696
- VariableDeclarator: markVariable,
697
- FunctionDeclaration: markFunction,
698
- FunctionExpression: markFunction,
699
- ArrowFunctionExpression: markFunction,
700
- ImportSpecifier: markImport,
701
- ImportDefaultSpecifier: markImport,
702
- ImportNamespaceSpecifier: markImport,
703
- ExportDefaultDeclaration: markImport,
704
- // ExportNamedDeclaration(path, state) {
705
- // if (t.isVariableDeclaration(path.node.declaration)) {
706
- // if (t.isVariableDeclarator(path.node.declaration.declarations?.[0])) {
707
- // if (t.isIdentifier(path.node.declaration.declarations[0].id)) {
708
- // if (
709
- // keepExports.includes(
710
- // path.node.declaration.declarations[0].id.name,
711
- // )
712
- // ) {
713
- // return
714
- // }
715
- // }
716
- // path.replaceWith(path.node.declaration.declarations[0])
717
- // return
718
- // }
719
- // }
720
- // path.remove()
721
- // },
722
- ImportDeclaration: (path) => {
723
- if (path.node.source.value.endsWith('.css')) {
724
- path.remove()
725
- }
726
- },
727
- },
728
- state,
729
- )
730
-
731
- // Sweet all of the remaining references and remove unused ones
732
- const refs: any = state.refs
733
- let count: number
734
- function sweepFunction(sweepPath: any) {
735
- const ident = getIdentifier(sweepPath)
736
- if (
737
- ident &&
738
- ident.node &&
739
- refs.has(ident) &&
740
- !isIdentifierReferenced(ident)
741
- ) {
742
- ++count
743
- if (
744
- t.isAssignmentExpression(sweepPath.parentPath) ||
745
- t.isVariableDeclarator(sweepPath.parentPath)
746
- ) {
747
- sweepPath.parentPath.remove()
748
- } else {
749
- sweepPath.remove()
750
- }
751
- }
752
- }
753
- function sweepImport(sweepPath: any) {
754
- const local = sweepPath.get('local')
755
- if (refs.has(local) && !isIdentifierReferenced(local)) {
756
- ++count
757
- sweepPath.remove()
758
- if (sweepPath.parent.specifiers.length === 0) {
759
- sweepPath.parentPath.remove()
760
- }
761
- }
762
- }
763
- do {
764
- programPath.scope.crawl()
765
- count = 0
766
- programPath.traverse({
767
- VariableDeclarator(variablePath) {
768
- if (variablePath.node.id.type === 'Identifier') {
769
- const local = variablePath.get('id')
770
- if (refs.has(local) && !isIdentifierReferenced(local)) {
771
- ++count
772
-
773
- variablePath.remove()
774
- }
775
- } else if (variablePath.node.id.type === 'ObjectPattern') {
776
- const pattern = variablePath.get('id')
777
- const beforeCount = count
778
- const properties: any = pattern.get('properties')
779
- properties.forEach((p: any) => {
780
- const local = p.get(
781
- p.node.type === 'ObjectProperty'
782
- ? 'value'
783
- : p.node.type === 'RestElement'
784
- ? 'argument'
785
- : (function () {
786
- throw new Error('invariant')
787
- })(),
788
- )
789
- if (refs.has(local) && !isIdentifierReferenced(local)) {
790
- ++count
791
- p.remove()
792
- }
793
- })
794
- if (
795
- beforeCount !== count &&
796
- (pattern.get('properties') as any).length < 1
797
- ) {
798
- variablePath.remove()
799
- }
800
- } else if (variablePath.node.id.type === 'ArrayPattern') {
801
- const pattern = variablePath.get('id')
802
- const beforeCount = count
803
- const elements: any = pattern.get('elements')
804
- elements.forEach((e: any) => {
805
- let local
806
- if (e.node && e.node.type === 'Identifier') {
807
- local = e
808
- } else if (e.node && e.node.type === 'RestElement') {
809
- local = e.get('argument')
810
- } else {
811
- return
812
- }
813
- if (refs.has(local) && !isIdentifierReferenced(local)) {
814
- ++count
815
-
816
- e.remove()
817
- }
818
- })
819
- if (
820
- beforeCount !== count &&
821
- (pattern.get('elements') as any).length < 1
822
- ) {
823
- variablePath.remove()
824
- }
825
- }
826
- },
827
- FunctionDeclaration: sweepFunction,
828
- FunctionExpression: sweepFunction,
829
- ArrowFunctionExpression: sweepFunction,
830
- ImportSpecifier: sweepImport,
831
- ImportDefaultSpecifier: sweepImport,
832
- ImportNamespaceSpecifier: sweepImport,
833
- })
834
- } while (count)
835
-
836
- // Do we need the * import for react?
837
- let hasReact = false
838
-
839
- // Mark react elements as having react
840
- programPath.traverse({
841
- JSXElement(path) {
842
- hasReact = true
843
- },
844
- })
845
-
846
- if (!hasReact) {
847
- // Mark all variables and functions if used
848
- programPath.traverse({
849
- ImportDeclaration(path) {
850
- if (
851
- t.isStringLiteral(path.node.source) &&
852
- path.node.source.value === 'react' &&
853
- t.isImportNamespaceSpecifier(path.node.specifiers[0])
854
- ) {
855
- path.remove()
856
- }
857
- },
858
- })
859
- }
860
- }