compileScript.ts 44 KB

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