compileScript.ts 47 KB

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