compileScript.ts 49 KB

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