@@ -18,10 +18,11 @@ import path from 'path';
18
18
import type { T , BabelAPI , PluginObj } from 'playwright/src/transform/babelBundle' ;
19
19
import { types , declare , traverse } from 'playwright/lib/transform/babelBundle' ;
20
20
import { resolveImportSpecifierExtension } from 'playwright/lib/util' ;
21
+ import { setTransformData } from 'playwright/lib/transform/transform' ;
21
22
const t : typeof T = types ;
22
23
23
- let componentNames : Set < string > ;
24
- let componentImports : Map < string , ImportInfo > ;
24
+ let jsxComponentNames : Set < string > ;
25
+ let importInfos : Map < string , ImportInfo > ;
25
26
26
27
export default declare ( ( api : BabelAPI ) => {
27
28
api . assertVersion ( 7 ) ;
@@ -31,9 +32,8 @@ export default declare((api: BabelAPI) => {
31
32
visitor : {
32
33
Program : {
33
34
enter ( path ) {
34
- const result = collectComponentUsages ( path . node ) ;
35
- componentNames = result . names ;
36
- componentImports = new Map ( ) ;
35
+ jsxComponentNames = collectJsxComponentUsages ( path . node ) ;
36
+ importInfos = new Map ( ) ;
37
37
} ,
38
38
exit ( path ) {
39
39
let firstDeclaration : any ;
@@ -47,13 +47,14 @@ export default declare((api: BabelAPI) => {
47
47
const insertionPath = lastImportDeclaration || firstDeclaration ;
48
48
if ( ! insertionPath )
49
49
return ;
50
- for ( const componentImport of [ ...componentImports . values ( ) ] . reverse ( ) ) {
50
+
51
+ for ( const [ localName , componentImport ] of [ ...importInfos . entries ( ) ] . reverse ( ) ) {
51
52
insertionPath . insertAfter (
52
53
t . variableDeclaration (
53
54
'const' ,
54
55
[
55
56
t . variableDeclarator (
56
- t . identifier ( componentImport . localName ) ,
57
+ t . identifier ( localName ) ,
57
58
t . objectExpression ( [
58
59
t . objectProperty ( t . identifier ( '__pw_type' ) , t . stringLiteral ( 'importRef' ) ) ,
59
60
t . objectProperty ( t . identifier ( 'id' ) , t . stringLiteral ( componentImport . id ) ) ,
@@ -63,6 +64,7 @@ export default declare((api: BabelAPI) => {
63
64
)
64
65
) ;
65
66
}
67
+ setTransformData ( 'playwright-ct-core' , [ ...importInfos . values ( ) ] ) ;
66
68
}
67
69
} ,
68
70
@@ -71,19 +73,35 @@ export default declare((api: BabelAPI) => {
71
73
if ( ! t . isStringLiteral ( importNode . source ) )
72
74
return ;
73
75
74
- let components = 0 ;
76
+ const ext = path . extname ( importNode . source . value ) ;
77
+
78
+ // Convert all non-JS imports into refs.
79
+ if ( ! allJsExtensions . has ( ext ) ) {
80
+ for ( const specifier of importNode . specifiers ) {
81
+ if ( t . isImportNamespaceSpecifier ( specifier ) )
82
+ continue ;
83
+ const { localName, info } = importInfo ( importNode , specifier , this . filename ! ) ;
84
+ importInfos . set ( localName , info ) ;
85
+ }
86
+ p . skip ( ) ;
87
+ p . remove ( ) ;
88
+ return ;
89
+ }
90
+
91
+ // Convert JS imports that are used as components in JSX expressions into refs.
92
+ let importCount = 0 ;
75
93
for ( const specifier of importNode . specifiers ) {
76
94
if ( t . isImportNamespaceSpecifier ( specifier ) )
77
95
continue ;
78
- const info = importInfo ( importNode , specifier , this . filename ! ) ;
79
- if ( ! componentNames . has ( info . localName ) )
80
- continue ;
81
- componentImports . set ( info . localName , info ) ;
82
- ++ components ;
96
+ const { localName , info } = importInfo ( importNode , specifier , this . filename ! ) ;
97
+ if ( jsxComponentNames . has ( localName ) ) {
98
+ importInfos . set ( localName , info ) ;
99
+ ++ importCount ;
100
+ }
83
101
}
84
102
85
- // All the imports were components => delete.
86
- if ( components && components === importNode . specifiers . length ) {
103
+ // All the imports were from JSX => delete.
104
+ if ( importCount && importCount === importNode . specifiers . length ) {
87
105
p . skip ( ) ;
88
106
p . remove ( ) ;
89
107
}
@@ -92,7 +110,7 @@ export default declare((api: BabelAPI) => {
92
110
MemberExpression ( path ) {
93
111
if ( ! t . isIdentifier ( path . node . object ) )
94
112
return ;
95
- if ( ! componentImports . has ( path . node . object . name ) )
113
+ if ( ! importInfos . has ( path . node . object . name ) )
96
114
return ;
97
115
if ( ! t . isIdentifier ( path . node . property ) )
98
116
return ;
@@ -108,56 +126,30 @@ export default declare((api: BabelAPI) => {
108
126
return result ;
109
127
} ) ;
110
128
111
- export function collectComponentUsages ( node : T . Node ) {
112
- const importedLocalNames = new Set < string > ( ) ;
129
+ function collectJsxComponentUsages ( node : T . Node ) : Set < string > {
113
130
const names = new Set < string > ( ) ;
114
131
traverse ( node , {
115
132
enter : p => {
116
-
117
- // First look at all the imports.
118
- if ( t . isImportDeclaration ( p . node ) ) {
119
- const importNode = p . node ;
120
- if ( ! t . isStringLiteral ( importNode . source ) )
121
- return ;
122
-
123
- for ( const specifier of importNode . specifiers ) {
124
- if ( t . isImportNamespaceSpecifier ( specifier ) )
125
- continue ;
126
- importedLocalNames . add ( specifier . local . name ) ;
127
- }
128
- }
129
-
130
133
// Treat JSX-everything as component usages.
131
134
if ( t . isJSXElement ( p . node ) ) {
132
135
if ( t . isJSXIdentifier ( p . node . openingElement . name ) )
133
136
names . add ( p . node . openingElement . name . name ) ;
134
137
if ( t . isJSXMemberExpression ( p . node . openingElement . name ) && t . isJSXIdentifier ( p . node . openingElement . name . object ) && t . isJSXIdentifier ( p . node . openingElement . name . property ) )
135
138
names . add ( p . node . openingElement . name . object . name ) ;
136
139
}
137
-
138
- // Treat mount(identifier, ...) as component usage if it is in the importedLocalNames list.
139
- if ( t . isAwaitExpression ( p . node ) && t . isCallExpression ( p . node . argument ) && t . isIdentifier ( p . node . argument . callee ) && p . node . argument . callee . name === 'mount' ) {
140
- const callExpression = p . node . argument ;
141
- const arg = callExpression . arguments [ 0 ] ;
142
- if ( ! t . isIdentifier ( arg ) || ! importedLocalNames . has ( arg . name ) )
143
- return ;
144
-
145
- names . add ( arg . name ) ;
146
- }
147
140
}
148
141
} ) ;
149
- return { names } ;
142
+ return names ;
150
143
}
151
144
152
145
export type ImportInfo = {
153
146
id : string ;
154
147
isModuleOrAlias : boolean ;
155
148
importPath : string ;
156
- localName : string ;
157
149
remoteName : string | undefined ;
158
150
} ;
159
151
160
- export function importInfo ( importNode : T . ImportDeclaration , specifier : T . ImportSpecifier | T . ImportDefaultSpecifier , filename : string ) : ImportInfo {
152
+ export function importInfo ( importNode : T . ImportDeclaration , specifier : T . ImportSpecifier | T . ImportDefaultSpecifier , filename : string ) : { localName : string , info : ImportInfo } {
161
153
const importSource = importNode . source . value ;
162
154
const isModuleOrAlias = ! importSource . startsWith ( '.' ) ;
163
155
const unresolvedImportPath = path . resolve ( path . dirname ( filename ) , importSource ) ;
@@ -171,7 +163,6 @@ export function importInfo(importNode: T.ImportDeclaration, specifier: T.ImportS
171
163
id : idPrefix ,
172
164
importPath,
173
165
isModuleOrAlias,
174
- localName : specifier . local . name ,
175
166
remoteName : undefined ,
176
167
} ;
177
168
@@ -184,5 +175,7 @@ export function importInfo(importNode: T.ImportDeclaration, specifier: T.ImportS
184
175
185
176
if ( result . remoteName )
186
177
result . id += '_' + result . remoteName ;
187
- return result ;
178
+ return { localName : specifier . local . name , info : result } ;
188
179
}
180
+
181
+ const allJsExtensions = new Set ( [ '.js' , '.jsx' , '.cjs' , '.mjs' , '.ts' , '.tsx' , '.cts' , '.mts' , '' ] ) ;
0 commit comments