@@ -13,17 +13,47 @@ const reactHooks = new Set([
1313 'useState' ,
1414] ) ;
1515
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+
1646module . exports = function ( babel ) {
1747 const { types : t } = babel ;
1848
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 ) {
2151 const node = path . node ;
2252 const hooks = [ ] ;
2353 if ( t . isStringLiteral ( node . source ) && node . source . value === 'react' ) {
2454 const specifiers = path . get ( 'specifiers' ) ;
25- if ( state . hasDefaultSpecifier === undefined ) {
26- state . hasDefaultSpecifier = false ;
55+ if ( state . hasDefaultOrNamespaceSpecifier === undefined ) {
56+ state . hasDefaultOrNamespaceSpecifier = false ;
2757 }
2858
2959 for ( let specifier of specifiers ) {
@@ -32,7 +62,7 @@ module.exports = function(babel) {
3262 const localNode = specifier . node . local ;
3363
3464 if ( t . isIdentifier ( importedNode ) && t . isIdentifier ( localNode ) ) {
35- if ( reactHooks . has ( importedNode . name ) ) {
65+ if ( reactNamedImports . has ( importedNode . name ) ) {
3666 hooks . push ( {
3767 imported : importedNode . name ,
3868 local : localNode . name ,
@@ -41,17 +71,33 @@ module.exports = function(babel) {
4171 }
4272 }
4373 } 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+ }
4585 }
4686 }
4787 // 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+ ) {
4992 const defaultSpecifierNode = t . importDefaultSpecifier (
5093 t . identifier ( 'React' )
5194 ) ;
5295
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 ) ;
55101 }
56102 }
57103 return hooks ;
@@ -65,7 +111,10 @@ module.exports = function(babel) {
65111 if ( binding !== undefined ) {
66112 const bindingPath = binding . path ;
67113
68- if ( t . isImportDefaultSpecifier ( bindingPath ) ) {
114+ if (
115+ t . isImportDefaultSpecifier ( bindingPath ) ||
116+ t . isImportNamespaceSpecifier ( bindingPath )
117+ ) {
69118 const parentPath = bindingPath . parentPath ;
70119
71120 if (
@@ -187,6 +236,7 @@ module.exports = function(babel) {
187236
188237 if (
189238 t . isImportDefaultSpecifier ( bindingPath ) ||
239+ t . isImportNamespaceSpecifier ( bindingPath ) ||
190240 t . isVariableDeclarator ( bindingPath )
191241 ) {
192242 bindingPath . parentPath . insertAfter ( createElementDeclaration ) ;
@@ -205,18 +255,18 @@ module.exports = function(babel) {
205255 // import React, {useState} from "react";
206256 // As we collection them, we also remove the imports from the declaration.
207257
208- const importedHooks = collectReactHooksAndRemoveTheirNamedImports (
258+ const reactNamedImports = collectAllReactImportalsAndRemoveTheirNamedImports (
209259 path ,
210260 state
211261 ) ;
212- if ( importedHooks . length > 0 ) {
262+ if ( reactNamedImports . length > 0 ) {
213263 // Create a destructured variable declaration. i.e.:
214- // const {useEffect, useState} = React;
264+ // const {memo, useEffect, useState} = React;
215265 // Then insert it below the import declaration node.
216266
217267 const declarations = t . variableDeclarator (
218268 t . objectPattern (
219- importedHooks . map ( ( { imported, local } ) =>
269+ reactNamedImports . map ( ( { imported, local } ) =>
220270 t . objectProperty (
221271 t . identifier ( imported ) ,
222272 t . identifier ( local ) ,
0 commit comments