11import { Parser as HTMLParser } from 'htmlparser2'
2- import { types } from '@babel/core'
2+ import { types as t } from '@babel/core'
33import { parse , ParserOptions , ParserPlugin } from '@babel/parser'
4- import { isHTMLTag , isSVGTag , isVoidTag } from '@vue/shared'
4+ import { camelize , capitalize , isHTMLTag , isSVGTag , isVoidTag } from '@vue/shared'
55import traverse from '@babel/traverse'
66import generate from '@babel/generator'
77
@@ -11,7 +11,8 @@ interface TagMeta {
1111 contentStart : number
1212 contentEnd : number
1313 content : string
14- attributes : Record < string , string >
14+ attrs : Record < string , string >
15+ found : boolean
1516}
1617
1718export interface ParseResult {
@@ -38,15 +39,17 @@ export function parseVueSFC(code: string): ParseResult {
3839 contentStart : 0 ,
3940 contentEnd : 0 ,
4041 content : '' ,
41- attributes : { } ,
42+ attrs : { } ,
43+ found : false ,
4244 }
4345 const script : TagMeta = {
4446 start : 0 ,
4547 end : 0 ,
4648 contentStart : 0 ,
4749 contentEnd : 0 ,
4850 content : '' ,
49- attributes : { } ,
51+ attrs : { } ,
52+ found : false ,
5053 }
5154
5255 const parser = new HTMLParser ( {
@@ -56,7 +59,7 @@ export function parseVueSFC(code: string): ParseResult {
5659
5760 if ( templateLevel > 0 ) {
5861 if ( ! isHTMLTag ( name ) && ! isSVGTag ( name ) && ! isVoidTag ( name ) )
59- components . add ( name )
62+ components . add ( capitalize ( camelize ( name ) ) )
6063 Object . entries ( attributes ) . forEach ( ( [ key , value ] ) => {
6164 if ( ! value )
6265 return
@@ -71,13 +74,15 @@ export function parseVueSFC(code: string): ParseResult {
7174 if ( 'setup' in attributes ) {
7275 scriptSetup . start = parser . startIndex
7376 scriptSetup . contentStart = parser . endIndex ! + 1
74- scriptSetup . attributes = attributes
77+ scriptSetup . attrs = attributes
78+ scriptSetup . found = true
7579 inScriptSetup = true
7680 }
7781 else {
7882 script . start = parser . startIndex
7983 script . contentStart = parser . endIndex ! + 1
80- script . attributes = attributes
84+ script . attrs = attributes
85+ script . found = true
8186 inScript = true
8287 }
8388 }
@@ -140,16 +145,19 @@ export function getIdentifiersFromCode(code: string, identifiers = new Set<strin
140145}
141146
142147export 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 )
144149 throw new SyntaxError ( '<script setup> language must be the same as <script>' )
145150
151+ const lang = result . scriptSetup . attrs . lang || result . script . attrs . lang
146152 const plugins : ParserPlugin [ ] = [ ]
147- if ( result . scriptSetup . attributes . lang === 'ts' )
153+ if ( lang === 'ts' )
148154 plugins . push ( 'typescript' )
149- if ( result . scriptSetup . attributes . lang === 'jsx' )
155+ else if ( lang === 'jsx' )
150156 plugins . push ( 'jsx' )
151- if ( result . scriptSetup . attributes . lang === 'tsx' )
157+ else if ( lang === 'tsx' )
152158 plugins . push ( 'typescript' , 'jsx' )
159+ else if ( lang !== 'js' )
160+ throw new SyntaxError ( `Unsupported script language: ${ lang } ` )
153161
154162 const identifiers = new Set < string > ( )
155163 const scriptSetupAst = parse ( result . scriptSetup . content , {
@@ -168,33 +176,28 @@ export function transformScriptSetup(result: ParseResult) {
168176 } )
169177
170178 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+ )
171184
172185 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' )
174187 // 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- ] ) )
187188
189+ // append `<script setup>` imports to `<script>`
188190 scriptAst . program . body . unshift ( ...imports )
189191
190192 // replace `export default` with a temproray variable
193+ // `const __sfc_main = { ... }`
191194 traverse ( scriptAst as any , {
192195 ExportDefaultDeclaration ( path ) {
193196 const decl = path . node . declaration
194197 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' ) ,
198201 decl as any ,
199202 ) ,
200203 ] ) ,
@@ -203,15 +206,63 @@ export function transformScriptSetup(result: ParseResult) {
203206 } )
204207
205208 // 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+ }
211261
212262 // re-export
263+ // `export default __sfc_main`
213264 scriptAst . program . body . push (
214- types . exportDefaultDeclaration ( types . identifier ( '__sfc_main' ) ) as any ,
265+ t . exportDefaultDeclaration ( t . identifier ( '__sfc_main' ) ) as any ,
215266 )
216267
217268 return {
0 commit comments