@@ -13,17 +13,47 @@ const reactHooks = new Set([
13
13
'useState' ,
14
14
] ) ;
15
15
16
+ const reactNamedImports = new Set ( [
17
+ 'Children' ,
18
+ 'cloneElement' ,
19
+ 'Component' ,
20
+ 'ConcurrentMode' ,
21
+ 'createContext' ,
22
+ 'createElement' ,
23
+ 'createFactory' ,
24
+ 'forwardRef' ,
25
+ 'Fragment' ,
26
+ 'isValidElement' ,
27
+ 'lazy' ,
28
+ 'memo' ,
29
+ 'Profiler' ,
30
+ 'PureComponent' ,
31
+ 'StrictMode' ,
32
+ 'Suspense' ,
33
+ 'useCallback' ,
34
+ 'useContext' ,
35
+ 'useDebugValue' ,
36
+ 'useEffect' ,
37
+ 'useImperativeHandle' ,
38
+ 'useLayoutEffect' ,
39
+ 'useMemo' ,
40
+ 'useReducer' ,
41
+ 'useRef' ,
42
+ 'useState' ,
43
+ 'version' ,
44
+ ] ) ;
45
+
16
46
module . exports = function ( babel ) {
17
47
const { types : t } = babel ;
18
48
19
- // Collects named imports of React hooks from the "react" package
20
- function collectReactHooksAndRemoveTheirNamedImports ( path , state ) {
49
+ // Collects named imports from the "react" package
50
+ function collectAllReactImportalsAndRemoveTheirNamedImports ( path , state ) {
21
51
const node = path . node ;
22
52
const hooks = [ ] ;
23
53
if ( t . isStringLiteral ( node . source ) && node . source . value === 'react' ) {
24
54
const specifiers = path . get ( 'specifiers' ) ;
25
- if ( state . hasDefaultSpecifier === undefined ) {
26
- state . hasDefaultSpecifier = false ;
55
+ if ( state . hasDefaultOrNamespaceSpecifier === undefined ) {
56
+ state . hasDefaultOrNamespaceSpecifier = false ;
27
57
}
28
58
29
59
for ( let specifier of specifiers ) {
@@ -32,7 +62,7 @@ module.exports = function(babel) {
32
62
const localNode = specifier . node . local ;
33
63
34
64
if ( t . isIdentifier ( importedNode ) && t . isIdentifier ( localNode ) ) {
35
- if ( reactHooks . has ( importedNode . name ) ) {
65
+ if ( reactNamedImports . has ( importedNode . name ) ) {
36
66
hooks . push ( {
37
67
imported : importedNode . name ,
38
68
local : localNode . name ,
@@ -41,17 +71,33 @@ module.exports = function(babel) {
41
71
}
42
72
}
43
73
} else if ( t . isImportDefaultSpecifier ( specifier ) ) {
44
- state . hasDefaultSpecifier = true ;
74
+ const local = specifier . get ( 'local' ) ;
75
+
76
+ if ( t . isIdentifier ( local ) && local . node . name === 'React' ) {
77
+ state . hasDefaultOrNamespaceSpecifier = true ;
78
+ }
79
+ } else if ( t . isImportNamespaceSpecifier ( specifier ) ) {
80
+ const local = specifier . get ( 'local' ) ;
81
+
82
+ if ( t . isIdentifier ( local ) && local . node . name === 'React' ) {
83
+ state . hasDefaultOrNamespaceSpecifier = true ;
84
+ }
45
85
}
46
86
}
47
87
// If there is no default specifier for React, add one
48
- if ( state . hasDefaultSpecifier === false && specifiers . length > 0 ) {
88
+ if (
89
+ state . hasDefaultOrNamespaceSpecifier === false &&
90
+ specifiers . length > 0
91
+ ) {
49
92
const defaultSpecifierNode = t . importDefaultSpecifier (
50
93
t . identifier ( 'React' )
51
94
) ;
52
95
53
- path . pushContainer ( 'specifiers' , defaultSpecifierNode ) ;
54
- state . hasDefaultSpecifier = true ;
96
+ // We unshift so it goes to the beginning
97
+ path . unshiftContainer ( 'specifiers' , defaultSpecifierNode ) ;
98
+ state . hasDefaultOrNamespaceSpecifier = true ;
99
+ // Make sure we register the binding, so tracking continues to work
100
+ path . scope . registerDeclaration ( path ) ;
55
101
}
56
102
}
57
103
return hooks ;
@@ -65,7 +111,10 @@ module.exports = function(babel) {
65
111
if ( binding !== undefined ) {
66
112
const bindingPath = binding . path ;
67
113
68
- if ( t . isImportDefaultSpecifier ( bindingPath ) ) {
114
+ if (
115
+ t . isImportDefaultSpecifier ( bindingPath ) ||
116
+ t . isImportNamespaceSpecifier ( bindingPath )
117
+ ) {
69
118
const parentPath = bindingPath . parentPath ;
70
119
71
120
if (
@@ -187,6 +236,7 @@ module.exports = function(babel) {
187
236
188
237
if (
189
238
t . isImportDefaultSpecifier ( bindingPath ) ||
239
+ t . isImportNamespaceSpecifier ( bindingPath ) ||
190
240
t . isVariableDeclarator ( bindingPath )
191
241
) {
192
242
bindingPath . parentPath . insertAfter ( createElementDeclaration ) ;
@@ -205,18 +255,18 @@ module.exports = function(babel) {
205
255
// import React, {useState} from "react";
206
256
// As we collection them, we also remove the imports from the declaration.
207
257
208
- const importedHooks = collectReactHooksAndRemoveTheirNamedImports (
258
+ const reactNamedImports = collectAllReactImportalsAndRemoveTheirNamedImports (
209
259
path ,
210
260
state
211
261
) ;
212
- if ( importedHooks . length > 0 ) {
262
+ if ( reactNamedImports . length > 0 ) {
213
263
// Create a destructured variable declaration. i.e.:
214
- // const {useEffect, useState} = React;
264
+ // const {memo, useEffect, useState} = React;
215
265
// Then insert it below the import declaration node.
216
266
217
267
const declarations = t . variableDeclarator (
218
268
t . objectPattern (
219
- importedHooks . map ( ( { imported, local } ) =>
269
+ reactNamedImports . map ( ( { imported, local } ) =>
220
270
t . objectProperty (
221
271
t . identifier ( imported ) ,
222
272
t . identifier ( local ) ,
0 commit comments