compileScript.ts 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280
  1. import {
  2. BindingTypes,
  3. UNREF,
  4. isFunctionType,
  5. unwrapTSNode,
  6. walkIdentifiers,
  7. } from '@vue/compiler-dom'
  8. import {
  9. DEFAULT_FILENAME,
  10. type SFCDescriptor,
  11. type SFCScriptBlock,
  12. } from './parse'
  13. import type { ParserPlugin } from '@babel/parser'
  14. import { generateCodeFrame } from '@vue/shared'
  15. import type {
  16. ArrayPattern,
  17. CallExpression,
  18. Declaration,
  19. ExportSpecifier,
  20. Identifier,
  21. Node,
  22. ObjectPattern,
  23. Statement,
  24. } from '@babel/types'
  25. import { walk } from 'estree-walker'
  26. import type { RawSourceMap } from 'source-map-js'
  27. import {
  28. normalScriptDefaultVar,
  29. processNormalScript,
  30. } from './script/normalScript'
  31. import { CSS_VARS_HELPER, genCssVarsCode } from './style/cssVars'
  32. import {
  33. type SFCTemplateCompileOptions,
  34. compileTemplate,
  35. } from './compileTemplate'
  36. import { warnOnce } from './warn'
  37. import { transformDestructuredProps } from './script/definePropsDestructure'
  38. import { ScriptCompileContext } from './script/context'
  39. import {
  40. DEFINE_PROPS,
  41. WITH_DEFAULTS,
  42. genRuntimeProps,
  43. processDefineProps,
  44. } from './script/defineProps'
  45. import {
  46. DEFINE_EMITS,
  47. genRuntimeEmits,
  48. processDefineEmits,
  49. } from './script/defineEmits'
  50. import { DEFINE_EXPOSE, processDefineExpose } from './script/defineExpose'
  51. import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
  52. import { processDefineSlots } from './script/defineSlots'
  53. import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
  54. import { getImportedName, isCallOf, isLiteralNode } from './script/utils'
  55. import { analyzeScriptBindings } from './script/analyzeScriptBindings'
  56. import { isImportUsed } from './script/importUsageCheck'
  57. import { processAwait } from './script/topLevelAwait'
  58. export interface SFCScriptCompileOptions {
  59. /**
  60. * Scope ID for prefixing injected CSS variables.
  61. * This must be consistent with the `id` passed to `compileStyle`.
  62. */
  63. id: string
  64. /**
  65. * Production mode. Used to determine whether to generate hashed CSS variables
  66. */
  67. isProd?: boolean
  68. /**
  69. * Enable/disable source map. Defaults to true.
  70. */
  71. sourceMap?: boolean
  72. /**
  73. * https://babeljs.io/docs/en/babel-parser#plugins
  74. */
  75. babelParserPlugins?: ParserPlugin[]
  76. /**
  77. * A list of files to parse for global types to be made available for type
  78. * resolving in SFC macros. The list must be fully resolved file system paths.
  79. */
  80. globalTypeFiles?: string[]
  81. /**
  82. * Compile the template and inline the resulting render function
  83. * directly inside setup().
  84. * - Only affects `<script setup>`
  85. * - This should only be used in production because it prevents the template
  86. * from being hot-reloaded separately from component state.
  87. */
  88. inlineTemplate?: boolean
  89. /**
  90. * Generate the final component as a variable instead of default export.
  91. * This is useful in e.g. @vitejs/plugin-vue where the script needs to be
  92. * placed inside the main module.
  93. */
  94. genDefaultAs?: string
  95. /**
  96. * Options for template compilation when inlining. Note these are options that
  97. * would normally be passed to `compiler-sfc`'s own `compileTemplate()`, not
  98. * options passed to `compiler-dom`.
  99. */
  100. templateOptions?: Partial<SFCTemplateCompileOptions>
  101. /**
  102. * Hoist <script setup> static constants.
  103. * - Only enables when one `<script setup>` exists.
  104. * @default true
  105. */
  106. hoistStatic?: boolean
  107. /**
  108. * (**Experimental**) Enable reactive destructure for `defineProps`
  109. * @default false
  110. */
  111. propsDestructure?: boolean
  112. /**
  113. * File system access methods to be used when resolving types
  114. * imported in SFC macros. Defaults to ts.sys in Node.js, can be overwritten
  115. * to use a virtual file system for use in browsers (e.g. in REPLs)
  116. */
  117. fs?: {
  118. fileExists(file: string): boolean
  119. readFile(file: string): string | undefined
  120. realpath?(file: string): string
  121. }
  122. /**
  123. * Transform Vue SFCs into custom elements.
  124. */
  125. customElement?: boolean | ((filename: string) => boolean)
  126. /**
  127. * Force to use of Vapor mode.
  128. */
  129. vapor?: boolean
  130. }
  131. export interface ImportBinding {
  132. isType: boolean
  133. imported: string
  134. local: string
  135. source: string
  136. isFromSetup: boolean
  137. isUsedInTemplate: boolean
  138. }
  139. /**
  140. * Compile `<script setup>`
  141. * It requires the whole SFC descriptor because we need to handle and merge
  142. * normal `<script>` + `<script setup>` if both are present.
  143. */
  144. export function compileScript(
  145. sfc: SFCDescriptor,
  146. options: SFCScriptCompileOptions,
  147. ): SFCScriptBlock {
  148. if (!options.id) {
  149. warnOnce(
  150. `compileScript now requires passing the \`id\` option.\n` +
  151. `Upgrade your vite or vue-loader version for compatibility with ` +
  152. `the latest experimental proposals.`,
  153. )
  154. }
  155. const ctx = new ScriptCompileContext(sfc, options)
  156. const { script, scriptSetup, source, filename } = sfc
  157. const hoistStatic = options.hoistStatic !== false && !script
  158. const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''
  159. const scriptLang = script && script.lang
  160. const scriptSetupLang = scriptSetup && scriptSetup.lang
  161. const vapor = sfc.vapor || options.vapor
  162. let refBindings: string[] | undefined
  163. if (!scriptSetup) {
  164. if (!script) {
  165. throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
  166. }
  167. // normal <script> only
  168. return processNormalScript(ctx, scopeId)
  169. }
  170. if (script && scriptLang !== scriptSetupLang) {
  171. throw new Error(
  172. `[@vue/compiler-sfc] <script> and <script setup> must have the same ` +
  173. `language type.`,
  174. )
  175. }
  176. if (scriptSetupLang && !ctx.isJS && !ctx.isTS) {
  177. // do not process non js/ts script blocks
  178. return scriptSetup
  179. }
  180. // metadata that needs to be returned
  181. // const ctx.bindingMetadata: BindingMetadata = {}
  182. const scriptBindings: Record<string, BindingTypes> = Object.create(null)
  183. const setupBindings: Record<string, BindingTypes> = Object.create(null)
  184. let defaultExport: Node | undefined
  185. let hasAwait = false
  186. let hasInlinedSsrRenderFn = false
  187. // string offsets
  188. const startOffset = ctx.startOffset!
  189. const endOffset = ctx.endOffset!
  190. const scriptStartOffset = script && script.loc.start.offset
  191. const scriptEndOffset = script && script.loc.end.offset
  192. function hoistNode(node: Statement) {
  193. const start = node.start! + startOffset
  194. let end = node.end! + startOffset
  195. // locate comment
  196. if (node.trailingComments && node.trailingComments.length > 0) {
  197. const lastCommentNode =
  198. node.trailingComments[node.trailingComments.length - 1]
  199. end = lastCommentNode.end! + startOffset
  200. }
  201. // locate the end of whitespace between this statement and the next
  202. while (end <= source.length) {
  203. if (!/\s/.test(source.charAt(end))) {
  204. break
  205. }
  206. end++
  207. }
  208. ctx.s.move(start, end, 0)
  209. }
  210. function registerUserImport(
  211. source: string,
  212. local: string,
  213. imported: string,
  214. isType: boolean,
  215. isFromSetup: boolean,
  216. needTemplateUsageCheck: boolean,
  217. ) {
  218. // template usage check is only needed in non-inline mode, so we can skip
  219. // the work if inlineTemplate is true.
  220. let isUsedInTemplate = needTemplateUsageCheck
  221. if (
  222. needTemplateUsageCheck &&
  223. ctx.isTS &&
  224. sfc.template &&
  225. !sfc.template.src &&
  226. !sfc.template.lang
  227. ) {
  228. isUsedInTemplate = isImportUsed(local, sfc)
  229. }
  230. ctx.userImports[local] = {
  231. isType,
  232. imported,
  233. local,
  234. source,
  235. isFromSetup,
  236. isUsedInTemplate,
  237. }
  238. }
  239. function checkInvalidScopeReference(node: Node | undefined, method: string) {
  240. if (!node) return
  241. walkIdentifiers(node, id => {
  242. const binding = setupBindings[id.name]
  243. if (binding && binding !== BindingTypes.LITERAL_CONST) {
  244. ctx.error(
  245. `\`${method}()\` in <script setup> cannot reference locally ` +
  246. `declared variables because it will be hoisted outside of the ` +
  247. `setup() function. If your component options require initialization ` +
  248. `in the module scope, use a separate normal <script> to export ` +
  249. `the options instead.`,
  250. id,
  251. )
  252. }
  253. })
  254. }
  255. const scriptAst = ctx.scriptAst
  256. const scriptSetupAst = ctx.scriptSetupAst!
  257. // 1.1 walk import declarations of <script>
  258. if (scriptAst) {
  259. for (const node of scriptAst.body) {
  260. if (node.type === 'ImportDeclaration') {
  261. // record imports for dedupe
  262. for (const specifier of node.specifiers) {
  263. const imported = getImportedName(specifier)
  264. registerUserImport(
  265. node.source.value,
  266. specifier.local.name,
  267. imported,
  268. node.importKind === 'type' ||
  269. (specifier.type === 'ImportSpecifier' &&
  270. specifier.importKind === 'type'),
  271. false,
  272. !options.inlineTemplate,
  273. )
  274. }
  275. }
  276. }
  277. }
  278. // 1.2 walk import declarations of <script setup>
  279. for (const node of scriptSetupAst.body) {
  280. if (node.type === 'ImportDeclaration') {
  281. // import declarations are moved to top
  282. hoistNode(node)
  283. // dedupe imports
  284. let removed = 0
  285. const removeSpecifier = (i: number) => {
  286. const removeLeft = i > removed
  287. removed++
  288. const current = node.specifiers[i]
  289. const next = node.specifiers[i + 1]
  290. ctx.s.remove(
  291. removeLeft
  292. ? node.specifiers[i - 1].end! + startOffset
  293. : current.start! + startOffset,
  294. next && !removeLeft
  295. ? next.start! + startOffset
  296. : current.end! + startOffset,
  297. )
  298. }
  299. for (let i = 0; i < node.specifiers.length; i++) {
  300. const specifier = node.specifiers[i]
  301. const local = specifier.local.name
  302. const imported = getImportedName(specifier)
  303. const source = node.source.value
  304. const existing = ctx.userImports[local]
  305. if (
  306. source === 'vue' &&
  307. (imported === DEFINE_PROPS ||
  308. imported === DEFINE_EMITS ||
  309. imported === DEFINE_EXPOSE)
  310. ) {
  311. warnOnce(
  312. `\`${imported}\` is a compiler macro and no longer needs to be imported.`,
  313. )
  314. removeSpecifier(i)
  315. } else if (existing) {
  316. if (existing.source === source && existing.imported === imported) {
  317. // already imported in <script setup>, dedupe
  318. removeSpecifier(i)
  319. } else {
  320. ctx.error(
  321. `different imports aliased to same local name.`,
  322. specifier,
  323. )
  324. }
  325. } else {
  326. registerUserImport(
  327. source,
  328. local,
  329. imported,
  330. node.importKind === 'type' ||
  331. (specifier.type === 'ImportSpecifier' &&
  332. specifier.importKind === 'type'),
  333. true,
  334. !options.inlineTemplate,
  335. )
  336. }
  337. }
  338. if (node.specifiers.length && removed === node.specifiers.length) {
  339. ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
  340. }
  341. }
  342. }
  343. // 1.3 resolve possible user import alias of `ref` and `reactive`
  344. const vueImportAliases: Record<string, string> = {}
  345. for (const key in ctx.userImports) {
  346. const { source, imported, local } = ctx.userImports[key]
  347. if (['vue', 'vue/vapor'].includes(source))
  348. vueImportAliases[imported] = local
  349. }
  350. // 2.1 process normal <script> body
  351. if (script && scriptAst) {
  352. for (const node of scriptAst.body) {
  353. if (node.type === 'ExportDefaultDeclaration') {
  354. // export default
  355. defaultExport = node
  356. // check if user has manually specified `name` or 'render` option in
  357. // export default
  358. // if has name, skip name inference
  359. // if has render and no template, generate return object instead of
  360. // empty render function (#4980)
  361. let optionProperties
  362. if (defaultExport.declaration.type === 'ObjectExpression') {
  363. optionProperties = defaultExport.declaration.properties
  364. } else if (
  365. defaultExport.declaration.type === 'CallExpression' &&
  366. defaultExport.declaration.arguments[0] &&
  367. defaultExport.declaration.arguments[0].type === 'ObjectExpression'
  368. ) {
  369. optionProperties = defaultExport.declaration.arguments[0].properties
  370. }
  371. if (optionProperties) {
  372. for (const p of optionProperties) {
  373. if (
  374. p.type === 'ObjectProperty' &&
  375. p.key.type === 'Identifier' &&
  376. p.key.name === 'name'
  377. ) {
  378. ctx.hasDefaultExportName = true
  379. }
  380. if (
  381. (p.type === 'ObjectMethod' || p.type === 'ObjectProperty') &&
  382. p.key.type === 'Identifier' &&
  383. p.key.name === 'render'
  384. ) {
  385. // TODO warn when we provide a better way to do it?
  386. ctx.hasDefaultExportRender = true
  387. }
  388. }
  389. }
  390. // export default { ... } --> const __default__ = { ... }
  391. const start = node.start! + scriptStartOffset!
  392. const end = node.declaration.start! + scriptStartOffset!
  393. ctx.s.overwrite(start, end, `const ${normalScriptDefaultVar} = `)
  394. } else if (node.type === 'ExportNamedDeclaration') {
  395. const defaultSpecifier = node.specifiers.find(
  396. s =>
  397. s.exported.type === 'Identifier' && s.exported.name === 'default',
  398. ) as ExportSpecifier
  399. if (defaultSpecifier) {
  400. defaultExport = node
  401. // 1. remove specifier
  402. if (node.specifiers.length > 1) {
  403. ctx.s.remove(
  404. defaultSpecifier.start! + scriptStartOffset!,
  405. defaultSpecifier.end! + scriptStartOffset!,
  406. )
  407. } else {
  408. ctx.s.remove(
  409. node.start! + scriptStartOffset!,
  410. node.end! + scriptStartOffset!,
  411. )
  412. }
  413. if (node.source) {
  414. // export { x as default } from './x'
  415. // rewrite to `import { x as __default__ } from './x'` and
  416. // add to top
  417. ctx.s.prepend(
  418. `import { ${defaultSpecifier.local.name} as ${normalScriptDefaultVar} } from '${node.source.value}'\n`,
  419. )
  420. } else {
  421. // export { x as default }
  422. // rewrite to `const __default__ = x` and move to end
  423. ctx.s.appendLeft(
  424. scriptEndOffset!,
  425. `\nconst ${normalScriptDefaultVar} = ${defaultSpecifier.local.name}\n`,
  426. )
  427. }
  428. }
  429. if (node.declaration) {
  430. walkDeclaration(
  431. 'script',
  432. node.declaration,
  433. scriptBindings,
  434. vueImportAliases,
  435. hoistStatic,
  436. )
  437. }
  438. } else if (
  439. (node.type === 'VariableDeclaration' ||
  440. node.type === 'FunctionDeclaration' ||
  441. node.type === 'ClassDeclaration' ||
  442. node.type === 'TSEnumDeclaration') &&
  443. !node.declare
  444. ) {
  445. walkDeclaration(
  446. 'script',
  447. node,
  448. scriptBindings,
  449. vueImportAliases,
  450. hoistStatic,
  451. )
  452. }
  453. }
  454. // <script> after <script setup>
  455. // we need to move the block up so that `const __default__` is
  456. // declared before being used in the actual component definition
  457. if (scriptStartOffset! > startOffset) {
  458. // if content doesn't end with newline, add one
  459. if (!/\n$/.test(script.content.trim())) {
  460. ctx.s.appendLeft(scriptEndOffset!, `\n`)
  461. }
  462. ctx.s.move(scriptStartOffset!, scriptEndOffset!, 0)
  463. }
  464. }
  465. // 2.2 process <script setup> body
  466. for (const node of scriptSetupAst.body) {
  467. if (node.type === 'ExpressionStatement') {
  468. const expr = unwrapTSNode(node.expression)
  469. // process `defineProps` and `defineEmit(s)` calls
  470. if (
  471. processDefineProps(ctx, expr) ||
  472. processDefineEmits(ctx, expr) ||
  473. processDefineOptions(ctx, expr) ||
  474. processDefineSlots(ctx, expr)
  475. ) {
  476. ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
  477. } else if (processDefineExpose(ctx, expr)) {
  478. // defineExpose({}) -> expose({})
  479. const callee = (expr as CallExpression).callee
  480. ctx.s.overwrite(
  481. callee.start! + startOffset,
  482. callee.end! + startOffset,
  483. '__expose',
  484. )
  485. } else {
  486. processDefineModel(ctx, expr)
  487. }
  488. }
  489. if (node.type === 'VariableDeclaration' && !node.declare) {
  490. const total = node.declarations.length
  491. let left = total
  492. let lastNonRemoved: number | undefined
  493. for (let i = 0; i < total; i++) {
  494. const decl = node.declarations[i]
  495. const init = decl.init && unwrapTSNode(decl.init)
  496. if (init) {
  497. if (processDefineOptions(ctx, init)) {
  498. ctx.error(
  499. `${DEFINE_OPTIONS}() has no returning value, it cannot be assigned.`,
  500. node,
  501. )
  502. }
  503. // defineProps / defineEmits
  504. const isDefineProps = processDefineProps(ctx, init, decl.id)
  505. const isDefineEmits =
  506. !isDefineProps && processDefineEmits(ctx, init, decl.id)
  507. !isDefineEmits &&
  508. (processDefineSlots(ctx, init, decl.id) ||
  509. processDefineModel(ctx, init, decl.id))
  510. if (
  511. isDefineProps &&
  512. !ctx.propsDestructureRestId &&
  513. ctx.propsDestructureDecl
  514. ) {
  515. if (left === 1) {
  516. ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
  517. } else {
  518. let start = decl.start! + startOffset
  519. let end = decl.end! + startOffset
  520. if (i === total - 1) {
  521. // last one, locate the end of the last one that is not removed
  522. // if we arrive at this branch, there must have been a
  523. // non-removed decl before us, so lastNonRemoved is non-null.
  524. start = node.declarations[lastNonRemoved!].end! + startOffset
  525. } else {
  526. // not the last one, locate the start of the next
  527. end = node.declarations[i + 1].start! + startOffset
  528. }
  529. ctx.s.remove(start, end)
  530. left--
  531. }
  532. } else if (isDefineEmits) {
  533. ctx.s.overwrite(
  534. startOffset + init.start!,
  535. startOffset + init.end!,
  536. '__emit',
  537. )
  538. } else {
  539. lastNonRemoved = i
  540. }
  541. }
  542. }
  543. }
  544. let isAllLiteral = false
  545. // walk declarations to record declared bindings
  546. if (
  547. (node.type === 'VariableDeclaration' ||
  548. node.type === 'FunctionDeclaration' ||
  549. node.type === 'ClassDeclaration' ||
  550. node.type === 'TSEnumDeclaration') &&
  551. !node.declare
  552. ) {
  553. isAllLiteral = walkDeclaration(
  554. 'scriptSetup',
  555. node,
  556. setupBindings,
  557. vueImportAliases,
  558. hoistStatic,
  559. )
  560. }
  561. // hoist literal constants
  562. if (hoistStatic && isAllLiteral) {
  563. hoistNode(node)
  564. }
  565. // walk statements & named exports / variable declarations for top level
  566. // await
  567. if (
  568. (node.type === 'VariableDeclaration' && !node.declare) ||
  569. node.type.endsWith('Statement')
  570. ) {
  571. const scope: Statement[][] = [scriptSetupAst.body]
  572. walk(node, {
  573. enter(child: Node, parent: Node | undefined) {
  574. if (isFunctionType(child)) {
  575. this.skip()
  576. }
  577. if (child.type === 'BlockStatement') {
  578. scope.push(child.body)
  579. }
  580. if (child.type === 'AwaitExpression') {
  581. hasAwait = true
  582. // if the await expression is an expression statement and
  583. // - is in the root scope
  584. // - or is not the first statement in a nested block scope
  585. // then it needs a semicolon before the generated code.
  586. const currentScope = scope[scope.length - 1]
  587. const needsSemi = currentScope.some((n, i) => {
  588. return (
  589. (scope.length === 1 || i > 0) &&
  590. n.type === 'ExpressionStatement' &&
  591. n.start === child.start
  592. )
  593. })
  594. processAwait(
  595. ctx,
  596. child,
  597. needsSemi,
  598. parent!.type === 'ExpressionStatement',
  599. )
  600. }
  601. },
  602. exit(node: Node) {
  603. if (node.type === 'BlockStatement') scope.pop()
  604. },
  605. })
  606. }
  607. if (
  608. (node.type === 'ExportNamedDeclaration' && node.exportKind !== 'type') ||
  609. node.type === 'ExportAllDeclaration' ||
  610. node.type === 'ExportDefaultDeclaration'
  611. ) {
  612. ctx.error(
  613. `<script setup> cannot contain ES module exports. ` +
  614. `If you are using a previous version of <script setup>, please ` +
  615. `consult the updated RFC at https://github.com/vuejs/rfcs/pull/227.`,
  616. node,
  617. )
  618. }
  619. if (ctx.isTS) {
  620. // move all Type declarations to outer scope
  621. if (
  622. node.type.startsWith('TS') ||
  623. (node.type === 'ExportNamedDeclaration' &&
  624. node.exportKind === 'type') ||
  625. (node.type === 'VariableDeclaration' && node.declare)
  626. ) {
  627. if (node.type !== 'TSEnumDeclaration') {
  628. hoistNode(node)
  629. }
  630. }
  631. }
  632. }
  633. // 3 props destructure transform
  634. if (ctx.propsDestructureDecl) {
  635. transformDestructuredProps(ctx, vueImportAliases)
  636. }
  637. // 4. check macro args to make sure it doesn't reference setup scope
  638. // variables
  639. checkInvalidScopeReference(ctx.propsRuntimeDecl, DEFINE_PROPS)
  640. checkInvalidScopeReference(ctx.propsRuntimeDefaults, DEFINE_PROPS)
  641. checkInvalidScopeReference(ctx.propsDestructureDecl, DEFINE_PROPS)
  642. checkInvalidScopeReference(ctx.emitsRuntimeDecl, DEFINE_EMITS)
  643. checkInvalidScopeReference(ctx.optionsRuntimeDecl, DEFINE_OPTIONS)
  644. for (const { runtimeOptionNodes } of Object.values(ctx.modelDecls)) {
  645. for (const node of runtimeOptionNodes) {
  646. checkInvalidScopeReference(node, DEFINE_MODEL)
  647. }
  648. }
  649. // 5. remove non-script content
  650. if (script) {
  651. if (startOffset < scriptStartOffset!) {
  652. // <script setup> before <script>
  653. ctx.s.remove(0, startOffset)
  654. ctx.s.remove(endOffset, scriptStartOffset!)
  655. ctx.s.remove(scriptEndOffset!, source.length)
  656. } else {
  657. // <script> before <script setup>
  658. ctx.s.remove(0, scriptStartOffset!)
  659. ctx.s.remove(scriptEndOffset!, startOffset)
  660. ctx.s.remove(endOffset, source.length)
  661. }
  662. } else {
  663. // only <script setup>
  664. ctx.s.remove(0, startOffset)
  665. ctx.s.remove(endOffset, source.length)
  666. }
  667. // 6. analyze binding metadata
  668. // `defineProps` & `defineModel` also register props bindings
  669. if (scriptAst) {
  670. Object.assign(ctx.bindingMetadata, analyzeScriptBindings(scriptAst.body))
  671. }
  672. for (const [key, { isType, imported, source }] of Object.entries(
  673. ctx.userImports,
  674. )) {
  675. if (isType) continue
  676. ctx.bindingMetadata[key] =
  677. imported === '*' ||
  678. (imported === 'default' && source.endsWith('.vue')) ||
  679. ['vue', 'vue/vapor'].includes(source)
  680. ? BindingTypes.SETUP_CONST
  681. : BindingTypes.SETUP_MAYBE_REF
  682. }
  683. for (const key in scriptBindings) {
  684. ctx.bindingMetadata[key] = scriptBindings[key]
  685. }
  686. for (const key in setupBindings) {
  687. ctx.bindingMetadata[key] = setupBindings[key]
  688. }
  689. // known ref bindings
  690. if (refBindings) {
  691. for (const key of refBindings) {
  692. ctx.bindingMetadata[key] = BindingTypes.SETUP_REF
  693. }
  694. }
  695. // 7. inject `useCssVars` calls
  696. if (
  697. sfc.cssVars.length &&
  698. // no need to do this when targeting SSR
  699. !options.templateOptions?.ssr
  700. ) {
  701. ctx.helperImports.add(CSS_VARS_HELPER)
  702. ctx.helperImports.add('unref')
  703. ctx.s.prependLeft(
  704. startOffset,
  705. `\n${genCssVarsCode(
  706. sfc.cssVars,
  707. ctx.bindingMetadata,
  708. scopeId,
  709. !!options.isProd,
  710. )}\n`,
  711. )
  712. }
  713. // 8. finalize setup() argument signature
  714. let args = `__props`
  715. if (ctx.propsTypeDecl) {
  716. // mark as any and only cast on assignment
  717. // since the user defined complex types may be incompatible with the
  718. // inferred type from generated runtime declarations
  719. args += `: any`
  720. }
  721. // inject user assignment of props
  722. // we use a default __props so that template expressions referencing props
  723. // can use it directly
  724. if (ctx.propsDecl) {
  725. if (ctx.propsDestructureRestId) {
  726. ctx.s.overwrite(
  727. startOffset + ctx.propsCall!.start!,
  728. startOffset + ctx.propsCall!.end!,
  729. `${ctx.helper(`createPropsRestProxy`)}(__props, ${JSON.stringify(
  730. Object.keys(ctx.propsDestructuredBindings),
  731. )})`,
  732. )
  733. ctx.s.overwrite(
  734. startOffset + ctx.propsDestructureDecl!.start!,
  735. startOffset + ctx.propsDestructureDecl!.end!,
  736. ctx.propsDestructureRestId,
  737. )
  738. } else if (!ctx.propsDestructureDecl) {
  739. ctx.s.overwrite(
  740. startOffset + ctx.propsCall!.start!,
  741. startOffset + ctx.propsCall!.end!,
  742. '__props',
  743. )
  744. }
  745. }
  746. // inject temp variables for async context preservation
  747. if (hasAwait) {
  748. const any = ctx.isTS ? `: any` : ``
  749. ctx.s.prependLeft(startOffset, `\nlet __temp${any}, __restore${any}\n`)
  750. }
  751. const destructureElements =
  752. ctx.hasDefineExposeCall || !options.inlineTemplate
  753. ? [`expose: __expose`]
  754. : []
  755. if (ctx.emitDecl) {
  756. destructureElements.push(`emit: __emit`)
  757. }
  758. if (destructureElements.length) {
  759. args += `, { ${destructureElements.join(', ')} }`
  760. }
  761. // 9. generate return statement
  762. let returned
  763. if (
  764. !options.inlineTemplate ||
  765. (!sfc.template && ctx.hasDefaultExportRender)
  766. ) {
  767. // non-inline mode, or has manual render in normal <script>
  768. // return bindings from script and script setup
  769. const allBindings: Record<string, any> = {
  770. ...scriptBindings,
  771. ...setupBindings,
  772. }
  773. for (const key in ctx.userImports) {
  774. if (
  775. !ctx.userImports[key].isType &&
  776. ctx.userImports[key].isUsedInTemplate
  777. ) {
  778. allBindings[key] = true
  779. }
  780. }
  781. returned = `{ `
  782. for (const key in allBindings) {
  783. if (
  784. allBindings[key] === true &&
  785. !['vue', 'vue/vapor'].includes(ctx.userImports[key].source) &&
  786. !ctx.userImports[key].source.endsWith('.vue')
  787. ) {
  788. // generate getter for import bindings
  789. // skip vue imports since we know they will never change
  790. returned += `get ${key}() { return ${key} }, `
  791. } else if (ctx.bindingMetadata[key] === BindingTypes.SETUP_LET) {
  792. // local let binding, also add setter
  793. const setArg = key === 'v' ? `_v` : `v`
  794. returned +=
  795. `get ${key}() { return ${key} }, ` +
  796. `set ${key}(${setArg}) { ${key} = ${setArg} }, `
  797. } else {
  798. returned += `${key}, `
  799. }
  800. }
  801. returned = returned.replace(/, $/, '') + ` }`
  802. } else {
  803. // inline mode
  804. if (sfc.template && !sfc.template.src) {
  805. if (options.templateOptions && options.templateOptions.ssr) {
  806. hasInlinedSsrRenderFn = true
  807. }
  808. // inline render function mode - we are going to compile the template and
  809. // inline it right here
  810. const { code, preamble, tips, errors, helpers } = compileTemplate({
  811. filename,
  812. ast: sfc.template.ast,
  813. source: sfc.template.content,
  814. inMap: sfc.template.map,
  815. ...options.templateOptions,
  816. id: scopeId,
  817. scoped: sfc.styles.some(s => s.scoped),
  818. isProd: options.isProd,
  819. ssrCssVars: sfc.cssVars,
  820. compilerOptions: {
  821. ...(options.templateOptions &&
  822. options.templateOptions.compilerOptions),
  823. inline: true,
  824. isTS: ctx.isTS,
  825. bindingMetadata: ctx.bindingMetadata,
  826. },
  827. })
  828. if (tips.length) {
  829. tips.forEach(warnOnce)
  830. }
  831. const err = errors[0]
  832. if (typeof err === 'string') {
  833. throw new Error(err)
  834. } else if (err) {
  835. if (err.loc) {
  836. err.message +=
  837. `\n\n` +
  838. sfc.filename +
  839. '\n' +
  840. generateCodeFrame(
  841. source,
  842. err.loc.start.offset,
  843. err.loc.end.offset,
  844. ) +
  845. `\n`
  846. }
  847. throw err
  848. }
  849. if (preamble) {
  850. ctx.s.prepend(preamble)
  851. }
  852. // avoid duplicated unref import
  853. // as this may get injected by the render function preamble OR the
  854. // css vars codegen
  855. if (helpers && helpers.has(UNREF)) {
  856. ctx.helperImports.delete('unref')
  857. }
  858. returned = code
  859. } else {
  860. returned = `() => {}`
  861. }
  862. }
  863. if (!options.inlineTemplate && !__TEST__) {
  864. // in non-inline mode, the `__isScriptSetup: true` flag is used by
  865. // componentPublicInstance proxy to allow properties that start with $ or _
  866. ctx.s.appendRight(
  867. endOffset,
  868. `\nconst __returned__ = ${returned}\n` +
  869. `Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })\n` +
  870. `return __returned__` +
  871. `\n}\n\n`,
  872. )
  873. } else {
  874. ctx.s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)
  875. }
  876. // 10. finalize default export
  877. const genDefaultAs = options.genDefaultAs
  878. ? `const ${options.genDefaultAs} =`
  879. : `export default`
  880. let runtimeOptions = ``
  881. if (vapor) {
  882. runtimeOptions += `\n vapor: true,`
  883. }
  884. if (!ctx.hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {
  885. const match = filename.match(/([^/\\]+)\.\w+$/)
  886. if (match) {
  887. runtimeOptions += `\n __name: '${match[1]}',`
  888. }
  889. }
  890. if (hasInlinedSsrRenderFn) {
  891. runtimeOptions += `\n __ssrInlineRender: true,`
  892. }
  893. const propsDecl = genRuntimeProps(ctx)
  894. if (propsDecl) runtimeOptions += `\n props: ${propsDecl},`
  895. const emitsDecl = genRuntimeEmits(ctx)
  896. if (emitsDecl) runtimeOptions += `\n emits: ${emitsDecl},`
  897. let definedOptions = ''
  898. if (ctx.optionsRuntimeDecl) {
  899. definedOptions = scriptSetup.content
  900. .slice(ctx.optionsRuntimeDecl.start!, ctx.optionsRuntimeDecl.end!)
  901. .trim()
  902. }
  903. // <script setup> components are closed by default. If the user did not
  904. // explicitly call `defineExpose`, call expose() with no args.
  905. const exposeCall =
  906. ctx.hasDefineExposeCall || options.inlineTemplate ? `` : ` __expose();\n`
  907. // wrap setup code with function.
  908. if (ctx.isTS) {
  909. // for TS, make sure the exported type is still valid type with
  910. // correct props information
  911. // we have to use object spread for types to be merged properly
  912. // user's TS setting should compile it down to proper targets
  913. // export default defineComponent({ ...__default__, ... })
  914. const def =
  915. (defaultExport ? `\n ...${normalScriptDefaultVar},` : ``) +
  916. (definedOptions ? `\n ...${definedOptions},` : '')
  917. ctx.s.prependLeft(
  918. startOffset,
  919. `\n${genDefaultAs} /*#__PURE__*/${ctx.helper(
  920. `defineComponent`,
  921. vapor,
  922. )}({${def}${runtimeOptions}\n ${
  923. hasAwait ? `async ` : ``
  924. }setup(${args}) {\n${exposeCall}`,
  925. )
  926. ctx.s.appendRight(endOffset, `})`)
  927. } else {
  928. if (defaultExport || definedOptions) {
  929. // without TS, can't rely on rest spread, so we use Object.assign
  930. // export default Object.assign(__default__, { ... })
  931. ctx.s.prependLeft(
  932. startOffset,
  933. `\n${genDefaultAs} /*#__PURE__*/Object.assign(${
  934. defaultExport ? `${normalScriptDefaultVar}, ` : ''
  935. }${definedOptions ? `${definedOptions}, ` : ''}{${runtimeOptions}\n ` +
  936. `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`,
  937. )
  938. ctx.s.appendRight(endOffset, `})`)
  939. } else {
  940. ctx.s.prependLeft(
  941. startOffset,
  942. `\n${genDefaultAs} {${runtimeOptions}\n ` +
  943. `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`,
  944. )
  945. ctx.s.appendRight(endOffset, `}`)
  946. }
  947. }
  948. // 11. finalize Vue helper imports
  949. if (ctx.helperImports.size > 0) {
  950. ctx.s.prepend(
  951. `import { ${[...ctx.helperImports]
  952. .map(h => `${h} as _${h}`)
  953. .join(', ')} } from 'vue'\n`,
  954. )
  955. }
  956. if (ctx.vaporHelperImports.size > 0) {
  957. ctx.s.prepend(
  958. `import { ${[...ctx.vaporHelperImports]
  959. .map(h => `${h} as _${h}`)
  960. .join(', ')} } from 'vue/vapor'\n`,
  961. )
  962. }
  963. return {
  964. ...scriptSetup,
  965. bindings: ctx.bindingMetadata,
  966. imports: ctx.userImports,
  967. content: ctx.s.toString(),
  968. map:
  969. options.sourceMap !== false
  970. ? (ctx.s.generateMap({
  971. source: filename,
  972. hires: true,
  973. includeContent: true,
  974. }) as unknown as RawSourceMap)
  975. : undefined,
  976. scriptAst: scriptAst?.body,
  977. scriptSetupAst: scriptSetupAst?.body,
  978. deps: ctx.deps ? [...ctx.deps] : undefined,
  979. }
  980. }
  981. function registerBinding(
  982. bindings: Record<string, BindingTypes>,
  983. node: Identifier,
  984. type: BindingTypes,
  985. ) {
  986. bindings[node.name] = type
  987. }
  988. function walkDeclaration(
  989. from: 'script' | 'scriptSetup',
  990. node: Declaration,
  991. bindings: Record<string, BindingTypes>,
  992. userImportAliases: Record<string, string>,
  993. hoistStatic: boolean,
  994. ): boolean {
  995. let isAllLiteral = false
  996. if (node.type === 'VariableDeclaration') {
  997. const isConst = node.kind === 'const'
  998. isAllLiteral =
  999. isConst &&
  1000. node.declarations.every(
  1001. decl => decl.id.type === 'Identifier' && isStaticNode(decl.init!),
  1002. )
  1003. // export const foo = ...
  1004. for (const { id, init: _init } of node.declarations) {
  1005. const init = _init && unwrapTSNode(_init)
  1006. const isDefineCall = !!(
  1007. isConst &&
  1008. isCallOf(
  1009. init,
  1010. c => c === DEFINE_PROPS || c === DEFINE_EMITS || c === WITH_DEFAULTS,
  1011. )
  1012. )
  1013. if (id.type === 'Identifier') {
  1014. let bindingType
  1015. const userReactiveBinding = userImportAliases['reactive']
  1016. if (
  1017. (hoistStatic || from === 'script') &&
  1018. (isAllLiteral || (isConst && isStaticNode(init!)))
  1019. ) {
  1020. bindingType = BindingTypes.LITERAL_CONST
  1021. } else if (isCallOf(init, userReactiveBinding)) {
  1022. // treat reactive() calls as let since it's meant to be mutable
  1023. bindingType = isConst
  1024. ? BindingTypes.SETUP_REACTIVE_CONST
  1025. : BindingTypes.SETUP_LET
  1026. } else if (
  1027. // if a declaration is a const literal, we can mark it so that
  1028. // the generated render fn code doesn't need to unref() it
  1029. isDefineCall ||
  1030. (isConst && canNeverBeRef(init!, userReactiveBinding))
  1031. ) {
  1032. bindingType = isCallOf(init, DEFINE_PROPS)
  1033. ? BindingTypes.SETUP_REACTIVE_CONST
  1034. : BindingTypes.SETUP_CONST
  1035. } else if (isConst) {
  1036. if (
  1037. isCallOf(
  1038. init,
  1039. m =>
  1040. m === userImportAliases['ref'] ||
  1041. m === userImportAliases['computed'] ||
  1042. m === userImportAliases['shallowRef'] ||
  1043. m === userImportAliases['customRef'] ||
  1044. m === userImportAliases['toRef'] ||
  1045. m === DEFINE_MODEL,
  1046. )
  1047. ) {
  1048. bindingType = BindingTypes.SETUP_REF
  1049. } else {
  1050. bindingType = BindingTypes.SETUP_MAYBE_REF
  1051. }
  1052. } else {
  1053. bindingType = BindingTypes.SETUP_LET
  1054. }
  1055. registerBinding(bindings, id, bindingType)
  1056. } else {
  1057. if (isCallOf(init, DEFINE_PROPS)) {
  1058. continue
  1059. }
  1060. if (id.type === 'ObjectPattern') {
  1061. walkObjectPattern(id, bindings, isConst, isDefineCall)
  1062. } else if (id.type === 'ArrayPattern') {
  1063. walkArrayPattern(id, bindings, isConst, isDefineCall)
  1064. }
  1065. }
  1066. }
  1067. } else if (node.type === 'TSEnumDeclaration') {
  1068. isAllLiteral = node.members.every(
  1069. member => !member.initializer || isStaticNode(member.initializer),
  1070. )
  1071. bindings[node.id!.name] = isAllLiteral
  1072. ? BindingTypes.LITERAL_CONST
  1073. : BindingTypes.SETUP_CONST
  1074. } else if (
  1075. node.type === 'FunctionDeclaration' ||
  1076. node.type === 'ClassDeclaration'
  1077. ) {
  1078. // export function foo() {} / export class Foo {}
  1079. // export declarations must be named.
  1080. bindings[node.id!.name] = BindingTypes.SETUP_CONST
  1081. }
  1082. return isAllLiteral
  1083. }
  1084. function walkObjectPattern(
  1085. node: ObjectPattern,
  1086. bindings: Record<string, BindingTypes>,
  1087. isConst: boolean,
  1088. isDefineCall = false,
  1089. ) {
  1090. for (const p of node.properties) {
  1091. if (p.type === 'ObjectProperty') {
  1092. if (p.key.type === 'Identifier' && p.key === p.value) {
  1093. // shorthand: const { x } = ...
  1094. const type = isDefineCall
  1095. ? BindingTypes.SETUP_CONST
  1096. : isConst
  1097. ? BindingTypes.SETUP_MAYBE_REF
  1098. : BindingTypes.SETUP_LET
  1099. registerBinding(bindings, p.key, type)
  1100. } else {
  1101. walkPattern(p.value, bindings, isConst, isDefineCall)
  1102. }
  1103. } else {
  1104. // ...rest
  1105. // argument can only be identifier when destructuring
  1106. const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET
  1107. registerBinding(bindings, p.argument as Identifier, type)
  1108. }
  1109. }
  1110. }
  1111. function walkArrayPattern(
  1112. node: ArrayPattern,
  1113. bindings: Record<string, BindingTypes>,
  1114. isConst: boolean,
  1115. isDefineCall = false,
  1116. ) {
  1117. for (const e of node.elements) {
  1118. e && walkPattern(e, bindings, isConst, isDefineCall)
  1119. }
  1120. }
  1121. function walkPattern(
  1122. node: Node,
  1123. bindings: Record<string, BindingTypes>,
  1124. isConst: boolean,
  1125. isDefineCall = false,
  1126. ) {
  1127. if (node.type === 'Identifier') {
  1128. const type = isDefineCall
  1129. ? BindingTypes.SETUP_CONST
  1130. : isConst
  1131. ? BindingTypes.SETUP_MAYBE_REF
  1132. : BindingTypes.SETUP_LET
  1133. registerBinding(bindings, node, type)
  1134. } else if (node.type === 'RestElement') {
  1135. // argument can only be identifier when destructuring
  1136. const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET
  1137. registerBinding(bindings, node.argument as Identifier, type)
  1138. } else if (node.type === 'ObjectPattern') {
  1139. walkObjectPattern(node, bindings, isConst)
  1140. } else if (node.type === 'ArrayPattern') {
  1141. walkArrayPattern(node, bindings, isConst)
  1142. } else if (node.type === 'AssignmentPattern') {
  1143. if (node.left.type === 'Identifier') {
  1144. const type = isDefineCall
  1145. ? BindingTypes.SETUP_CONST
  1146. : isConst
  1147. ? BindingTypes.SETUP_MAYBE_REF
  1148. : BindingTypes.SETUP_LET
  1149. registerBinding(bindings, node.left, type)
  1150. } else {
  1151. walkPattern(node.left, bindings, isConst)
  1152. }
  1153. }
  1154. }
  1155. function canNeverBeRef(node: Node, userReactiveImport?: string): boolean {
  1156. if (isCallOf(node, userReactiveImport)) {
  1157. return true
  1158. }
  1159. switch (node.type) {
  1160. case 'UnaryExpression':
  1161. case 'BinaryExpression':
  1162. case 'ArrayExpression':
  1163. case 'ObjectExpression':
  1164. case 'FunctionExpression':
  1165. case 'ArrowFunctionExpression':
  1166. case 'UpdateExpression':
  1167. case 'ClassExpression':
  1168. case 'TaggedTemplateExpression':
  1169. return true
  1170. case 'SequenceExpression':
  1171. return canNeverBeRef(
  1172. node.expressions[node.expressions.length - 1],
  1173. userReactiveImport,
  1174. )
  1175. default:
  1176. if (isLiteralNode(node)) {
  1177. return true
  1178. }
  1179. return false
  1180. }
  1181. }
  1182. function isStaticNode(node: Node): boolean {
  1183. node = unwrapTSNode(node)
  1184. switch (node.type) {
  1185. case 'UnaryExpression': // void 0, !true
  1186. return isStaticNode(node.argument)
  1187. case 'LogicalExpression': // 1 > 2
  1188. case 'BinaryExpression': // 1 + 2
  1189. return isStaticNode(node.left) && isStaticNode(node.right)
  1190. case 'ConditionalExpression': {
  1191. // 1 ? 2 : 3
  1192. return (
  1193. isStaticNode(node.test) &&
  1194. isStaticNode(node.consequent) &&
  1195. isStaticNode(node.alternate)
  1196. )
  1197. }
  1198. case 'SequenceExpression': // (1, 2)
  1199. case 'TemplateLiteral': // `foo${1}`
  1200. return node.expressions.every(expr => isStaticNode(expr))
  1201. case 'ParenthesizedExpression': // (1)
  1202. return isStaticNode(node.expression)
  1203. case 'StringLiteral':
  1204. case 'NumericLiteral':
  1205. case 'BooleanLiteral':
  1206. case 'NullLiteral':
  1207. case 'BigIntLiteral':
  1208. return true
  1209. }
  1210. return false
  1211. }