1
1
import { Parser as HTMLParser } from 'htmlparser2'
2
- import { types } from '@babel/core'
2
+ import { types as t } from '@babel/core'
3
3
import { parse , ParserOptions , ParserPlugin } from '@babel/parser'
4
- import { isHTMLTag , isSVGTag , isVoidTag } from '@vue/shared'
4
+ import { camelize , capitalize , isHTMLTag , isSVGTag , isVoidTag } from '@vue/shared'
5
5
import traverse from '@babel/traverse'
6
6
import generate from '@babel/generator'
7
7
@@ -11,7 +11,8 @@ interface TagMeta {
11
11
contentStart : number
12
12
contentEnd : number
13
13
content : string
14
- attributes : Record < string , string >
14
+ attrs : Record < string , string >
15
+ found : boolean
15
16
}
16
17
17
18
export interface ParseResult {
@@ -38,15 +39,17 @@ export function parseVueSFC(code: string): ParseResult {
38
39
contentStart : 0 ,
39
40
contentEnd : 0 ,
40
41
content : '' ,
41
- attributes : { } ,
42
+ attrs : { } ,
43
+ found : false ,
42
44
}
43
45
const script : TagMeta = {
44
46
start : 0 ,
45
47
end : 0 ,
46
48
contentStart : 0 ,
47
49
contentEnd : 0 ,
48
50
content : '' ,
49
- attributes : { } ,
51
+ attrs : { } ,
52
+ found : false ,
50
53
}
51
54
52
55
const parser = new HTMLParser ( {
@@ -56,7 +59,7 @@ export function parseVueSFC(code: string): ParseResult {
56
59
57
60
if ( templateLevel > 0 ) {
58
61
if ( ! isHTMLTag ( name ) && ! isSVGTag ( name ) && ! isVoidTag ( name ) )
59
- components . add ( name )
62
+ components . add ( capitalize ( camelize ( name ) ) )
60
63
Object . entries ( attributes ) . forEach ( ( [ key , value ] ) => {
61
64
if ( ! value )
62
65
return
@@ -71,13 +74,15 @@ export function parseVueSFC(code: string): ParseResult {
71
74
if ( 'setup' in attributes ) {
72
75
scriptSetup . start = parser . startIndex
73
76
scriptSetup . contentStart = parser . endIndex ! + 1
74
- scriptSetup . attributes = attributes
77
+ scriptSetup . attrs = attributes
78
+ scriptSetup . found = true
75
79
inScriptSetup = true
76
80
}
77
81
else {
78
82
script . start = parser . startIndex
79
83
script . contentStart = parser . endIndex ! + 1
80
- script . attributes = attributes
84
+ script . attrs = attributes
85
+ script . found = true
81
86
inScript = true
82
87
}
83
88
}
@@ -140,16 +145,19 @@ export function getIdentifiersFromCode(code: string, identifiers = new Set<strin
140
145
}
141
146
142
147
export function transformScriptSetup ( result : ParseResult ) {
143
- if ( result . scriptSetup . attributes . lang !== result . script . attributes . lang )
148
+ if ( result . script . found && result . scriptSetup . found && result . scriptSetup . attrs . lang !== result . script . attrs . lang )
144
149
throw new SyntaxError ( '<script setup> language must be the same as <script>' )
145
150
151
+ const lang = result . scriptSetup . attrs . lang || result . script . attrs . lang
146
152
const plugins : ParserPlugin [ ] = [ ]
147
- if ( result . scriptSetup . attributes . lang === 'ts' )
153
+ if ( lang === 'ts' )
148
154
plugins . push ( 'typescript' )
149
- if ( result . scriptSetup . attributes . lang === 'jsx' )
155
+ else if ( lang === 'jsx' )
150
156
plugins . push ( 'jsx' )
151
- if ( result . scriptSetup . attributes . lang === 'tsx' )
157
+ else if ( lang === 'tsx' )
152
158
plugins . push ( 'typescript' , 'jsx' )
159
+ else if ( lang !== 'js' )
160
+ throw new SyntaxError ( `Unsupported script language: ${ lang } ` )
153
161
154
162
const identifiers = new Set < string > ( )
155
163
const scriptSetupAst = parse ( result . scriptSetup . content , {
@@ -168,33 +176,28 @@ export function transformScriptSetup(result: ParseResult) {
168
176
} )
169
177
170
178
const returns = Array . from ( identifiers ) . filter ( i => result . template . identifiers . has ( i ) )
179
+ const components = Array . from ( identifiers ) . filter ( i =>
180
+ result . template . components . has ( i )
181
+ || result . template . components . has ( camelize ( i ) )
182
+ || result . template . components . has ( capitalize ( camelize ( i ) ) ) ,
183
+ )
171
184
172
185
const imports = scriptSetupAst . program . body . filter ( n => n . type === 'ImportDeclaration' )
173
- const body = scriptSetupAst . program . body . filter ( n => n . type !== 'ImportDeclaration' )
186
+ const scriptSetupBody = scriptSetupAst . program . body . filter ( n => n . type !== 'ImportDeclaration' )
174
187
// TODO: apply macros
175
- const returnStatement = types . returnStatement (
176
- types . objectExpression (
177
- returns . map ( ( i ) => {
178
- const id = types . identifier ( i )
179
- return types . objectProperty ( id , id , false , true )
180
- } ) ,
181
- ) ,
182
- )
183
- const setup = types . arrowFunctionExpression ( [ ] , types . blockStatement ( [
184
- ...body ,
185
- returnStatement as any ,
186
- ] ) )
187
188
189
+ // append `<script setup>` imports to `<script>`
188
190
scriptAst . program . body . unshift ( ...imports )
189
191
190
192
// replace `export default` with a temproray variable
193
+ // `const __sfc_main = { ... }`
191
194
traverse ( scriptAst as any , {
192
195
ExportDefaultDeclaration ( path ) {
193
196
const decl = path . node . declaration
194
197
path . replaceWith (
195
- types . variableDeclaration ( 'const' , [
196
- types . variableDeclarator (
197
- types . identifier ( '__sfc_main' ) ,
198
+ t . variableDeclaration ( 'const' , [
199
+ t . variableDeclarator (
200
+ t . identifier ( '__sfc_main' ) ,
198
201
decl as any ,
199
202
) ,
200
203
] ) ,
@@ -203,15 +206,63 @@ export function transformScriptSetup(result: ParseResult) {
203
206
} )
204
207
205
208
// inject setup function
206
- scriptAst . program . body . push (
207
- types . expressionStatement (
208
- types . assignmentExpression ( '=' , types . memberExpression ( types . identifier ( '__sfc_main' ) , types . identifier ( 'setup' ) ) , setup ) ,
209
- ) as any ,
210
- )
209
+ // `__sfc_main.setup = () => {}`
210
+ if ( scriptSetupBody . length ) {
211
+ const returnStatement = t . returnStatement (
212
+ t . objectExpression (
213
+ returns . map ( ( i ) => {
214
+ const id = t . identifier ( i )
215
+ return t . objectProperty ( id , id , false , true )
216
+ } ) ,
217
+ ) ,
218
+ )
219
+
220
+ scriptAst . program . body . push (
221
+ t . expressionStatement (
222
+ t . assignmentExpression ( '=' ,
223
+ t . memberExpression ( t . identifier ( '__sfc_main' ) , t . identifier ( 'setup' ) ) ,
224
+ t . arrowFunctionExpression ( [ ] , t . blockStatement ( [
225
+ ...scriptSetupBody ,
226
+ returnStatement as any ,
227
+ ] ) ) ,
228
+ ) ,
229
+ ) as any ,
230
+ )
231
+ }
232
+
233
+ // inject components
234
+ // `__sfc_main.components = Object.assign({ ... }, __sfc_main.components)`
235
+ if ( components . length ) {
236
+ const componentsObject = t . objectExpression (
237
+ components . map ( ( i ) => {
238
+ const id = t . identifier ( i )
239
+ return t . objectProperty ( id , id , false , true )
240
+ } ) ,
241
+ )
242
+
243
+ scriptAst . program . body . push (
244
+ t . expressionStatement (
245
+ t . assignmentExpression ( '=' ,
246
+ t . memberExpression ( t . identifier ( '__sfc_main' ) , t . identifier ( 'components' ) ) ,
247
+ t . callExpression (
248
+ t . memberExpression ( t . identifier ( 'Object' ) , t . identifier ( 'assign' ) ) ,
249
+ [
250
+ componentsObject ,
251
+ t . memberExpression (
252
+ t . identifier ( '__sfc_main' ) ,
253
+ t . identifier ( 'components' ) ,
254
+ ) ,
255
+ ] ,
256
+ ) ,
257
+ ) ,
258
+ ) as any ,
259
+ )
260
+ }
211
261
212
262
// re-export
263
+ // `export default __sfc_main`
213
264
scriptAst . program . body . push (
214
- types . exportDefaultDeclaration ( types . identifier ( '__sfc_main' ) ) as any ,
265
+ t . exportDefaultDeclaration ( t . identifier ( '__sfc_main' ) ) as any ,
215
266
)
216
267
217
268
return {
0 commit comments