compileScript.ts 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445
  1. import MagicString from 'magic-string'
  2. import { BindingMetadata } from '@vue/compiler-core'
  3. import { SFCDescriptor, SFCScriptBlock } from './parse'
  4. import { parse as _parse, ParserOptions, ParserPlugin } from '@babel/parser'
  5. import { babelParserDefaultPlugins, generateCodeFrame } from '@vue/shared'
  6. import {
  7. Node,
  8. Declaration,
  9. ObjectPattern,
  10. ObjectExpression,
  11. ArrayPattern,
  12. Identifier,
  13. ExportSpecifier,
  14. Function as FunctionNode,
  15. TSType,
  16. TSTypeLiteral,
  17. TSFunctionType,
  18. ObjectProperty,
  19. ArrayExpression,
  20. Statement,
  21. Expression,
  22. LabeledStatement,
  23. TSUnionType
  24. } from '@babel/types'
  25. import { walk } from 'estree-walker'
  26. import { RawSourceMap } from 'source-map'
  27. import { genCssVarsCode, injectCssVarsCalls } from './genCssVars'
  28. import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate'
  29. import { BindingTypes } from 'packages/compiler-core/src/options'
  30. const USE_OPTIONS = 'useOptions'
  31. export interface SFCScriptCompileOptions {
  32. /**
  33. * https://babeljs.io/docs/en/babel-parser#plugins
  34. */
  35. babelParserPlugins?: ParserPlugin[]
  36. /**
  37. * Enable ref: label sugar
  38. * https://github.com/vuejs/rfcs/pull/228
  39. * @default true
  40. */
  41. refSugar?: boolean
  42. /**
  43. * Compile the template and inline the resulting render function
  44. * directly inside setup().
  45. * - Only affects <script setup>
  46. * - This should only be used in production because it prevents the template
  47. * from being hot-reloaded separately from component state.
  48. */
  49. inlineTemplate?: boolean
  50. templateOptions?: SFCTemplateCompileOptions
  51. }
  52. const hasWarned: Record<string, boolean> = {}
  53. function warnOnce(msg: string) {
  54. if (!hasWarned[msg]) {
  55. hasWarned[msg] = true
  56. console.log(`\x1b[33m[@vue/compiler-sfc] %s\x1b[0m\n`, msg)
  57. }
  58. }
  59. /**
  60. * Compile `<script setup>`
  61. * It requires the whole SFC descriptor because we need to handle and merge
  62. * normal `<script>` + `<script setup>` if both are present.
  63. */
  64. export function compileScript(
  65. sfc: SFCDescriptor,
  66. options: SFCScriptCompileOptions = {}
  67. ): SFCScriptBlock {
  68. const { script, scriptSetup, styles, source, filename } = sfc
  69. if (__DEV__ && !__TEST__ && scriptSetup) {
  70. warnOnce(
  71. `<script setup> is still an experimental proposal.\n` +
  72. `Follow its status at https://github.com/vuejs/rfcs/pull/227.`
  73. )
  74. }
  75. const hasCssVars = styles.some(s => typeof s.attrs.vars === 'string')
  76. const scriptLang = script && script.lang
  77. const scriptSetupLang = scriptSetup && scriptSetup.lang
  78. const isTS = scriptLang === 'ts' || scriptSetupLang === 'ts'
  79. const plugins: ParserPlugin[] = [...babelParserDefaultPlugins, 'jsx']
  80. if (options.babelParserPlugins) plugins.push(...options.babelParserPlugins)
  81. if (isTS) plugins.push('typescript', 'decorators-legacy')
  82. if (!scriptSetup) {
  83. if (!script) {
  84. throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
  85. }
  86. if (scriptLang && scriptLang !== 'ts') {
  87. // do not process non js/ts script blocks
  88. return script
  89. }
  90. try {
  91. const scriptAst = _parse(script.content, {
  92. plugins,
  93. sourceType: 'module'
  94. }).program.body
  95. return {
  96. ...script,
  97. content: hasCssVars ? injectCssVarsCalls(sfc, plugins) : script.content,
  98. bindings: analyzeScriptBindings(scriptAst),
  99. scriptAst
  100. }
  101. } catch (e) {
  102. // silently fallback if parse fails since user may be using custom
  103. // babel syntax
  104. return script
  105. }
  106. }
  107. if (script && scriptLang !== scriptSetupLang) {
  108. throw new Error(
  109. `[@vue/compiler-sfc] <script> and <script setup> must have the same language type.`
  110. )
  111. }
  112. if (scriptSetupLang && scriptSetupLang !== 'ts') {
  113. // do not process non js/ts script blocks
  114. return scriptSetup
  115. }
  116. const defaultTempVar = `__default__`
  117. const bindingMetadata: BindingMetadata = {}
  118. const helperImports: Set<string> = new Set()
  119. const userImports: Record<
  120. string,
  121. {
  122. imported: string | null
  123. source: string
  124. }
  125. > = Object.create(null)
  126. const setupBindings: Record<
  127. string,
  128. BindingTypes.SETUP | BindingTypes.CONST
  129. > = Object.create(null)
  130. const refBindings: Record<string, BindingTypes.SETUP> = Object.create(null)
  131. const refIdentifiers: Set<Identifier> = new Set()
  132. const enableRefSugar = options.refSugar !== false
  133. let defaultExport: Node | undefined
  134. let hasOptionsCall = false
  135. let optionsExp: string | undefined
  136. let optionsArg: ObjectExpression | undefined
  137. let optionsType: TSTypeLiteral | undefined
  138. let hasAwait = false
  139. const s = new MagicString(source)
  140. const startOffset = scriptSetup.loc.start.offset
  141. const endOffset = scriptSetup.loc.end.offset
  142. const scriptStartOffset = script && script.loc.start.offset
  143. const scriptEndOffset = script && script.loc.end.offset
  144. function parse(
  145. input: string,
  146. options: ParserOptions,
  147. offset: number
  148. ): Statement[] {
  149. try {
  150. return _parse(input, options).program.body
  151. } catch (e) {
  152. e.message = `[@vue/compiler-sfc] ${e.message}\n\n${generateCodeFrame(
  153. source,
  154. e.pos + offset,
  155. e.pos + offset + 1
  156. )}`
  157. throw e
  158. }
  159. }
  160. function error(
  161. msg: string,
  162. node: Node,
  163. end: number = node.end! + startOffset
  164. ) {
  165. throw new Error(
  166. `[@vue/compiler-sfc] ${msg}\n\n` +
  167. generateCodeFrame(source, node.start! + startOffset, end)
  168. )
  169. }
  170. function processUseOptions(node: Node): boolean {
  171. if (
  172. node.type === 'CallExpression' &&
  173. node.callee.type === 'Identifier' &&
  174. node.callee.name === USE_OPTIONS
  175. ) {
  176. if (hasOptionsCall) {
  177. error(`duplicate ${USE_OPTIONS}() call`, node)
  178. }
  179. hasOptionsCall = true
  180. const optsArg = node.arguments[0]
  181. if (optsArg) {
  182. if (optsArg.type === 'ObjectExpression') {
  183. optionsArg = optsArg
  184. } else {
  185. error(`${USE_OPTIONS}() argument must be an object literal.`, optsArg)
  186. }
  187. }
  188. // context call has type parameters - infer runtime types from it
  189. if (node.typeParameters) {
  190. if (optionsArg) {
  191. error(
  192. `${USE_OPTIONS}() cannot accept both type and non-type arguments ` +
  193. `at the same time. Use one or the other.`,
  194. node
  195. )
  196. }
  197. const typeArg = node.typeParameters.params[0]
  198. if (typeArg.type === 'TSTypeLiteral') {
  199. optionsType = typeArg
  200. } else {
  201. error(
  202. `type argument passed to ${USE_OPTIONS}() must be a literal type.`,
  203. typeArg
  204. )
  205. }
  206. }
  207. return true
  208. }
  209. return false
  210. }
  211. function processRefExpression(exp: Expression, statement: LabeledStatement) {
  212. if (exp.type === 'AssignmentExpression') {
  213. helperImports.add('ref')
  214. const { left, right } = exp
  215. if (left.type === 'Identifier') {
  216. registerRefBinding(left)
  217. s.prependRight(right.start! + startOffset, `ref(`)
  218. s.appendLeft(right.end! + startOffset, ')')
  219. } else if (left.type === 'ObjectPattern') {
  220. // remove wrapping parens
  221. for (let i = left.start!; i > 0; i--) {
  222. const char = source[i + startOffset]
  223. if (char === '(') {
  224. s.remove(i + startOffset, i + startOffset + 1)
  225. break
  226. }
  227. }
  228. for (let i = left.end!; i > 0; i++) {
  229. const char = source[i + startOffset]
  230. if (char === ')') {
  231. s.remove(i + startOffset, i + startOffset + 1)
  232. break
  233. }
  234. }
  235. processRefObjectPattern(left, statement)
  236. } else if (left.type === 'ArrayPattern') {
  237. processRefArrayPattern(left, statement)
  238. }
  239. } else if (exp.type === 'SequenceExpression') {
  240. // possible multiple declarations
  241. // ref: x = 1, y = 2
  242. exp.expressions.forEach(e => processRefExpression(e, statement))
  243. } else if (exp.type === 'Identifier') {
  244. registerRefBinding(exp)
  245. s.appendLeft(exp.end! + startOffset, ` = ref()`)
  246. } else {
  247. error(`ref: statements can only contain assignment expressions.`, exp)
  248. }
  249. }
  250. function registerRefBinding(id: Identifier) {
  251. if (id.name[0] === '$') {
  252. error(`ref variable identifiers cannot start with $.`, id)
  253. }
  254. refBindings[id.name] = setupBindings[id.name] = BindingTypes.SETUP
  255. refIdentifiers.add(id)
  256. }
  257. function processRefObjectPattern(
  258. pattern: ObjectPattern,
  259. statement: LabeledStatement
  260. ) {
  261. for (const p of pattern.properties) {
  262. let nameId: Identifier | undefined
  263. if (p.type === 'ObjectProperty') {
  264. if (p.key.start! === p.value.start!) {
  265. // shorthand { foo } --> { foo: __foo }
  266. nameId = p.key as Identifier
  267. s.appendLeft(nameId.end! + startOffset, `: __${nameId.name}`)
  268. if (p.value.type === 'AssignmentPattern') {
  269. // { foo = 1 }
  270. refIdentifiers.add(p.value.left as Identifier)
  271. }
  272. } else {
  273. if (p.value.type === 'Identifier') {
  274. // { foo: bar } --> { foo: __bar }
  275. nameId = p.value
  276. s.prependRight(nameId.start! + startOffset, `__`)
  277. } else if (p.value.type === 'ObjectPattern') {
  278. processRefObjectPattern(p.value, statement)
  279. } else if (p.value.type === 'ArrayPattern') {
  280. processRefArrayPattern(p.value, statement)
  281. } else if (p.value.type === 'AssignmentPattern') {
  282. // { foo: bar = 1 } --> { foo: __bar = 1 }
  283. nameId = p.value.left as Identifier
  284. s.prependRight(nameId.start! + startOffset, `__`)
  285. }
  286. }
  287. } else {
  288. // rest element { ...foo } --> { ...__foo }
  289. nameId = p.argument as Identifier
  290. s.prependRight(nameId.start! + startOffset, `__`)
  291. }
  292. if (nameId) {
  293. registerRefBinding(nameId)
  294. // append binding declarations after the parent statement
  295. s.appendLeft(
  296. statement.end! + startOffset,
  297. `\nconst ${nameId.name} = ref(__${nameId.name});`
  298. )
  299. }
  300. }
  301. }
  302. function processRefArrayPattern(
  303. pattern: ArrayPattern,
  304. statement: LabeledStatement
  305. ) {
  306. for (const e of pattern.elements) {
  307. if (!e) continue
  308. let nameId: Identifier | undefined
  309. if (e.type === 'Identifier') {
  310. // [a] --> [__a]
  311. nameId = e
  312. } else if (e.type === 'AssignmentPattern') {
  313. // [a = 1] --> [__a = 1]
  314. nameId = e.left as Identifier
  315. } else if (e.type === 'RestElement') {
  316. // [...a] --> [...__a]
  317. nameId = e.argument as Identifier
  318. } else if (e.type === 'ObjectPattern') {
  319. processRefObjectPattern(e, statement)
  320. } else if (e.type === 'ArrayPattern') {
  321. processRefArrayPattern(e, statement)
  322. }
  323. if (nameId) {
  324. registerRefBinding(nameId)
  325. // prefix original
  326. s.prependRight(nameId.start! + startOffset, `__`)
  327. // append binding declarations after the parent statement
  328. s.appendLeft(
  329. statement.end! + startOffset,
  330. `\nconst ${nameId.name} = ref(__${nameId.name});`
  331. )
  332. }
  333. }
  334. }
  335. // 1. process normal <script> first if it exists
  336. let scriptAst
  337. if (script) {
  338. // import dedupe between <script> and <script setup>
  339. scriptAst = parse(
  340. script.content,
  341. {
  342. plugins,
  343. sourceType: 'module'
  344. },
  345. scriptStartOffset!
  346. )
  347. for (const node of scriptAst) {
  348. if (node.type === 'ImportDeclaration') {
  349. // record imports for dedupe
  350. for (const specifier of node.specifiers) {
  351. const name = specifier.local.name
  352. const imported =
  353. specifier.type === 'ImportSpecifier' &&
  354. specifier.imported.type === 'Identifier' &&
  355. specifier.imported.name
  356. userImports[name] = {
  357. imported: imported || null,
  358. source: node.source.value
  359. }
  360. }
  361. } else if (node.type === 'ExportDefaultDeclaration') {
  362. // export default
  363. defaultExport = node
  364. const start = node.start! + scriptStartOffset!
  365. s.overwrite(
  366. start,
  367. start + `export default`.length,
  368. `const ${defaultTempVar} =`
  369. )
  370. } else if (node.type === 'ExportNamedDeclaration' && node.specifiers) {
  371. const defaultSpecifier = node.specifiers.find(
  372. s => s.exported.type === 'Identifier' && s.exported.name === 'default'
  373. ) as ExportSpecifier
  374. if (defaultSpecifier) {
  375. defaultExport = node
  376. // 1. remove specifier
  377. if (node.specifiers.length > 1) {
  378. s.remove(
  379. defaultSpecifier.start! + scriptStartOffset!,
  380. defaultSpecifier.end! + scriptStartOffset!
  381. )
  382. } else {
  383. s.remove(
  384. node.start! + scriptStartOffset!,
  385. node.end! + scriptStartOffset!
  386. )
  387. }
  388. if (node.source) {
  389. // export { x as default } from './x'
  390. // rewrite to `import { x as __default__ } from './x'` and
  391. // add to top
  392. s.prepend(
  393. `import { ${
  394. defaultSpecifier.local.name
  395. } as ${defaultTempVar} } from '${node.source.value}'\n`
  396. )
  397. } else {
  398. // export { x as default }
  399. // rewrite to `const __default__ = x` and move to end
  400. s.append(
  401. `\nconst ${defaultTempVar} = ${defaultSpecifier.local.name}\n`
  402. )
  403. }
  404. }
  405. }
  406. }
  407. }
  408. let propsType = `{}`
  409. let emitType = `(e: string, ...args: any[]) => void`
  410. let slotsType = `Slots`
  411. let attrsType = `Record<string, any>`
  412. // props/emits declared via types
  413. const typeDeclaredProps: Record<string, PropTypeData> = {}
  414. const typeDeclaredEmits: Set<string> = new Set()
  415. // record declared types for runtime props type generation
  416. const declaredTypes: Record<string, string[]> = {}
  417. // 3. parse <script setup> and walk over top level statements
  418. const scriptSetupAst = parse(
  419. scriptSetup.content,
  420. {
  421. plugins: [
  422. ...plugins,
  423. // allow top level await but only inside <script setup>
  424. 'topLevelAwait'
  425. ],
  426. sourceType: 'module'
  427. },
  428. startOffset
  429. )
  430. for (const node of scriptSetupAst) {
  431. const start = node.start! + startOffset
  432. let end = node.end! + startOffset
  433. // import or type declarations: move to top
  434. // locate comment
  435. if (node.trailingComments && node.trailingComments.length > 0) {
  436. const lastCommentNode =
  437. node.trailingComments[node.trailingComments.length - 1]
  438. end = lastCommentNode.end + startOffset
  439. }
  440. // locate the end of whitespace between this statement and the next
  441. while (end <= source.length) {
  442. if (!/\s/.test(source.charAt(end))) {
  443. break
  444. }
  445. end++
  446. }
  447. // process `ref: x` bindings (convert to refs)
  448. if (
  449. node.type === 'LabeledStatement' &&
  450. node.label.name === 'ref' &&
  451. node.body.type === 'ExpressionStatement'
  452. ) {
  453. if (enableRefSugar) {
  454. warnOnce(
  455. `ref: sugar is still an experimental proposal and is not ` +
  456. `guaranteed to be a part of <script setup>.\n` +
  457. `Follow its status at https://github.com/vuejs/rfcs/pull/228.`
  458. )
  459. s.overwrite(
  460. node.label.start! + startOffset,
  461. node.body.start! + startOffset,
  462. 'const '
  463. )
  464. processRefExpression(node.body.expression, node)
  465. } else {
  466. // TODO if we end up shipping ref: sugar as an opt-in feature,
  467. // need to proxy the option in vite, vue-loader and rollup-plugin-vue.
  468. error(
  469. `ref: sugar needs to be explicitly enabled via vite or vue-loader options.`,
  470. node
  471. )
  472. }
  473. }
  474. if (node.type === 'ImportDeclaration') {
  475. // import declarations are moved to top
  476. s.move(start, end, 0)
  477. // dedupe imports
  478. let prev
  479. let removed = 0
  480. for (const specifier of node.specifiers) {
  481. const local = specifier.local.name
  482. const imported =
  483. specifier.type === 'ImportSpecifier' &&
  484. specifier.imported.type === 'Identifier' &&
  485. specifier.imported.name
  486. const source = node.source.value
  487. const existing = userImports[local]
  488. if (source === 'vue' && imported === USE_OPTIONS) {
  489. removed++
  490. s.remove(
  491. prev ? prev.end! + startOffset : specifier.start! + startOffset,
  492. specifier.end! + startOffset
  493. )
  494. } else if (existing) {
  495. if (existing.source === source && existing.imported === imported) {
  496. // already imported in <script setup>, dedupe
  497. removed++
  498. s.remove(
  499. prev ? prev.end! + startOffset : specifier.start! + startOffset,
  500. specifier.end! + startOffset
  501. )
  502. } else {
  503. error(`different imports aliased to same local name.`, specifier)
  504. }
  505. } else {
  506. userImports[local] = {
  507. imported: imported || null,
  508. source: node.source.value
  509. }
  510. }
  511. prev = specifier
  512. }
  513. if (removed === node.specifiers.length) {
  514. s.remove(node.start! + startOffset, node.end! + startOffset)
  515. }
  516. }
  517. if (
  518. node.type === 'ExpressionStatement' &&
  519. processUseOptions(node.expression)
  520. ) {
  521. s.remove(node.start! + startOffset, node.end! + startOffset)
  522. }
  523. if (node.type === 'VariableDeclaration' && !node.declare) {
  524. for (const decl of node.declarations) {
  525. if (decl.init && processUseOptions(decl.init)) {
  526. optionsExp = scriptSetup.content.slice(decl.id.start!, decl.id.end!)
  527. if (node.declarations.length === 1) {
  528. s.remove(node.start! + startOffset, node.end! + startOffset)
  529. } else {
  530. s.remove(decl.start! + startOffset, decl.end! + startOffset)
  531. }
  532. }
  533. }
  534. }
  535. // walk decalrations to record declared bindings
  536. if (
  537. (node.type === 'VariableDeclaration' ||
  538. node.type === 'FunctionDeclaration' ||
  539. node.type === 'ClassDeclaration') &&
  540. !node.declare
  541. ) {
  542. walkDeclaration(node, setupBindings)
  543. }
  544. // Type declarations
  545. if (node.type === 'VariableDeclaration' && node.declare) {
  546. s.remove(start, end)
  547. }
  548. // move all type declarations to outer scope
  549. if (
  550. node.type.startsWith('TS') ||
  551. (node.type === 'ExportNamedDeclaration' && node.exportKind === 'type')
  552. ) {
  553. recordType(node, declaredTypes)
  554. s.move(start, end, 0)
  555. }
  556. // walk statements & named exports / variable declarations for top level
  557. // await
  558. if (
  559. (node.type === 'VariableDeclaration' && !node.declare) ||
  560. node.type.endsWith('Statement')
  561. ) {
  562. ;(walk as any)(node, {
  563. enter(node: Node) {
  564. if (isFunction(node)) {
  565. this.skip()
  566. }
  567. if (node.type === 'AwaitExpression') {
  568. hasAwait = true
  569. }
  570. }
  571. })
  572. }
  573. if (
  574. (node.type === 'ExportNamedDeclaration' && node.exportKind !== 'type') ||
  575. node.type === 'ExportAllDeclaration' ||
  576. node.type === 'ExportDefaultDeclaration'
  577. ) {
  578. error(
  579. `<script setup> cannot contain ES module exports. ` +
  580. `If you are using a previous version of <script setup>, please ` +
  581. `consult the updated RFC at https://github.com/vuejs/rfcs/pull/227.`,
  582. node
  583. )
  584. }
  585. }
  586. // 4. Do a full walk to rewrite identifiers referencing let exports with ref
  587. // value access
  588. if (enableRefSugar && Object.keys(refBindings).length) {
  589. for (const node of scriptSetupAst) {
  590. if (node.type !== 'ImportDeclaration') {
  591. walkIdentifiers(node, (id, parent) => {
  592. if (refBindings[id.name] && !refIdentifiers.has(id)) {
  593. if (isStaticProperty(parent) && parent.shorthand) {
  594. // let binding used in a property shorthand
  595. // { foo } -> { foo: foo.value }
  596. // skip for destructure patterns
  597. if (!(parent as any).inPattern) {
  598. s.appendLeft(id.end! + startOffset, `: ${id.name}.value`)
  599. }
  600. } else {
  601. s.appendLeft(id.end! + startOffset, '.value')
  602. }
  603. } else if (id.name[0] === '$' && refBindings[id.name.slice(1)]) {
  604. // $xxx raw ref access variables, remove the $ prefix
  605. s.remove(id.start! + startOffset, id.start! + startOffset + 1)
  606. }
  607. })
  608. }
  609. }
  610. }
  611. // 5. extract runtime props/emits code from setup context type
  612. if (optionsType) {
  613. for (const m of optionsType.members) {
  614. if (m.type === 'TSPropertySignature' && m.key.type === 'Identifier') {
  615. const typeNode = m.typeAnnotation!.typeAnnotation
  616. const typeString = scriptSetup.content.slice(
  617. typeNode.start!,
  618. typeNode.end!
  619. )
  620. const key = m.key.name
  621. if (key === 'props') {
  622. propsType = typeString
  623. if (typeNode.type === 'TSTypeLiteral') {
  624. extractRuntimeProps(typeNode, typeDeclaredProps, declaredTypes)
  625. } else {
  626. // TODO be able to trace references
  627. error(`props type must be an object literal type`, typeNode)
  628. }
  629. } else if (key === 'emit') {
  630. emitType = typeString
  631. if (
  632. typeNode.type === 'TSFunctionType' ||
  633. typeNode.type === 'TSUnionType'
  634. ) {
  635. extractRuntimeEmits(typeNode, typeDeclaredEmits)
  636. } else {
  637. // TODO be able to trace references
  638. error(`emit type must be a function type`, typeNode)
  639. }
  640. } else if (key === 'attrs') {
  641. attrsType = typeString
  642. } else if (key === 'slots') {
  643. slotsType = typeString
  644. } else {
  645. error(`invalid setup context property: "${key}"`, m.key)
  646. }
  647. }
  648. }
  649. }
  650. // 5. check useOptions args to make sure it doesn't reference setup scope
  651. // variables
  652. if (optionsArg) {
  653. walkIdentifiers(optionsArg, id => {
  654. if (setupBindings[id.name]) {
  655. error(
  656. `\`${USE_OPTIONS}()\` in <script setup> cannot reference locally ` +
  657. `declared variables because it will be hoisted outside of the ` +
  658. `setup() function. If your component options requires initialization ` +
  659. `in the module scope, use a separate normal <script> to export ` +
  660. `the options instead.`,
  661. id
  662. )
  663. }
  664. })
  665. }
  666. // 6. remove non-script content
  667. if (script) {
  668. if (startOffset < scriptStartOffset!) {
  669. // <script setup> before <script>
  670. s.remove(0, startOffset)
  671. s.remove(endOffset, scriptStartOffset!)
  672. s.remove(scriptEndOffset!, source.length)
  673. } else {
  674. // <script> before <script setup>
  675. s.remove(0, scriptStartOffset!)
  676. s.remove(scriptEndOffset!, startOffset)
  677. s.remove(endOffset, source.length)
  678. }
  679. } else {
  680. // only <script setup>
  681. s.remove(0, startOffset)
  682. s.remove(endOffset, source.length)
  683. }
  684. // 7. finalize setup argument signature.
  685. let args = optionsExp ? `__props, ${optionsExp}` : ``
  686. if (optionsExp && optionsType) {
  687. if (slotsType === 'Slots') {
  688. helperImports.add('Slots')
  689. }
  690. args += `: {
  691. props: ${propsType},
  692. emit: ${emitType},
  693. slots: ${slotsType},
  694. attrs: ${attrsType}
  695. }`
  696. }
  697. const allBindings: Record<string, any> = { ...setupBindings }
  698. for (const key in userImports) {
  699. allBindings[key] = true
  700. }
  701. // 9. inject `useCssVars` calls
  702. if (hasCssVars) {
  703. helperImports.add(`useCssVars`)
  704. for (const style of styles) {
  705. const vars = style.attrs.vars
  706. if (typeof vars === 'string') {
  707. s.prependRight(
  708. endOffset,
  709. `\n${genCssVarsCode(vars, !!style.scoped, allBindings)}`
  710. )
  711. }
  712. }
  713. }
  714. // 10. analyze binding metadata
  715. if (scriptAst) {
  716. Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst))
  717. }
  718. if (optionsType) {
  719. for (const key in typeDeclaredProps) {
  720. bindingMetadata[key] = BindingTypes.PROPS
  721. }
  722. }
  723. if (optionsArg) {
  724. Object.assign(bindingMetadata, analyzeBindingsFromOptions(optionsArg))
  725. }
  726. for (const [key, { source }] of Object.entries(userImports)) {
  727. bindingMetadata[key] = source.endsWith('.vue')
  728. ? BindingTypes.CONST
  729. : BindingTypes.SETUP
  730. }
  731. for (const key in setupBindings) {
  732. bindingMetadata[key] = setupBindings[key]
  733. }
  734. // 11. generate return statement
  735. let returned
  736. if (options.inlineTemplate) {
  737. if (sfc.template) {
  738. // inline render function mode - we are going to compile the template and
  739. // inline it right here
  740. const { code, preamble, tips, errors } = compileTemplate({
  741. ...options.templateOptions,
  742. filename,
  743. source: sfc.template.content,
  744. compilerOptions: {
  745. inline: true,
  746. bindingMetadata
  747. }
  748. // TODO source map
  749. })
  750. if (tips.length) {
  751. tips.forEach(warnOnce)
  752. }
  753. const err = errors[0]
  754. if (typeof err === 'string') {
  755. throw new Error(err)
  756. } else if (err) {
  757. throw err
  758. }
  759. if (preamble) {
  760. s.prepend(preamble)
  761. }
  762. returned = code
  763. } else {
  764. returned = `() => {}`
  765. }
  766. } else {
  767. // return bindings from setup
  768. returned = `{ ${Object.keys(allBindings).join(', ')} }`
  769. }
  770. s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)
  771. // 12. finalize default export
  772. let runtimeOptions = ``
  773. if (optionsArg) {
  774. runtimeOptions = `\n ${scriptSetup.content
  775. .slice(optionsArg.start! + 1, optionsArg.end! - 1)
  776. .trim()},`
  777. } else if (optionsType) {
  778. runtimeOptions =
  779. genRuntimeProps(typeDeclaredProps) + genRuntimeEmits(typeDeclaredEmits)
  780. }
  781. if (isTS) {
  782. // for TS, make sure the exported type is still valid type with
  783. // correct props information
  784. helperImports.add(`defineComponent`)
  785. // we have to use object spread for types to be merged properly
  786. // user's TS setting should compile it down to proper targets
  787. const def = defaultExport ? `\n ...${defaultTempVar},` : ``
  788. // wrap setup code with function.
  789. // export the content of <script setup> as a named export, `setup`.
  790. // this allows `import { setup } from '*.vue'` for testing purposes.
  791. s.prependLeft(
  792. startOffset,
  793. `\nexport default defineComponent({${def}${runtimeOptions}\n ${
  794. hasAwait ? `async ` : ``
  795. }setup(${args}) {\n`
  796. )
  797. s.append(`})`)
  798. } else {
  799. if (defaultExport) {
  800. // can't rely on spread operator in non ts mode
  801. s.prependLeft(
  802. startOffset,
  803. `\n${hasAwait ? `async ` : ``}function setup(${args}) {\n`
  804. )
  805. s.append(
  806. `/*#__PURE__*/ Object.assign(${defaultTempVar}, {${runtimeOptions}\n setup\n})\n` +
  807. `export default ${defaultTempVar}`
  808. )
  809. } else {
  810. s.prependLeft(
  811. startOffset,
  812. `\nexport default {${runtimeOptions}\n ` +
  813. `${hasAwait ? `async ` : ``}setup(${args}) {\n`
  814. )
  815. s.append(`}`)
  816. }
  817. }
  818. // 13. finalize Vue helper imports
  819. // TODO account for cases where user imports a helper with the same name
  820. // from a non-vue source
  821. const helpers = [...helperImports].filter(i => !userImports[i])
  822. if (helpers.length) {
  823. s.prepend(`import { ${helpers.join(', ')} } from 'vue'\n`)
  824. }
  825. s.trim()
  826. return {
  827. ...scriptSetup,
  828. bindings: bindingMetadata,
  829. content: s.toString(),
  830. map: (s.generateMap({
  831. source: filename,
  832. hires: true,
  833. includeContent: true
  834. }) as unknown) as RawSourceMap,
  835. scriptAst,
  836. scriptSetupAst
  837. }
  838. }
  839. function walkDeclaration(
  840. node: Declaration,
  841. bindings: Record<string, BindingTypes>
  842. ) {
  843. if (node.type === 'VariableDeclaration') {
  844. const isConst = node.kind === 'const'
  845. // export const foo = ...
  846. for (const { id, init } of node.declarations) {
  847. const isUseOptionsCall = !!(
  848. isConst &&
  849. init &&
  850. init.type === 'CallExpression' &&
  851. init.callee.type === 'Identifier' &&
  852. init.callee.name === USE_OPTIONS
  853. )
  854. if (id.type === 'Identifier') {
  855. bindings[id.name] =
  856. // if a declaration is a const literal, we can mark it so that
  857. // the generated render fn code doesn't need to unref() it
  858. isUseOptionsCall ||
  859. (isConst &&
  860. init!.type !== 'Identifier' && // const a = b
  861. init!.type !== 'CallExpression' && // const a = ref()
  862. init!.type !== 'MemberExpression') // const a = b.c
  863. ? BindingTypes.CONST
  864. : BindingTypes.SETUP
  865. } else if (id.type === 'ObjectPattern') {
  866. walkObjectPattern(id, bindings, isConst, isUseOptionsCall)
  867. } else if (id.type === 'ArrayPattern') {
  868. walkArrayPattern(id, bindings, isConst, isUseOptionsCall)
  869. }
  870. }
  871. } else if (
  872. node.type === 'FunctionDeclaration' ||
  873. node.type === 'ClassDeclaration'
  874. ) {
  875. // export function foo() {} / export class Foo {}
  876. // export declarations must be named.
  877. bindings[node.id!.name] = BindingTypes.CONST
  878. }
  879. }
  880. function walkObjectPattern(
  881. node: ObjectPattern,
  882. bindings: Record<string, BindingTypes>,
  883. isConst: boolean,
  884. isUseOptionsCall = false
  885. ) {
  886. for (const p of node.properties) {
  887. if (p.type === 'ObjectProperty') {
  888. // key can only be Identifier in ObjectPattern
  889. if (p.key.type === 'Identifier') {
  890. if (p.key === p.value) {
  891. // const { x } = ...
  892. bindings[p.key.name] = isUseOptionsCall
  893. ? BindingTypes.CONST
  894. : BindingTypes.SETUP
  895. } else {
  896. walkPattern(p.value, bindings, isConst, isUseOptionsCall)
  897. }
  898. }
  899. } else {
  900. // ...rest
  901. // argument can only be identifer when destructuring
  902. bindings[(p.argument as Identifier).name] = isConst
  903. ? BindingTypes.CONST
  904. : BindingTypes.SETUP
  905. }
  906. }
  907. }
  908. function walkArrayPattern(
  909. node: ArrayPattern,
  910. bindings: Record<string, BindingTypes>,
  911. isConst: boolean,
  912. isUseOptionsCall = false
  913. ) {
  914. for (const e of node.elements) {
  915. e && walkPattern(e, bindings, isConst, isUseOptionsCall)
  916. }
  917. }
  918. function walkPattern(
  919. node: Node,
  920. bindings: Record<string, BindingTypes>,
  921. isConst: boolean,
  922. isUseOptionsCall = false
  923. ) {
  924. if (node.type === 'Identifier') {
  925. bindings[node.name] = isUseOptionsCall
  926. ? BindingTypes.CONST
  927. : BindingTypes.SETUP
  928. } else if (node.type === 'RestElement') {
  929. // argument can only be identifer when destructuring
  930. bindings[(node.argument as Identifier).name] = isConst
  931. ? BindingTypes.CONST
  932. : BindingTypes.SETUP
  933. } else if (node.type === 'ObjectPattern') {
  934. walkObjectPattern(node, bindings, isConst)
  935. } else if (node.type === 'ArrayPattern') {
  936. walkArrayPattern(node, bindings, isConst)
  937. } else if (node.type === 'AssignmentPattern') {
  938. if (node.left.type === 'Identifier') {
  939. bindings[node.left.name] = isUseOptionsCall
  940. ? BindingTypes.CONST
  941. : BindingTypes.SETUP
  942. } else {
  943. walkPattern(node.left, bindings, isConst)
  944. }
  945. }
  946. }
  947. interface PropTypeData {
  948. key: string
  949. type: string[]
  950. required: boolean
  951. }
  952. function recordType(node: Node, declaredTypes: Record<string, string[]>) {
  953. if (node.type === 'TSInterfaceDeclaration') {
  954. declaredTypes[node.id.name] = [`Object`]
  955. } else if (node.type === 'TSTypeAliasDeclaration') {
  956. declaredTypes[node.id.name] = inferRuntimeType(
  957. node.typeAnnotation,
  958. declaredTypes
  959. )
  960. } else if (node.type === 'ExportNamedDeclaration' && node.declaration) {
  961. recordType(node.declaration, declaredTypes)
  962. }
  963. }
  964. function extractRuntimeProps(
  965. node: TSTypeLiteral,
  966. props: Record<string, PropTypeData>,
  967. declaredTypes: Record<string, string[]>
  968. ) {
  969. for (const m of node.members) {
  970. if (m.type === 'TSPropertySignature' && m.key.type === 'Identifier') {
  971. props[m.key.name] = {
  972. key: m.key.name,
  973. required: !m.optional,
  974. type:
  975. __DEV__ && m.typeAnnotation
  976. ? inferRuntimeType(m.typeAnnotation.typeAnnotation, declaredTypes)
  977. : [`null`]
  978. }
  979. }
  980. }
  981. }
  982. function inferRuntimeType(
  983. node: TSType,
  984. declaredTypes: Record<string, string[]>
  985. ): string[] {
  986. switch (node.type) {
  987. case 'TSStringKeyword':
  988. return ['String']
  989. case 'TSNumberKeyword':
  990. return ['Number']
  991. case 'TSBooleanKeyword':
  992. return ['Boolean']
  993. case 'TSObjectKeyword':
  994. return ['Object']
  995. case 'TSTypeLiteral':
  996. // TODO (nice to have) generate runtime property validation
  997. return ['Object']
  998. case 'TSFunctionType':
  999. return ['Function']
  1000. case 'TSArrayType':
  1001. case 'TSTupleType':
  1002. // TODO (nice to have) generate runtime element type/length checks
  1003. return ['Array']
  1004. case 'TSLiteralType':
  1005. switch (node.literal.type) {
  1006. case 'StringLiteral':
  1007. return ['String']
  1008. case 'BooleanLiteral':
  1009. return ['Boolean']
  1010. case 'NumericLiteral':
  1011. case 'BigIntLiteral':
  1012. return ['Number']
  1013. default:
  1014. return [`null`]
  1015. }
  1016. case 'TSTypeReference':
  1017. if (node.typeName.type === 'Identifier') {
  1018. if (declaredTypes[node.typeName.name]) {
  1019. return declaredTypes[node.typeName.name]
  1020. }
  1021. switch (node.typeName.name) {
  1022. case 'Array':
  1023. case 'Function':
  1024. case 'Object':
  1025. case 'Set':
  1026. case 'Map':
  1027. case 'WeakSet':
  1028. case 'WeakMap':
  1029. return [node.typeName.name]
  1030. case 'Record':
  1031. case 'Partial':
  1032. case 'Readonly':
  1033. case 'Pick':
  1034. case 'Omit':
  1035. case 'Exclude':
  1036. case 'Extract':
  1037. case 'Required':
  1038. case 'InstanceType':
  1039. return ['Object']
  1040. }
  1041. }
  1042. return [`null`]
  1043. case 'TSUnionType':
  1044. return [
  1045. ...new Set(
  1046. [].concat(node.types.map(t =>
  1047. inferRuntimeType(t, declaredTypes)
  1048. ) as any)
  1049. )
  1050. ]
  1051. case 'TSIntersectionType':
  1052. return ['Object']
  1053. default:
  1054. return [`null`] // no runtime check
  1055. }
  1056. }
  1057. function genRuntimeProps(props: Record<string, PropTypeData>) {
  1058. const keys = Object.keys(props)
  1059. if (!keys.length) {
  1060. return ``
  1061. }
  1062. if (!__DEV__) {
  1063. // production: generate array version only
  1064. return `\n props: [\n ${keys
  1065. .map(k => JSON.stringify(k))
  1066. .join(',\n ')}\n ] as unknown as undefined,`
  1067. }
  1068. return `\n props: {\n ${keys
  1069. .map(key => {
  1070. const { type, required } = props[key]
  1071. return `${key}: { type: ${toRuntimeTypeString(
  1072. type
  1073. )}, required: ${required} }`
  1074. })
  1075. .join(',\n ')}\n } as unknown as undefined,`
  1076. }
  1077. function toRuntimeTypeString(types: string[]) {
  1078. return types.some(t => t === 'null')
  1079. ? `null`
  1080. : types.length > 1
  1081. ? `[${types.join(', ')}]`
  1082. : types[0]
  1083. }
  1084. function extractRuntimeEmits(
  1085. node: TSFunctionType | TSUnionType,
  1086. emits: Set<string>
  1087. ) {
  1088. if (node.type === 'TSUnionType') {
  1089. for (let t of node.types) {
  1090. if (t.type === 'TSParenthesizedType') t = t.typeAnnotation
  1091. if (t.type === 'TSFunctionType') {
  1092. extractRuntimeEmits(t, emits)
  1093. }
  1094. }
  1095. return
  1096. }
  1097. const eventName = node.parameters[0]
  1098. if (
  1099. eventName.type === 'Identifier' &&
  1100. eventName.typeAnnotation &&
  1101. eventName.typeAnnotation.type === 'TSTypeAnnotation'
  1102. ) {
  1103. const typeNode = eventName.typeAnnotation.typeAnnotation
  1104. if (typeNode.type === 'TSLiteralType') {
  1105. emits.add(String(typeNode.literal.value))
  1106. } else if (typeNode.type === 'TSUnionType') {
  1107. for (const t of typeNode.types) {
  1108. if (t.type === 'TSLiteralType') {
  1109. emits.add(String(t.literal.value))
  1110. }
  1111. }
  1112. }
  1113. }
  1114. }
  1115. function genRuntimeEmits(emits: Set<string>) {
  1116. return emits.size
  1117. ? `\n emits: [${Array.from(emits)
  1118. .map(p => JSON.stringify(p))
  1119. .join(', ')}] as unknown as undefined,`
  1120. : ``
  1121. }
  1122. /**
  1123. * Walk an AST and find identifiers that are variable references.
  1124. * This is largely the same logic with `transformExpressions` in compiler-core
  1125. * but with some subtle differences as this needs to handle a wider range of
  1126. * possible syntax.
  1127. */
  1128. function walkIdentifiers(
  1129. root: Node,
  1130. onIdentifier: (node: Identifier, parent: Node) => void
  1131. ) {
  1132. const knownIds: Record<string, number> = Object.create(null)
  1133. ;(walk as any)(root, {
  1134. enter(node: Node & { scopeIds?: Set<string> }, parent: Node) {
  1135. if (node.type === 'Identifier') {
  1136. if (!knownIds[node.name] && isRefIdentifier(node, parent)) {
  1137. onIdentifier(node, parent)
  1138. }
  1139. } else if (isFunction(node)) {
  1140. // walk function expressions and add its arguments to known identifiers
  1141. // so that we don't prefix them
  1142. node.params.forEach(p =>
  1143. (walk as any)(p, {
  1144. enter(child: Node, parent: Node) {
  1145. if (
  1146. child.type === 'Identifier' &&
  1147. // do not record as scope variable if is a destructured key
  1148. !isStaticPropertyKey(child, parent) &&
  1149. // do not record if this is a default value
  1150. // assignment of a destructured variable
  1151. !(
  1152. parent &&
  1153. parent.type === 'AssignmentPattern' &&
  1154. parent.right === child
  1155. )
  1156. ) {
  1157. const { name } = child
  1158. if (node.scopeIds && node.scopeIds.has(name)) {
  1159. return
  1160. }
  1161. if (name in knownIds) {
  1162. knownIds[name]++
  1163. } else {
  1164. knownIds[name] = 1
  1165. }
  1166. ;(node.scopeIds || (node.scopeIds = new Set())).add(name)
  1167. }
  1168. }
  1169. })
  1170. )
  1171. } else if (
  1172. node.type === 'ObjectProperty' &&
  1173. parent.type === 'ObjectPattern'
  1174. ) {
  1175. // mark property in destructure pattern
  1176. ;(node as any).inPattern = true
  1177. }
  1178. },
  1179. leave(node: Node & { scopeIds?: Set<string> }) {
  1180. if (node.scopeIds) {
  1181. node.scopeIds.forEach((id: string) => {
  1182. knownIds[id]--
  1183. if (knownIds[id] === 0) {
  1184. delete knownIds[id]
  1185. }
  1186. })
  1187. }
  1188. }
  1189. })
  1190. }
  1191. function isRefIdentifier(id: Identifier, parent: Node) {
  1192. // declaration id
  1193. if (
  1194. (parent.type === 'VariableDeclarator' ||
  1195. parent.type === 'ClassDeclaration') &&
  1196. parent.id === id
  1197. ) {
  1198. return false
  1199. }
  1200. if (isFunction(parent)) {
  1201. // function decalration/expression id
  1202. if ((parent as any).id === id) {
  1203. return false
  1204. }
  1205. // params list
  1206. if (parent.params.includes(id)) {
  1207. return false
  1208. }
  1209. }
  1210. // property key
  1211. // this also covers object destructure pattern
  1212. if (isStaticPropertyKey(id, parent)) {
  1213. return false
  1214. }
  1215. // array destructure pattern
  1216. if (parent.type === 'ArrayPattern') {
  1217. return false
  1218. }
  1219. // member expression property
  1220. if (
  1221. (parent.type === 'MemberExpression' ||
  1222. parent.type === 'OptionalMemberExpression') &&
  1223. parent.property === id &&
  1224. !parent.computed
  1225. ) {
  1226. return false
  1227. }
  1228. // is a special keyword but parsed as identifier
  1229. if (id.name === 'arguments') {
  1230. return false
  1231. }
  1232. return true
  1233. }
  1234. const isStaticProperty = (node: Node): node is ObjectProperty =>
  1235. node &&
  1236. (node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
  1237. !node.computed
  1238. const isStaticPropertyKey = (node: Node, parent: Node) =>
  1239. isStaticProperty(parent) && parent.key === node
  1240. function isFunction(node: Node): node is FunctionNode {
  1241. return /Function(?:Expression|Declaration)$|Method$/.test(node.type)
  1242. }
  1243. function getObjectExpressionKeys(node: ObjectExpression): string[] {
  1244. const keys = []
  1245. for (const prop of node.properties) {
  1246. if (
  1247. (prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') &&
  1248. !prop.computed
  1249. ) {
  1250. if (prop.key.type === 'Identifier') {
  1251. keys.push(prop.key.name)
  1252. } else if (prop.key.type === 'StringLiteral') {
  1253. keys.push(prop.key.value)
  1254. }
  1255. }
  1256. }
  1257. return keys
  1258. }
  1259. function getArrayExpressionKeys(node: ArrayExpression): string[] {
  1260. const keys = []
  1261. for (const element of node.elements) {
  1262. if (element && element.type === 'StringLiteral') {
  1263. keys.push(element.value)
  1264. }
  1265. }
  1266. return keys
  1267. }
  1268. function getObjectOrArrayExpressionKeys(property: ObjectProperty): string[] {
  1269. if (property.value.type === 'ArrayExpression') {
  1270. return getArrayExpressionKeys(property.value)
  1271. }
  1272. if (property.value.type === 'ObjectExpression') {
  1273. return getObjectExpressionKeys(property.value)
  1274. }
  1275. return []
  1276. }
  1277. /**
  1278. * Analyze bindings in normal `<script>`
  1279. * Note that `compileScriptSetup` already analyzes bindings as part of its
  1280. * compilation process so this should only be used on single `<script>` SFCs.
  1281. */
  1282. function analyzeScriptBindings(ast: Statement[]): BindingMetadata {
  1283. for (const node of ast) {
  1284. if (
  1285. node.type === 'ExportDefaultDeclaration' &&
  1286. node.declaration.type === 'ObjectExpression'
  1287. ) {
  1288. return analyzeBindingsFromOptions(node.declaration)
  1289. }
  1290. }
  1291. return {}
  1292. }
  1293. function analyzeBindingsFromOptions(node: ObjectExpression): BindingMetadata {
  1294. const bindings: BindingMetadata = {}
  1295. for (const property of node.properties) {
  1296. if (
  1297. property.type === 'ObjectProperty' &&
  1298. !property.computed &&
  1299. property.key.type === 'Identifier'
  1300. ) {
  1301. // props
  1302. if (property.key.name === 'props') {
  1303. // props: ['foo']
  1304. // props: { foo: ... }
  1305. for (const key of getObjectOrArrayExpressionKeys(property)) {
  1306. bindings[key] = BindingTypes.PROPS
  1307. }
  1308. }
  1309. // inject
  1310. else if (property.key.name === 'inject') {
  1311. // inject: ['foo']
  1312. // inject: { foo: {} }
  1313. for (const key of getObjectOrArrayExpressionKeys(property)) {
  1314. bindings[key] = BindingTypes.OPTIONS
  1315. }
  1316. }
  1317. // computed & methods
  1318. else if (
  1319. property.value.type === 'ObjectExpression' &&
  1320. (property.key.name === 'computed' || property.key.name === 'methods')
  1321. ) {
  1322. // methods: { foo() {} }
  1323. // computed: { foo() {} }
  1324. for (const key of getObjectExpressionKeys(property.value)) {
  1325. bindings[key] = BindingTypes.OPTIONS
  1326. }
  1327. }
  1328. }
  1329. // setup & data
  1330. else if (
  1331. property.type === 'ObjectMethod' &&
  1332. property.key.type === 'Identifier' &&
  1333. (property.key.name === 'setup' || property.key.name === 'data')
  1334. ) {
  1335. for (const bodyItem of property.body.body) {
  1336. // setup() {
  1337. // return {
  1338. // foo: null
  1339. // }
  1340. // }
  1341. if (
  1342. bodyItem.type === 'ReturnStatement' &&
  1343. bodyItem.argument &&
  1344. bodyItem.argument.type === 'ObjectExpression'
  1345. ) {
  1346. for (const key of getObjectExpressionKeys(bodyItem.argument)) {
  1347. bindings[key] =
  1348. property.key.name === 'setup'
  1349. ? BindingTypes.SETUP
  1350. : BindingTypes.DATA
  1351. }
  1352. }
  1353. }
  1354. }
  1355. }
  1356. return bindings
  1357. }