compileScript.ts 54 KB

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