compileScript.ts 40 KB

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