@@ -5,10 +5,13 @@ module.exports = buildJsx
5
5
var walk = require ( 'estree-walker' ) . walk
6
6
var isIdentifierName = require ( 'estree-util-is-identifier-name' ) . name
7
7
8
+ var regex = / @ ( j s x | j s x F r a g | j s x I m p o r t S o u r c e | j s x R u n t i m e ) \s + ( \S + ) / g
9
+
8
10
function buildJsx ( tree , options ) {
9
11
var settings = options || { }
10
- var pragma = settings . pragma
11
- var pragmaFrag = settings . pragmaFrag
12
+ var automatic = settings . runtime === 'automatic'
13
+ var annotations = { }
14
+ var imports = { }
12
15
13
16
walk ( tree , { enter : enter , leave : leave } )
14
17
@@ -26,12 +29,36 @@ function buildJsx(tree, options) {
26
29
index = - 1
27
30
28
31
while ( ++ index < comments . length ) {
29
- if ( ( match = / @ j s x \s + ( \S + ) / . exec ( comments [ index ] . value ) ) ) {
30
- pragma = match [ 1 ]
32
+ regex . lastIndex = 0
33
+
34
+ while ( ( match = regex . exec ( comments [ index ] . value ) ) ) {
35
+ annotations [ match [ 1 ] ] = match [ 2 ]
31
36
}
37
+ }
38
+
39
+ if ( annotations . jsxRuntime ) {
40
+ if ( annotations . jsxRuntime === 'automatic' ) {
41
+ automatic = true
42
+
43
+ if ( annotations . jsx ) {
44
+ throw new Error ( 'Unexpected `@jsx` pragma w/ automatic runtime' )
45
+ }
32
46
33
- if ( ( match = / @ j s x F r a g \s + ( \S + ) / . exec ( comments [ index ] . value ) ) ) {
34
- pragmaFrag = match [ 1 ]
47
+ if ( annotations . jsxFrag ) {
48
+ throw new Error ( 'Unexpected `@jsxFrag` pragma w/ automatic runtime' )
49
+ }
50
+ } else if ( annotations . jsxRuntime === 'classic' ) {
51
+ automatic = false
52
+
53
+ if ( annotations . jsxImportSource ) {
54
+ throw new Error ( 'Unexpected `@jsxImportSource` w/ classic runtime' )
55
+ }
56
+ } else {
57
+ throw new Error (
58
+ 'Unexpected `jsxRuntime` `' +
59
+ annotations . jsxRuntime +
60
+ '`, expected `automatic` or `classic`'
61
+ )
35
62
}
36
63
}
37
64
}
@@ -41,19 +68,70 @@ function buildJsx(tree, options) {
41
68
// eslint-disable-next-line complexity
42
69
function leave ( node ) {
43
70
var parameters
71
+ var children
44
72
var fields
45
73
var objects
46
74
var index
47
75
var child
48
76
var name
49
77
var props
50
78
var attributes
79
+ var spread
80
+ var key
81
+ var callee
82
+ var specifiers
83
+ var prop
84
+
85
+ if ( node . type === 'Program' ) {
86
+ specifiers = [ ]
87
+
88
+ if ( imports . fragment ) {
89
+ specifiers . push ( {
90
+ type : 'ImportSpecifier' ,
91
+ imported : { type : 'Identifier' , name : 'Fragment' } ,
92
+ local : { type : 'Identifier' , name : '_Fragment' }
93
+ } )
94
+ }
95
+
96
+ if ( imports . jsx ) {
97
+ specifiers . push ( {
98
+ type : 'ImportSpecifier' ,
99
+ imported : { type : 'Identifier' , name : 'jsx' } ,
100
+ local : { type : 'Identifier' , name : '_jsx' }
101
+ } )
102
+ }
103
+
104
+ if ( imports . jsxs ) {
105
+ specifiers . push ( {
106
+ type : 'ImportSpecifier' ,
107
+ imported : { type : 'Identifier' , name : 'jsxs' } ,
108
+ local : { type : 'Identifier' , name : '_jsxs' }
109
+ } )
110
+ }
111
+
112
+ if ( specifiers . length ) {
113
+ node . body . unshift ( {
114
+ type : 'ImportDeclaration' ,
115
+ specifiers : specifiers ,
116
+ source : {
117
+ type : 'Literal' ,
118
+ value :
119
+ ( annotations . jsxImportSource ||
120
+ settings . importSource ||
121
+ 'react' ) + '/jsx-runtime'
122
+ }
123
+ } )
124
+ }
125
+ }
51
126
52
127
if ( node . type !== 'JSXElement' && node . type !== 'JSXFragment' ) {
53
128
return
54
129
}
55
130
56
131
parameters = [ ]
132
+ children = [ ]
133
+ objects = [ ]
134
+ fields = [ ]
57
135
index = - 1
58
136
59
137
// Figure out `children`.
@@ -86,22 +164,20 @@ function buildJsx(tree, options) {
86
164
}
87
165
// Otherwise, this is an already compiled call.
88
166
89
- parameters . push ( child )
167
+ children . push ( child )
90
168
}
91
169
92
170
// Do the stuff needed for elements.
93
171
if ( node . openingElement ) {
94
172
name = toIdentifier ( node . openingElement . name )
95
173
96
- // If the name could be an identifier, but start with something other than
97
- // a lowercase letter, it’s not a component.
174
+ // If the name could be an identifier, but start with a lowercase letter,
175
+ // it’s not a component.
98
176
if ( name . type === 'Identifier' && / ^ [ a - z ] / . test ( name . name ) ) {
99
177
name = create ( name , { type : 'Literal' , value : name . name } )
100
178
}
101
179
102
180
attributes = node . openingElement . attributes
103
- objects = [ ]
104
- fields = [ ]
105
181
index = - 1
106
182
107
183
// Place props in the right order, because we might have duplicates
@@ -114,46 +190,100 @@ function buildJsx(tree, options) {
114
190
}
115
191
116
192
objects . push ( attributes [ index ] . argument )
193
+ spread = true
117
194
} else {
118
- fields . push ( toProperty ( attributes [ index ] ) )
195
+ prop = toProperty ( attributes [ index ] )
196
+
197
+ if ( automatic && prop . key . name === 'key' ) {
198
+ if ( spread ) {
199
+ throw new Error (
200
+ 'Expected `key` to come before any spread expressions'
201
+ )
202
+ }
203
+
204
+ key = prop . value
205
+ } else {
206
+ fields . push ( prop )
207
+ }
119
208
}
120
209
}
210
+ }
211
+ // …and fragments.
212
+ else if ( automatic ) {
213
+ imports . fragment = true
214
+ name = { type : 'Identifier' , name : '_Fragment' }
215
+ } else {
216
+ name = toMemberExpression (
217
+ annotations . jsxFrag || settings . pragmaFrag || 'React.Fragment'
218
+ )
219
+ }
220
+
221
+ if ( automatic && children . length ) {
222
+ fields . push ( {
223
+ type : 'Property' ,
224
+ key : { type : 'Identifier' , name : 'children' } ,
225
+ value :
226
+ children . length > 1
227
+ ? { type : 'ArrayExpression' , elements : children }
228
+ : children [ 0 ] ,
229
+ kind : 'init'
230
+ } )
231
+ } else {
232
+ parameters = children
233
+ }
234
+
235
+ if ( fields . length ) {
236
+ objects . push ( { type : 'ObjectExpression' , properties : fields } )
237
+ }
121
238
122
- if ( fields . length ) {
123
- objects . push ( { type : 'ObjectExpression' , properties : fields } )
239
+ if ( objects . length > 1 ) {
240
+ // Don’t mutate the first object, shallow clone instead.
241
+ if ( objects [ 0 ] . type !== 'ObjectExpression' ) {
242
+ objects . unshift ( { type : 'ObjectExpression' , properties : [ ] } )
124
243
}
125
244
126
- if ( objects . length > 1 ) {
127
- // Don’t mutate the first object, shallow clone instead.
128
- if ( objects [ 0 ] . type !== 'ObjectExpression' ) {
129
- objects . unshift ( { type : 'ObjectExpression' , properties : [ ] } )
130
- }
245
+ props = {
246
+ type : 'CallExpression' ,
247
+ callee : toMemberExpression ( 'Object.assign' ) ,
248
+ arguments : objects
249
+ }
250
+ } else if ( objects . length ) {
251
+ props = objects [ 0 ]
252
+ }
131
253
132
- props = {
133
- type : 'CallExpression' ,
134
- callee : toMemberExpression ( 'Object.assign' ) ,
135
- arguments : objects
136
- }
137
- } else if ( objects . length ) {
138
- props = objects [ 0 ]
254
+ if ( automatic ) {
255
+ if ( children . length > 1 ) {
256
+ imports . jsxs = true
257
+ callee = { type : 'Identifier' , name : '_jsxs' }
258
+ } else {
259
+ imports . jsx = true
260
+ callee = { type : 'Identifier' , name : '_jsx' }
261
+ }
262
+
263
+ parameters . push ( props || { type : 'ObjectExpression' , properties : [ ] } )
264
+
265
+ if ( key ) {
266
+ parameters . push ( key )
139
267
}
140
268
}
141
- // …and fragments .
269
+ // Classic .
142
270
else {
143
- name = toMemberExpression ( pragmaFrag || 'React.Fragment' )
144
- }
271
+ // There are props or children.
272
+ if ( props || parameters . length ) {
273
+ parameters . unshift ( props || { type : 'Literal' , value : null } )
274
+ }
145
275
146
- // There are props or children.
147
- if ( props || parameters . length ) {
148
- parameters . unshift ( props || { type : 'Literal' , value : null } )
276
+ callee = toMemberExpression (
277
+ annotations . jsx || settings . pragma || 'React.createElement'
278
+ )
149
279
}
150
280
151
281
parameters . unshift ( name )
152
282
153
283
this . replace (
154
284
create ( node , {
155
285
type : 'CallExpression' ,
156
- callee : toMemberExpression ( pragma || 'React.createElement' ) ,
286
+ callee : callee ,
157
287
arguments : parameters
158
288
} )
159
289
)
0 commit comments