compileScript.ts 75 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533
  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. unwrapTSNode
  14. } from '@vue/compiler-dom'
  15. import { DEFAULT_FILENAME, SFCDescriptor, SFCScriptBlock } from './parse'
  16. import {
  17. parse as _parse,
  18. parseExpression,
  19. ParserOptions,
  20. ParserPlugin
  21. } from '@babel/parser'
  22. import { camelize, capitalize, generateCodeFrame, makeMap } 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. TSTypeElement,
  41. AwaitExpression,
  42. Program,
  43. ObjectMethod,
  44. LVal,
  45. Expression,
  46. VariableDeclaration
  47. } from '@babel/types'
  48. import { walk } from 'estree-walker'
  49. import { RawSourceMap } from 'source-map'
  50. import {
  51. CSS_VARS_HELPER,
  52. genCssVarsCode,
  53. genNormalScriptCssVarsCode
  54. } from './cssVars'
  55. import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate'
  56. import { warnOnce } from './warn'
  57. import { rewriteDefaultAST } from './rewriteDefault'
  58. import { createCache } from './cache'
  59. import { shouldTransform, transformAST } from '@vue/reactivity-transform'
  60. // Special compiler macros
  61. const DEFINE_PROPS = 'defineProps'
  62. const DEFINE_EMITS = 'defineEmits'
  63. const DEFINE_EXPOSE = 'defineExpose'
  64. const WITH_DEFAULTS = 'withDefaults'
  65. const DEFINE_OPTIONS = 'defineOptions'
  66. // constants
  67. const DEFAULT_VAR = `__default__`
  68. const isBuiltInDir = makeMap(
  69. `once,memo,if,for,else,else-if,slot,text,html,on,bind,model,show,cloak,is`
  70. )
  71. export interface SFCScriptCompileOptions {
  72. /**
  73. * Scope ID for prefixing injected CSS variables.
  74. * This must be consistent with the `id` passed to `compileStyle`.
  75. */
  76. id: string
  77. /**
  78. * Production mode. Used to determine whether to generate hashed CSS variables
  79. */
  80. isProd?: boolean
  81. /**
  82. * Enable/disable source map. Defaults to true.
  83. */
  84. sourceMap?: boolean
  85. /**
  86. * https://babeljs.io/docs/en/babel-parser#plugins
  87. */
  88. babelParserPlugins?: ParserPlugin[]
  89. /**
  90. * (Experimental) Enable syntax transform for using refs without `.value` and
  91. * using destructured props with reactivity
  92. * @deprecated the Reactivity Transform proposal has been dropped. This
  93. * feature will be removed from Vue core in 3.4. If you intend to continue
  94. * using it, disable this and switch to the [Vue Macros implementation](https://vue-macros.sxzz.moe/features/reactivity-transform.html).
  95. */
  96. reactivityTransform?: boolean
  97. /**
  98. * Compile the template and inline the resulting render function
  99. * directly inside setup().
  100. * - Only affects `<script setup>`
  101. * - This should only be used in production because it prevents the template
  102. * from being hot-reloaded separately from component state.
  103. */
  104. inlineTemplate?: boolean
  105. /**
  106. * Options for template compilation when inlining. Note these are options that
  107. * would normally be passed to `compiler-sfc`'s own `compileTemplate()`, not
  108. * options passed to `compiler-dom`.
  109. */
  110. templateOptions?: Partial<SFCTemplateCompileOptions>
  111. /**
  112. * Hoist <script setup> static constants.
  113. * - Only enables when one `<script setup>` exists.
  114. * @default true
  115. */
  116. hoistStatic?: boolean
  117. }
  118. export interface ImportBinding {
  119. isType: boolean
  120. imported: string
  121. local: string
  122. source: string
  123. isFromSetup: boolean
  124. isUsedInTemplate: boolean
  125. }
  126. type FromNormalScript<T> = T & { __fromNormalScript?: boolean | null }
  127. type PropsDeclType = FromNormalScript<TSTypeLiteral | TSInterfaceBody>
  128. type EmitsDeclType = FromNormalScript<
  129. TSFunctionType | TSTypeLiteral | TSInterfaceBody
  130. >
  131. /**
  132. * Compile `<script setup>`
  133. * It requires the whole SFC descriptor because we need to handle and merge
  134. * normal `<script>` + `<script setup>` if both are present.
  135. */
  136. export function compileScript(
  137. sfc: SFCDescriptor,
  138. options: SFCScriptCompileOptions
  139. ): SFCScriptBlock {
  140. let { script, scriptSetup, source, filename } = sfc
  141. // feature flags
  142. // TODO remove support for deprecated options when out of experimental
  143. const enableReactivityTransform = !!options.reactivityTransform
  144. const enablePropsTransform = !!options.reactivityTransform
  145. const isProd = !!options.isProd
  146. const genSourceMap = options.sourceMap !== false
  147. const hoistStatic = options.hoistStatic !== false && !script
  148. let refBindings: string[] | undefined
  149. if (!options.id) {
  150. warnOnce(
  151. `compileScript now requires passing the \`id\` option.\n` +
  152. `Upgrade your vite or vue-loader version for compatibility with ` +
  153. `the latest experimental proposals.`
  154. )
  155. }
  156. const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''
  157. const cssVars = sfc.cssVars
  158. const scriptLang = script && script.lang
  159. const scriptSetupLang = scriptSetup && scriptSetup.lang
  160. const isJS =
  161. scriptLang === 'js' ||
  162. scriptLang === 'jsx' ||
  163. scriptSetupLang === 'js' ||
  164. scriptSetupLang === 'jsx'
  165. const isTS =
  166. scriptLang === 'ts' ||
  167. scriptLang === 'tsx' ||
  168. scriptSetupLang === 'ts' ||
  169. scriptSetupLang === 'tsx'
  170. // resolve parser plugins
  171. const plugins: ParserPlugin[] = []
  172. if (!isTS || scriptLang === 'tsx' || scriptSetupLang === 'tsx') {
  173. plugins.push('jsx')
  174. } else {
  175. // If don't match the case of adding jsx, should remove the jsx from the babelParserPlugins
  176. if (options.babelParserPlugins)
  177. options.babelParserPlugins = options.babelParserPlugins.filter(
  178. n => n !== 'jsx'
  179. )
  180. }
  181. if (options.babelParserPlugins) plugins.push(...options.babelParserPlugins)
  182. if (isTS) {
  183. plugins.push('typescript')
  184. if (!plugins.includes('decorators')) {
  185. plugins.push('decorators-legacy')
  186. }
  187. }
  188. if (!scriptSetup) {
  189. if (!script) {
  190. throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
  191. }
  192. if (scriptLang && !isJS && !isTS) {
  193. // do not process non js/ts script blocks
  194. return script
  195. }
  196. try {
  197. let content = script.content
  198. let map = script.map
  199. const scriptAst = _parse(content, {
  200. plugins,
  201. sourceType: 'module'
  202. }).program
  203. const bindings = analyzeScriptBindings(scriptAst.body)
  204. if (enableReactivityTransform && shouldTransform(content)) {
  205. const s = new MagicString(source)
  206. const startOffset = script.loc.start.offset
  207. const endOffset = script.loc.end.offset
  208. const { importedHelpers } = transformAST(scriptAst, s, startOffset)
  209. if (importedHelpers.length) {
  210. s.prepend(
  211. `import { ${importedHelpers
  212. .map(h => `${h} as _${h}`)
  213. .join(', ')} } from 'vue'\n`
  214. )
  215. }
  216. s.remove(0, startOffset)
  217. s.remove(endOffset, source.length)
  218. content = s.toString()
  219. if (genSourceMap) {
  220. map = s.generateMap({
  221. source: filename,
  222. hires: true,
  223. includeContent: true
  224. }) as unknown as RawSourceMap
  225. }
  226. }
  227. if (cssVars.length) {
  228. const s = new MagicString(content)
  229. rewriteDefaultAST(scriptAst.body, s, DEFAULT_VAR)
  230. content = s.toString()
  231. content += genNormalScriptCssVarsCode(
  232. cssVars,
  233. bindings,
  234. scopeId,
  235. isProd
  236. )
  237. content += `\nexport default ${DEFAULT_VAR}`
  238. }
  239. return {
  240. ...script,
  241. content,
  242. map,
  243. bindings,
  244. scriptAst: scriptAst.body
  245. }
  246. } catch (e: any) {
  247. // silently fallback if parse fails since user may be using custom
  248. // babel syntax
  249. return script
  250. }
  251. }
  252. if (script && scriptLang !== scriptSetupLang) {
  253. throw new Error(
  254. `[@vue/compiler-sfc] <script> and <script setup> must have the same ` +
  255. `language type.`
  256. )
  257. }
  258. if (scriptSetupLang && !isJS && !isTS) {
  259. // do not process non js/ts script blocks
  260. return scriptSetup
  261. }
  262. // metadata that needs to be returned
  263. const bindingMetadata: BindingMetadata = {}
  264. const helperImports: Set<string> = new Set()
  265. const userImports: Record<string, ImportBinding> = Object.create(null)
  266. const scriptBindings: Record<string, BindingTypes> = Object.create(null)
  267. const setupBindings: Record<string, BindingTypes> = Object.create(null)
  268. let defaultExport: Node | undefined
  269. let hasDefinePropsCall = false
  270. let hasDefineEmitCall = false
  271. let hasDefineExposeCall = false
  272. let hasDefaultExportName = false
  273. let hasDefaultExportRender = false
  274. let hasDefineOptionsCall = false
  275. let propsRuntimeDecl: Node | undefined
  276. let propsRuntimeDefaults: ObjectExpression | undefined
  277. let propsDestructureDecl: Node | undefined
  278. let propsDestructureRestId: string | undefined
  279. let propsTypeDecl: PropsDeclType | undefined
  280. let propsTypeDeclRaw: Node | undefined
  281. let propsIdentifier: string | undefined
  282. let emitsRuntimeDecl: Node | undefined
  283. let emitsTypeDecl: EmitsDeclType | undefined
  284. let emitsTypeDeclRaw: Node | undefined
  285. let emitIdentifier: string | undefined
  286. let optionsRuntimeDecl: Node | undefined
  287. let hasAwait = false
  288. let hasInlinedSsrRenderFn = false
  289. // props/emits declared via types
  290. const typeDeclaredProps: Record<string, PropTypeData> = {}
  291. const typeDeclaredEmits: Set<string> = new Set()
  292. // record declared types for runtime props type generation
  293. const declaredTypes: Record<string, string[]> = {}
  294. // props destructure data
  295. const propsDestructuredBindings: Record<
  296. string, // public prop key
  297. {
  298. local: string // local identifier, may be different
  299. default?: Expression
  300. isConst: boolean
  301. }
  302. > = Object.create(null)
  303. // magic-string state
  304. const s = new MagicString(source)
  305. const startOffset = scriptSetup.loc.start.offset
  306. const endOffset = scriptSetup.loc.end.offset
  307. const scriptStartOffset = script && script.loc.start.offset
  308. const scriptEndOffset = script && script.loc.end.offset
  309. function helper(key: string): string {
  310. helperImports.add(key)
  311. return `_${key}`
  312. }
  313. function parse(
  314. input: string,
  315. options: ParserOptions,
  316. offset: number
  317. ): Program {
  318. try {
  319. return _parse(input, options).program
  320. } catch (e: any) {
  321. e.message = `[@vue/compiler-sfc] ${e.message}\n\n${
  322. sfc.filename
  323. }\n${generateCodeFrame(source, e.pos + offset, e.pos + offset + 1)}`
  324. throw e
  325. }
  326. }
  327. function error(
  328. msg: string,
  329. node: Node,
  330. end: number = node.end! + startOffset
  331. ): never {
  332. throw new Error(
  333. `[@vue/compiler-sfc] ${msg}\n\n${sfc.filename}\n${generateCodeFrame(
  334. source,
  335. node.start! + startOffset,
  336. end
  337. )}`
  338. )
  339. }
  340. function hoistNode(node: Statement) {
  341. const start = node.start! + startOffset
  342. let end = node.end! + startOffset
  343. // locate comment
  344. if (node.trailingComments && node.trailingComments.length > 0) {
  345. const lastCommentNode =
  346. node.trailingComments[node.trailingComments.length - 1]
  347. end = lastCommentNode.end! + startOffset
  348. }
  349. // locate the end of whitespace between this statement and the next
  350. while (end <= source.length) {
  351. if (!/\s/.test(source.charAt(end))) {
  352. break
  353. }
  354. end++
  355. }
  356. s.move(start, end, 0)
  357. }
  358. function registerUserImport(
  359. source: string,
  360. local: string,
  361. imported: string | false,
  362. isType: boolean,
  363. isFromSetup: boolean,
  364. needTemplateUsageCheck: boolean
  365. ) {
  366. // template usage check is only needed in non-inline mode, so we can skip
  367. // the work if inlineTemplate is true.
  368. let isUsedInTemplate = needTemplateUsageCheck
  369. if (
  370. needTemplateUsageCheck &&
  371. isTS &&
  372. sfc.template &&
  373. !sfc.template.src &&
  374. !sfc.template.lang
  375. ) {
  376. isUsedInTemplate = isImportUsed(local, sfc)
  377. }
  378. userImports[local] = {
  379. isType,
  380. imported: imported || 'default',
  381. local,
  382. source,
  383. isFromSetup,
  384. isUsedInTemplate
  385. }
  386. }
  387. function processDefineProps(
  388. node: Node,
  389. declId?: LVal,
  390. declKind?: VariableDeclaration['kind']
  391. ): boolean {
  392. if (!isCallOf(node, DEFINE_PROPS)) {
  393. return false
  394. }
  395. if (hasDefinePropsCall) {
  396. error(`duplicate ${DEFINE_PROPS}() call`, node)
  397. }
  398. hasDefinePropsCall = true
  399. propsRuntimeDecl = node.arguments[0]
  400. // call has type parameters - infer runtime types from it
  401. if (node.typeParameters) {
  402. if (propsRuntimeDecl) {
  403. error(
  404. `${DEFINE_PROPS}() cannot accept both type and non-type arguments ` +
  405. `at the same time. Use one or the other.`,
  406. node
  407. )
  408. }
  409. propsTypeDeclRaw = node.typeParameters.params[0]
  410. propsTypeDecl = resolveQualifiedType(
  411. propsTypeDeclRaw,
  412. node => node.type === 'TSTypeLiteral'
  413. ) as PropsDeclType | undefined
  414. if (!propsTypeDecl) {
  415. error(
  416. `type argument passed to ${DEFINE_PROPS}() must be a literal type, ` +
  417. `or a reference to an interface or literal type.`,
  418. propsTypeDeclRaw
  419. )
  420. }
  421. }
  422. if (declId) {
  423. const isConst = declKind === 'const'
  424. if (enablePropsTransform && declId.type === 'ObjectPattern') {
  425. propsDestructureDecl = declId
  426. // props destructure - handle compilation sugar
  427. for (const prop of declId.properties) {
  428. if (prop.type === 'ObjectProperty') {
  429. const propKey = resolveObjectKey(prop.key, prop.computed)
  430. if (!propKey) {
  431. error(
  432. `${DEFINE_PROPS}() destructure cannot use computed key.`,
  433. prop.key
  434. )
  435. }
  436. if (prop.value.type === 'AssignmentPattern') {
  437. // default value { foo = 123 }
  438. const { left, right } = prop.value
  439. if (left.type !== 'Identifier') {
  440. error(
  441. `${DEFINE_PROPS}() destructure does not support nested patterns.`,
  442. left
  443. )
  444. }
  445. // store default value
  446. propsDestructuredBindings[propKey] = {
  447. local: left.name,
  448. default: right,
  449. isConst
  450. }
  451. } else if (prop.value.type === 'Identifier') {
  452. // simple destructure
  453. propsDestructuredBindings[propKey] = {
  454. local: prop.value.name,
  455. isConst
  456. }
  457. } else {
  458. error(
  459. `${DEFINE_PROPS}() destructure does not support nested patterns.`,
  460. prop.value
  461. )
  462. }
  463. } else {
  464. // rest spread
  465. propsDestructureRestId = (prop.argument as Identifier).name
  466. }
  467. }
  468. } else {
  469. propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)
  470. }
  471. }
  472. return true
  473. }
  474. function processWithDefaults(
  475. node: Node,
  476. declId?: LVal,
  477. declKind?: VariableDeclaration['kind']
  478. ): boolean {
  479. if (!isCallOf(node, WITH_DEFAULTS)) {
  480. return false
  481. }
  482. if (processDefineProps(node.arguments[0], declId, declKind)) {
  483. if (propsRuntimeDecl) {
  484. error(
  485. `${WITH_DEFAULTS} can only be used with type-based ` +
  486. `${DEFINE_PROPS} declaration.`,
  487. node
  488. )
  489. }
  490. if (propsDestructureDecl) {
  491. error(
  492. `${WITH_DEFAULTS}() is unnecessary when using destructure with ${DEFINE_PROPS}().\n` +
  493. `Prefer using destructure default values, e.g. const { foo = 1 } = defineProps(...).`,
  494. node.callee
  495. )
  496. }
  497. propsRuntimeDefaults = node.arguments[1] as ObjectExpression
  498. if (
  499. !propsRuntimeDefaults ||
  500. propsRuntimeDefaults.type !== 'ObjectExpression'
  501. ) {
  502. error(
  503. `The 2nd argument of ${WITH_DEFAULTS} must be an object literal.`,
  504. propsRuntimeDefaults || node
  505. )
  506. }
  507. } else {
  508. error(
  509. `${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`,
  510. node.arguments[0] || node
  511. )
  512. }
  513. return true
  514. }
  515. function processDefineEmits(node: Node, declId?: LVal): boolean {
  516. if (!isCallOf(node, DEFINE_EMITS)) {
  517. return false
  518. }
  519. if (hasDefineEmitCall) {
  520. error(`duplicate ${DEFINE_EMITS}() call`, node)
  521. }
  522. hasDefineEmitCall = true
  523. emitsRuntimeDecl = node.arguments[0]
  524. if (node.typeParameters) {
  525. if (emitsRuntimeDecl) {
  526. error(
  527. `${DEFINE_EMITS}() cannot accept both type and non-type arguments ` +
  528. `at the same time. Use one or the other.`,
  529. node
  530. )
  531. }
  532. emitsTypeDeclRaw = node.typeParameters.params[0]
  533. emitsTypeDecl = resolveQualifiedType(
  534. emitsTypeDeclRaw,
  535. node => node.type === 'TSFunctionType' || node.type === 'TSTypeLiteral'
  536. ) as EmitsDeclType | undefined
  537. if (!emitsTypeDecl) {
  538. error(
  539. `type argument passed to ${DEFINE_EMITS}() must be a function type, ` +
  540. `a literal type with call signatures, or a reference to the above types.`,
  541. emitsTypeDeclRaw
  542. )
  543. }
  544. }
  545. if (declId) {
  546. emitIdentifier =
  547. declId.type === 'Identifier'
  548. ? declId.name
  549. : scriptSetup!.content.slice(declId.start!, declId.end!)
  550. }
  551. return true
  552. }
  553. function getAstBody(): Statement[] {
  554. return scriptAst
  555. ? [...scriptSetupAst.body, ...scriptAst.body]
  556. : scriptSetupAst.body
  557. }
  558. function resolveExtendsType(
  559. node: Node,
  560. qualifier: (node: Node) => boolean,
  561. cache: Array<Node> = []
  562. ): Array<Node> {
  563. if (node.type === 'TSInterfaceDeclaration' && node.extends) {
  564. node.extends.forEach(extend => {
  565. if (
  566. extend.type === 'TSExpressionWithTypeArguments' &&
  567. extend.expression.type === 'Identifier'
  568. ) {
  569. const body = getAstBody()
  570. for (const node of body) {
  571. const qualified = isQualifiedType(
  572. node,
  573. qualifier,
  574. extend.expression.name
  575. )
  576. if (qualified) {
  577. cache.push(qualified)
  578. resolveExtendsType(node, qualifier, cache)
  579. return cache
  580. }
  581. }
  582. }
  583. })
  584. }
  585. return cache
  586. }
  587. function isQualifiedType(
  588. node: Node,
  589. qualifier: (node: Node) => boolean,
  590. refName: String
  591. ): Node | undefined {
  592. if (node.type === 'TSInterfaceDeclaration' && node.id.name === refName) {
  593. return node.body
  594. } else if (
  595. node.type === 'TSTypeAliasDeclaration' &&
  596. node.id.name === refName &&
  597. qualifier(node.typeAnnotation)
  598. ) {
  599. return node.typeAnnotation
  600. } else if (node.type === 'ExportNamedDeclaration' && node.declaration) {
  601. return isQualifiedType(node.declaration, qualifier, refName)
  602. }
  603. }
  604. // filter all extends types to keep the override declaration
  605. function filterExtendsType(extendsTypes: Node[], bodies: TSTypeElement[]) {
  606. extendsTypes.forEach(extend => {
  607. const body = (extend as TSInterfaceBody).body
  608. body.forEach(newBody => {
  609. if (
  610. newBody.type === 'TSPropertySignature' &&
  611. newBody.key.type === 'Identifier'
  612. ) {
  613. const name = newBody.key.name
  614. const hasOverride = bodies.some(
  615. seenBody =>
  616. seenBody.type === 'TSPropertySignature' &&
  617. seenBody.key.type === 'Identifier' &&
  618. seenBody.key.name === name
  619. )
  620. if (!hasOverride) bodies.push(newBody)
  621. }
  622. })
  623. })
  624. }
  625. function processDefineOptions(node: Node): boolean {
  626. if (!isCallOf(node, DEFINE_OPTIONS)) {
  627. return false
  628. }
  629. if (hasDefineOptionsCall) {
  630. error(`duplicate ${DEFINE_OPTIONS}() call`, node)
  631. }
  632. if (node.typeParameters) {
  633. error(`${DEFINE_OPTIONS}() cannot accept type arguments`, node)
  634. }
  635. hasDefineOptionsCall = true
  636. optionsRuntimeDecl = node.arguments[0]
  637. let propsOption = undefined
  638. let emitsOption = undefined
  639. if (optionsRuntimeDecl.type === 'ObjectExpression') {
  640. for (const prop of optionsRuntimeDecl.properties) {
  641. if (
  642. (prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') &&
  643. prop.key.type === 'Identifier'
  644. ) {
  645. if (prop.key.name === 'props') propsOption = prop
  646. if (prop.key.name === 'emits') emitsOption = prop
  647. }
  648. }
  649. }
  650. if (propsOption) {
  651. error(
  652. `${DEFINE_OPTIONS}() cannot be used to declare props. Use ${DEFINE_PROPS}() instead.`,
  653. propsOption
  654. )
  655. }
  656. if (emitsOption) {
  657. error(
  658. `${DEFINE_OPTIONS}() cannot be used to declare emits. Use ${DEFINE_EMITS}() instead.`,
  659. emitsOption
  660. )
  661. }
  662. return true
  663. }
  664. function resolveQualifiedType(
  665. node: Node,
  666. qualifier: (node: Node) => boolean
  667. ): Node | undefined {
  668. if (qualifier(node)) {
  669. return node
  670. }
  671. if (
  672. node.type === 'TSTypeReference' &&
  673. node.typeName.type === 'Identifier'
  674. ) {
  675. const refName = node.typeName.name
  676. const body = getAstBody()
  677. for (let i = 0; i < body.length; i++) {
  678. const node = body[i]
  679. let qualified = isQualifiedType(
  680. node,
  681. qualifier,
  682. refName
  683. ) as TSInterfaceBody
  684. if (qualified) {
  685. const extendsTypes = resolveExtendsType(node, qualifier)
  686. if (extendsTypes.length) {
  687. const bodies: TSTypeElement[] = [...qualified.body]
  688. filterExtendsType(extendsTypes, bodies)
  689. qualified.body = bodies
  690. }
  691. ;(qualified as FromNormalScript<Node>).__fromNormalScript =
  692. scriptAst && i >= scriptSetupAst.body.length
  693. return qualified
  694. }
  695. }
  696. }
  697. }
  698. function processDefineExpose(node: Node): boolean {
  699. if (isCallOf(node, DEFINE_EXPOSE)) {
  700. if (hasDefineExposeCall) {
  701. error(`duplicate ${DEFINE_EXPOSE}() call`, node)
  702. }
  703. hasDefineExposeCall = true
  704. return true
  705. }
  706. return false
  707. }
  708. function checkInvalidScopeReference(node: Node | undefined, method: string) {
  709. if (!node) return
  710. walkIdentifiers(node, id => {
  711. const binding = setupBindings[id.name]
  712. if (binding && (binding !== BindingTypes.LITERAL_CONST || !hoistStatic)) {
  713. error(
  714. `\`${method}()\` in <script setup> cannot reference locally ` +
  715. `declared variables because it will be hoisted outside of the ` +
  716. `setup() function. If your component options require initialization ` +
  717. `in the module scope, use a separate normal <script> to export ` +
  718. `the options instead.`,
  719. id
  720. )
  721. }
  722. })
  723. }
  724. /**
  725. * await foo()
  726. * -->
  727. * ;(
  728. * ([__temp,__restore] = withAsyncContext(() => foo())),
  729. * await __temp,
  730. * __restore()
  731. * )
  732. *
  733. * const a = await foo()
  734. * -->
  735. * const a = (
  736. * ([__temp, __restore] = withAsyncContext(() => foo())),
  737. * __temp = await __temp,
  738. * __restore(),
  739. * __temp
  740. * )
  741. */
  742. function processAwait(
  743. node: AwaitExpression,
  744. needSemi: boolean,
  745. isStatement: boolean
  746. ) {
  747. const argumentStart =
  748. node.argument.extra && node.argument.extra.parenthesized
  749. ? (node.argument.extra.parenStart as number)
  750. : node.argument.start!
  751. const argumentStr = source.slice(
  752. argumentStart + startOffset,
  753. node.argument.end! + startOffset
  754. )
  755. const containsNestedAwait = /\bawait\b/.test(argumentStr)
  756. s.overwrite(
  757. node.start! + startOffset,
  758. argumentStart + startOffset,
  759. `${needSemi ? `;` : ``}(\n ([__temp,__restore] = ${helper(
  760. `withAsyncContext`
  761. )}(${containsNestedAwait ? `async ` : ``}() => `
  762. )
  763. s.appendLeft(
  764. node.end! + startOffset,
  765. `)),\n ${isStatement ? `` : `__temp = `}await __temp,\n __restore()${
  766. isStatement ? `` : `,\n __temp`
  767. }\n)`
  768. )
  769. }
  770. /**
  771. * check defaults. If the default object is an object literal with only
  772. * static properties, we can directly generate more optimized default
  773. * declarations. Otherwise we will have to fallback to runtime merging.
  774. */
  775. function hasStaticWithDefaults() {
  776. return (
  777. propsRuntimeDefaults &&
  778. propsRuntimeDefaults.type === 'ObjectExpression' &&
  779. propsRuntimeDefaults.properties.every(
  780. node =>
  781. (node.type === 'ObjectProperty' &&
  782. (!node.computed || node.key.type.endsWith('Literal'))) ||
  783. node.type === 'ObjectMethod'
  784. )
  785. )
  786. }
  787. function genRuntimeProps(props: Record<string, PropTypeData>) {
  788. const keys = Object.keys(props)
  789. if (!keys.length) {
  790. return ``
  791. }
  792. const hasStaticDefaults = hasStaticWithDefaults()
  793. const scriptSetupSource = scriptSetup!.content
  794. let propsDecls = `{
  795. ${keys
  796. .map(key => {
  797. let defaultString: string | undefined
  798. const destructured = genDestructuredDefaultValue(key)
  799. if (destructured) {
  800. defaultString = `default: ${destructured}`
  801. } else if (hasStaticDefaults) {
  802. const prop = propsRuntimeDefaults!.properties.find(node => {
  803. if (node.type === 'SpreadElement') return false
  804. return resolveObjectKey(node.key, node.computed) === key
  805. }) as ObjectProperty | ObjectMethod
  806. if (prop) {
  807. if (prop.type === 'ObjectProperty') {
  808. // prop has corresponding static default value
  809. defaultString = `default: ${scriptSetupSource.slice(
  810. prop.value.start!,
  811. prop.value.end!
  812. )}`
  813. } else {
  814. defaultString = `${prop.async ? 'async ' : ''}${
  815. prop.kind !== 'method' ? `${prop.kind} ` : ''
  816. }default() ${scriptSetupSource.slice(
  817. prop.body.start!,
  818. prop.body.end!
  819. )}`
  820. }
  821. }
  822. }
  823. const { type, required } = props[key]
  824. if (!isProd) {
  825. return `${key}: { type: ${toRuntimeTypeString(
  826. type
  827. )}, required: ${required}${
  828. defaultString ? `, ${defaultString}` : ``
  829. } }`
  830. } else if (
  831. type.some(
  832. el =>
  833. el === 'Boolean' ||
  834. ((!hasStaticDefaults || defaultString) && el === 'Function')
  835. )
  836. ) {
  837. // #4783 for boolean, should keep the type
  838. // #7111 for function, if default value exists or it's not static, should keep it
  839. // in production
  840. return `${key}: { type: ${toRuntimeTypeString(type)}${
  841. defaultString ? `, ${defaultString}` : ``
  842. } }`
  843. } else {
  844. // production: checks are useless
  845. return `${key}: ${defaultString ? `{ ${defaultString} }` : 'null'}`
  846. }
  847. })
  848. .join(',\n ')}\n }`
  849. if (propsRuntimeDefaults && !hasStaticDefaults) {
  850. propsDecls = `${helper('mergeDefaults')}(${propsDecls}, ${source.slice(
  851. propsRuntimeDefaults.start! + startOffset,
  852. propsRuntimeDefaults.end! + startOffset
  853. )})`
  854. }
  855. return `\n props: ${propsDecls},`
  856. }
  857. function genDestructuredDefaultValue(key: string): string | undefined {
  858. const destructured = propsDestructuredBindings[key]
  859. if (destructured && destructured.default) {
  860. const value = scriptSetup!.content.slice(
  861. destructured.default.start!,
  862. destructured.default.end!
  863. )
  864. const isLiteral = isLiteralNode(destructured.default)
  865. return isLiteral ? value : `() => (${value})`
  866. }
  867. }
  868. function genSetupPropsType(node: PropsDeclType) {
  869. const scriptSource = node.__fromNormalScript
  870. ? script!.content
  871. : scriptSetup!.content
  872. if (hasStaticWithDefaults()) {
  873. // if withDefaults() is used, we need to remove the optional flags
  874. // on props that have default values
  875. let res = `{ `
  876. const members = node.type === 'TSTypeLiteral' ? node.members : node.body
  877. for (const m of members) {
  878. if (
  879. (m.type === 'TSPropertySignature' ||
  880. m.type === 'TSMethodSignature') &&
  881. m.typeAnnotation &&
  882. m.key.type === 'Identifier'
  883. ) {
  884. if (
  885. propsRuntimeDefaults!.properties.some(p => {
  886. if (p.type === 'SpreadElement') return false
  887. return (
  888. resolveObjectKey(p.key, p.computed) ===
  889. (m.key as Identifier).name
  890. )
  891. })
  892. ) {
  893. res +=
  894. m.key.name +
  895. (m.type === 'TSMethodSignature' ? '()' : '') +
  896. scriptSource.slice(
  897. m.typeAnnotation.start!,
  898. m.typeAnnotation.end!
  899. ) +
  900. ', '
  901. } else {
  902. res += scriptSource.slice(m.start!, m.typeAnnotation.end!) + `, `
  903. }
  904. }
  905. }
  906. return (res.length ? res.slice(0, -2) : res) + ` }`
  907. } else {
  908. return scriptSource.slice(node.start!, node.end!)
  909. }
  910. }
  911. // 0. parse both <script> and <script setup> blocks
  912. const scriptAst =
  913. script &&
  914. parse(
  915. script.content,
  916. {
  917. plugins,
  918. sourceType: 'module'
  919. },
  920. scriptStartOffset!
  921. )
  922. const scriptSetupAst = parse(
  923. scriptSetup.content,
  924. {
  925. plugins: [
  926. ...plugins,
  927. // allow top level await but only inside <script setup>
  928. 'topLevelAwait'
  929. ],
  930. sourceType: 'module'
  931. },
  932. startOffset
  933. )
  934. // 1.1 walk import delcarations of <script>
  935. if (scriptAst) {
  936. for (const node of scriptAst.body) {
  937. if (node.type === 'ImportDeclaration') {
  938. // record imports for dedupe
  939. for (const specifier of node.specifiers) {
  940. const imported =
  941. specifier.type === 'ImportSpecifier' &&
  942. specifier.imported.type === 'Identifier' &&
  943. specifier.imported.name
  944. registerUserImport(
  945. node.source.value,
  946. specifier.local.name,
  947. imported,
  948. node.importKind === 'type' ||
  949. (specifier.type === 'ImportSpecifier' &&
  950. specifier.importKind === 'type'),
  951. false,
  952. !options.inlineTemplate
  953. )
  954. }
  955. }
  956. }
  957. }
  958. // 1.2 walk import declarations of <script setup>
  959. for (const node of scriptSetupAst.body) {
  960. if (node.type === 'ImportDeclaration') {
  961. // import declarations are moved to top
  962. hoistNode(node)
  963. // dedupe imports
  964. let removed = 0
  965. const removeSpecifier = (i: number) => {
  966. const removeLeft = i > removed
  967. removed++
  968. const current = node.specifiers[i]
  969. const next = node.specifiers[i + 1]
  970. s.remove(
  971. removeLeft
  972. ? node.specifiers[i - 1].end! + startOffset
  973. : current.start! + startOffset,
  974. next && !removeLeft
  975. ? next.start! + startOffset
  976. : current.end! + startOffset
  977. )
  978. }
  979. for (let i = 0; i < node.specifiers.length; i++) {
  980. const specifier = node.specifiers[i]
  981. const local = specifier.local.name
  982. let imported =
  983. specifier.type === 'ImportSpecifier' &&
  984. specifier.imported.type === 'Identifier' &&
  985. specifier.imported.name
  986. if (specifier.type === 'ImportNamespaceSpecifier') {
  987. imported = '*'
  988. }
  989. const source = node.source.value
  990. const existing = userImports[local]
  991. if (
  992. source === 'vue' &&
  993. (imported === DEFINE_PROPS ||
  994. imported === DEFINE_EMITS ||
  995. imported === DEFINE_EXPOSE)
  996. ) {
  997. warnOnce(
  998. `\`${imported}\` is a compiler macro and no longer needs to be imported.`
  999. )
  1000. removeSpecifier(i)
  1001. } else if (existing) {
  1002. if (existing.source === source && existing.imported === imported) {
  1003. // already imported in <script setup>, dedupe
  1004. removeSpecifier(i)
  1005. } else {
  1006. error(`different imports aliased to same local name.`, specifier)
  1007. }
  1008. } else {
  1009. registerUserImport(
  1010. source,
  1011. local,
  1012. imported,
  1013. node.importKind === 'type' ||
  1014. (specifier.type === 'ImportSpecifier' &&
  1015. specifier.importKind === 'type'),
  1016. true,
  1017. !options.inlineTemplate
  1018. )
  1019. }
  1020. }
  1021. if (node.specifiers.length && removed === node.specifiers.length) {
  1022. s.remove(node.start! + startOffset, node.end! + startOffset)
  1023. }
  1024. }
  1025. }
  1026. // 1.3 resolve possible user import alias of `ref` and `reactive`
  1027. const vueImportAliases: Record<string, string> = {}
  1028. for (const key in userImports) {
  1029. const { source, imported, local } = userImports[key]
  1030. if (source === 'vue') vueImportAliases[imported] = local
  1031. }
  1032. // 2.1 process normal <script> body
  1033. if (script && scriptAst) {
  1034. for (const node of scriptAst.body) {
  1035. if (node.type === 'ExportDefaultDeclaration') {
  1036. // export default
  1037. defaultExport = node
  1038. // check if user has manually specified `name` or 'render` option in
  1039. // export default
  1040. // if has name, skip name inference
  1041. // if has render and no template, generate return object instead of
  1042. // empty render function (#4980)
  1043. let optionProperties
  1044. if (defaultExport.declaration.type === 'ObjectExpression') {
  1045. optionProperties = defaultExport.declaration.properties
  1046. } else if (
  1047. defaultExport.declaration.type === 'CallExpression' &&
  1048. defaultExport.declaration.arguments[0].type === 'ObjectExpression'
  1049. ) {
  1050. optionProperties = defaultExport.declaration.arguments[0].properties
  1051. }
  1052. if (optionProperties) {
  1053. for (const s of optionProperties) {
  1054. if (
  1055. s.type === 'ObjectProperty' &&
  1056. s.key.type === 'Identifier' &&
  1057. s.key.name === 'name'
  1058. ) {
  1059. hasDefaultExportName = true
  1060. }
  1061. if (
  1062. (s.type === 'ObjectMethod' || s.type === 'ObjectProperty') &&
  1063. s.key.type === 'Identifier' &&
  1064. s.key.name === 'render'
  1065. ) {
  1066. // TODO warn when we provide a better way to do it?
  1067. hasDefaultExportRender = true
  1068. }
  1069. }
  1070. }
  1071. // export default { ... } --> const __default__ = { ... }
  1072. const start = node.start! + scriptStartOffset!
  1073. const end = node.declaration.start! + scriptStartOffset!
  1074. s.overwrite(start, end, `const ${DEFAULT_VAR} = `)
  1075. } else if (node.type === 'ExportNamedDeclaration') {
  1076. const defaultSpecifier = node.specifiers.find(
  1077. s => s.exported.type === 'Identifier' && s.exported.name === 'default'
  1078. ) as ExportSpecifier
  1079. if (defaultSpecifier) {
  1080. defaultExport = node
  1081. // 1. remove specifier
  1082. if (node.specifiers.length > 1) {
  1083. s.remove(
  1084. defaultSpecifier.start! + scriptStartOffset!,
  1085. defaultSpecifier.end! + scriptStartOffset!
  1086. )
  1087. } else {
  1088. s.remove(
  1089. node.start! + scriptStartOffset!,
  1090. node.end! + scriptStartOffset!
  1091. )
  1092. }
  1093. if (node.source) {
  1094. // export { x as default } from './x'
  1095. // rewrite to `import { x as __default__ } from './x'` and
  1096. // add to top
  1097. s.prepend(
  1098. `import { ${defaultSpecifier.local.name} as ${DEFAULT_VAR} } from '${node.source.value}'\n`
  1099. )
  1100. } else {
  1101. // export { x as default }
  1102. // rewrite to `const __default__ = x` and move to end
  1103. s.appendLeft(
  1104. scriptEndOffset!,
  1105. `\nconst ${DEFAULT_VAR} = ${defaultSpecifier.local.name}\n`
  1106. )
  1107. }
  1108. }
  1109. if (node.declaration) {
  1110. walkDeclaration(node.declaration, scriptBindings, vueImportAliases)
  1111. }
  1112. } else if (
  1113. (node.type === 'VariableDeclaration' ||
  1114. node.type === 'FunctionDeclaration' ||
  1115. node.type === 'ClassDeclaration' ||
  1116. node.type === 'TSEnumDeclaration') &&
  1117. !node.declare
  1118. ) {
  1119. walkDeclaration(node, scriptBindings, vueImportAliases)
  1120. }
  1121. }
  1122. // apply reactivity transform
  1123. if (enableReactivityTransform && shouldTransform(script.content)) {
  1124. const { rootRefs, importedHelpers } = transformAST(
  1125. scriptAst,
  1126. s,
  1127. scriptStartOffset!
  1128. )
  1129. refBindings = rootRefs
  1130. for (const h of importedHelpers) {
  1131. helperImports.add(h)
  1132. }
  1133. }
  1134. // <script> after <script setup>
  1135. // we need to move the block up so that `const __default__` is
  1136. // declared before being used in the actual component definition
  1137. if (scriptStartOffset! > startOffset) {
  1138. // if content doesn't end with newline, add one
  1139. if (!/\n$/.test(script.content.trim())) {
  1140. s.appendLeft(scriptEndOffset!, `\n`)
  1141. }
  1142. s.move(scriptStartOffset!, scriptEndOffset!, 0)
  1143. }
  1144. }
  1145. // 2.2 process <script setup> body
  1146. for (const node of scriptSetupAst.body) {
  1147. // (Dropped) `ref: x` bindings
  1148. // TODO remove when out of experimental
  1149. if (
  1150. node.type === 'LabeledStatement' &&
  1151. node.label.name === 'ref' &&
  1152. node.body.type === 'ExpressionStatement'
  1153. ) {
  1154. error(
  1155. `ref sugar using the label syntax was an experimental proposal and ` +
  1156. `has been dropped based on community feedback. Please check out ` +
  1157. `the new proposal at https://github.com/vuejs/rfcs/discussions/369`,
  1158. node
  1159. )
  1160. }
  1161. if (node.type === 'ExpressionStatement') {
  1162. const expr = unwrapTSNode(node.expression)
  1163. // process `defineProps` and `defineEmit(s)` calls
  1164. if (
  1165. processDefineProps(expr) ||
  1166. processDefineEmits(expr) ||
  1167. processDefineOptions(expr) ||
  1168. processWithDefaults(expr)
  1169. ) {
  1170. s.remove(node.start! + startOffset, node.end! + startOffset)
  1171. } else if (processDefineExpose(expr)) {
  1172. // defineExpose({}) -> expose({})
  1173. const callee = (expr as CallExpression).callee
  1174. s.overwrite(
  1175. callee.start! + startOffset,
  1176. callee.end! + startOffset,
  1177. 'expose'
  1178. )
  1179. }
  1180. }
  1181. if (node.type === 'VariableDeclaration' && !node.declare) {
  1182. const total = node.declarations.length
  1183. let left = total
  1184. for (let i = 0; i < total; i++) {
  1185. const decl = node.declarations[i]
  1186. const init = decl.init && unwrapTSNode(decl.init)
  1187. if (init) {
  1188. if (processDefineOptions(init)) {
  1189. error(
  1190. `${DEFINE_OPTIONS}() has no returning value, it cannot be assigned.`,
  1191. node
  1192. )
  1193. }
  1194. // defineProps / defineEmits
  1195. const isDefineProps =
  1196. processDefineProps(init, decl.id, node.kind) ||
  1197. processWithDefaults(init, decl.id, node.kind)
  1198. const isDefineEmits = processDefineEmits(init, decl.id)
  1199. if (isDefineProps || isDefineEmits) {
  1200. if (left === 1) {
  1201. s.remove(node.start! + startOffset, node.end! + startOffset)
  1202. } else {
  1203. let start = decl.start! + startOffset
  1204. let end = decl.end! + startOffset
  1205. if (i === 0) {
  1206. // first one, locate the start of the next
  1207. end = node.declarations[i + 1].start! + startOffset
  1208. } else {
  1209. // not first one, locate the end of the prev
  1210. start = node.declarations[i - 1].end! + startOffset
  1211. }
  1212. s.remove(start, end)
  1213. left--
  1214. }
  1215. }
  1216. }
  1217. }
  1218. }
  1219. let isAllLiteral = false
  1220. // walk declarations to record declared bindings
  1221. if (
  1222. (node.type === 'VariableDeclaration' ||
  1223. node.type === 'FunctionDeclaration' ||
  1224. node.type === 'ClassDeclaration' ||
  1225. node.type === 'TSEnumDeclaration') &&
  1226. !node.declare
  1227. ) {
  1228. isAllLiteral = walkDeclaration(node, setupBindings, vueImportAliases)
  1229. }
  1230. // hoist literal constants
  1231. if (hoistStatic && isAllLiteral) {
  1232. hoistNode(node)
  1233. }
  1234. // walk statements & named exports / variable declarations for top level
  1235. // await
  1236. if (
  1237. (node.type === 'VariableDeclaration' && !node.declare) ||
  1238. node.type.endsWith('Statement')
  1239. ) {
  1240. const scope: Statement[][] = [scriptSetupAst.body]
  1241. ;(walk as any)(node, {
  1242. enter(child: Node, parent: Node) {
  1243. if (isFunctionType(child)) {
  1244. this.skip()
  1245. }
  1246. if (child.type === 'BlockStatement') {
  1247. scope.push(child.body)
  1248. }
  1249. if (child.type === 'AwaitExpression') {
  1250. hasAwait = true
  1251. // if the await expression is an expression statement and
  1252. // - is in the root scope
  1253. // - or is not the first statement in a nested block scope
  1254. // then it needs a semicolon before the generated code.
  1255. const currentScope = scope[scope.length - 1]
  1256. const needsSemi = currentScope.some((n, i) => {
  1257. return (
  1258. (scope.length === 1 || i > 0) &&
  1259. n.type === 'ExpressionStatement' &&
  1260. n.start === child.start
  1261. )
  1262. })
  1263. processAwait(
  1264. child,
  1265. needsSemi,
  1266. parent.type === 'ExpressionStatement'
  1267. )
  1268. }
  1269. },
  1270. exit(node: Node) {
  1271. if (node.type === 'BlockStatement') scope.pop()
  1272. }
  1273. })
  1274. }
  1275. if (
  1276. (node.type === 'ExportNamedDeclaration' && node.exportKind !== 'type') ||
  1277. node.type === 'ExportAllDeclaration' ||
  1278. node.type === 'ExportDefaultDeclaration'
  1279. ) {
  1280. error(
  1281. `<script setup> cannot contain ES module exports. ` +
  1282. `If you are using a previous version of <script setup>, please ` +
  1283. `consult the updated RFC at https://github.com/vuejs/rfcs/pull/227.`,
  1284. node
  1285. )
  1286. }
  1287. if (isTS) {
  1288. // move all Type declarations to outer scope
  1289. if (
  1290. (node.type.startsWith('TS') ||
  1291. (node.type === 'ExportNamedDeclaration' &&
  1292. node.exportKind === 'type') ||
  1293. (node.type === 'VariableDeclaration' && node.declare)) &&
  1294. node.type !== 'TSEnumDeclaration'
  1295. ) {
  1296. recordType(node, declaredTypes)
  1297. hoistNode(node)
  1298. }
  1299. }
  1300. }
  1301. // 3. Apply reactivity transform
  1302. if (
  1303. (enableReactivityTransform &&
  1304. // normal <script> had ref bindings that maybe used in <script setup>
  1305. (refBindings || shouldTransform(scriptSetup.content))) ||
  1306. propsDestructureDecl
  1307. ) {
  1308. const { rootRefs, importedHelpers } = transformAST(
  1309. scriptSetupAst,
  1310. s,
  1311. startOffset,
  1312. refBindings,
  1313. propsDestructuredBindings
  1314. )
  1315. refBindings = refBindings ? [...refBindings, ...rootRefs] : rootRefs
  1316. for (const h of importedHelpers) {
  1317. helperImports.add(h)
  1318. }
  1319. }
  1320. // 4. extract runtime props/emits code from setup context type
  1321. if (propsTypeDecl) {
  1322. extractRuntimeProps(propsTypeDecl, typeDeclaredProps, declaredTypes, isProd)
  1323. }
  1324. if (emitsTypeDecl) {
  1325. extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits)
  1326. }
  1327. // 5. check useOptions args to make sure it doesn't reference setup scope
  1328. // variables
  1329. checkInvalidScopeReference(propsRuntimeDecl, DEFINE_PROPS)
  1330. checkInvalidScopeReference(propsRuntimeDefaults, DEFINE_PROPS)
  1331. checkInvalidScopeReference(propsDestructureDecl, DEFINE_PROPS)
  1332. checkInvalidScopeReference(emitsRuntimeDecl, DEFINE_EMITS)
  1333. checkInvalidScopeReference(optionsRuntimeDecl, DEFINE_OPTIONS)
  1334. // 6. remove non-script content
  1335. if (script) {
  1336. if (startOffset < scriptStartOffset!) {
  1337. // <script setup> before <script>
  1338. s.remove(0, startOffset)
  1339. s.remove(endOffset, scriptStartOffset!)
  1340. s.remove(scriptEndOffset!, source.length)
  1341. } else {
  1342. // <script> before <script setup>
  1343. s.remove(0, scriptStartOffset!)
  1344. s.remove(scriptEndOffset!, startOffset)
  1345. s.remove(endOffset, source.length)
  1346. }
  1347. } else {
  1348. // only <script setup>
  1349. s.remove(0, startOffset)
  1350. s.remove(endOffset, source.length)
  1351. }
  1352. // 7. analyze binding metadata
  1353. if (scriptAst) {
  1354. Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst.body))
  1355. }
  1356. if (propsRuntimeDecl) {
  1357. for (const key of getObjectOrArrayExpressionKeys(propsRuntimeDecl)) {
  1358. bindingMetadata[key] = BindingTypes.PROPS
  1359. }
  1360. }
  1361. for (const key in typeDeclaredProps) {
  1362. bindingMetadata[key] = BindingTypes.PROPS
  1363. }
  1364. // props aliases
  1365. if (propsDestructureDecl) {
  1366. if (propsDestructureRestId) {
  1367. bindingMetadata[propsDestructureRestId] =
  1368. BindingTypes.SETUP_REACTIVE_CONST
  1369. }
  1370. for (const key in propsDestructuredBindings) {
  1371. const { local } = propsDestructuredBindings[key]
  1372. if (local !== key) {
  1373. bindingMetadata[local] = BindingTypes.PROPS_ALIASED
  1374. ;(bindingMetadata.__propsAliases ||
  1375. (bindingMetadata.__propsAliases = {}))[local] = key
  1376. }
  1377. }
  1378. }
  1379. for (const [key, { isType, imported, source }] of Object.entries(
  1380. userImports
  1381. )) {
  1382. if (isType) continue
  1383. bindingMetadata[key] =
  1384. imported === '*' ||
  1385. (imported === 'default' && source.endsWith('.vue')) ||
  1386. source === 'vue'
  1387. ? BindingTypes.SETUP_CONST
  1388. : BindingTypes.SETUP_MAYBE_REF
  1389. }
  1390. for (const key in scriptBindings) {
  1391. bindingMetadata[key] = scriptBindings[key]
  1392. }
  1393. for (const key in setupBindings) {
  1394. bindingMetadata[key] = setupBindings[key]
  1395. }
  1396. // known ref bindings
  1397. if (refBindings) {
  1398. for (const key of refBindings) {
  1399. bindingMetadata[key] = BindingTypes.SETUP_REF
  1400. }
  1401. }
  1402. // 8. inject `useCssVars` calls
  1403. if (
  1404. cssVars.length &&
  1405. // no need to do this when targeting SSR
  1406. !(options.inlineTemplate && options.templateOptions?.ssr)
  1407. ) {
  1408. helperImports.add(CSS_VARS_HELPER)
  1409. helperImports.add('unref')
  1410. s.prependLeft(
  1411. startOffset,
  1412. `\n${genCssVarsCode(cssVars, bindingMetadata, scopeId, isProd)}\n`
  1413. )
  1414. }
  1415. // 9. finalize setup() argument signature
  1416. let args = `__props`
  1417. if (propsTypeDecl) {
  1418. // mark as any and only cast on assignment
  1419. // since the user defined complex types may be incompatible with the
  1420. // inferred type from generated runtime declarations
  1421. args += `: any`
  1422. }
  1423. // inject user assignment of props
  1424. // we use a default __props so that template expressions referencing props
  1425. // can use it directly
  1426. if (propsIdentifier) {
  1427. s.prependLeft(
  1428. startOffset,
  1429. `\nconst ${propsIdentifier} = __props${
  1430. propsTypeDecl ? ` as ${genSetupPropsType(propsTypeDecl)}` : ``
  1431. };\n`
  1432. )
  1433. }
  1434. if (propsDestructureRestId) {
  1435. s.prependLeft(
  1436. startOffset,
  1437. `\nconst ${propsDestructureRestId} = ${helper(
  1438. `createPropsRestProxy`
  1439. )}(__props, ${JSON.stringify(Object.keys(propsDestructuredBindings))});\n`
  1440. )
  1441. }
  1442. // inject temp variables for async context preservation
  1443. if (hasAwait) {
  1444. const any = isTS ? `: any` : ``
  1445. s.prependLeft(startOffset, `\nlet __temp${any}, __restore${any}\n`)
  1446. }
  1447. const destructureElements =
  1448. hasDefineExposeCall || !options.inlineTemplate ? [`expose`] : []
  1449. if (emitIdentifier) {
  1450. destructureElements.push(
  1451. emitIdentifier === `emit` ? `emit` : `emit: ${emitIdentifier}`
  1452. )
  1453. }
  1454. if (destructureElements.length) {
  1455. args += `, { ${destructureElements.join(', ')} }`
  1456. if (emitsTypeDecl) {
  1457. const content = emitsTypeDecl.__fromNormalScript
  1458. ? script!.content
  1459. : scriptSetup.content
  1460. args += `: { emit: (${content.slice(
  1461. emitsTypeDecl.start!,
  1462. emitsTypeDecl.end!
  1463. )}), expose: any, slots: any, attrs: any }`
  1464. }
  1465. }
  1466. // 10. generate return statement
  1467. let returned
  1468. if (!options.inlineTemplate || (!sfc.template && hasDefaultExportRender)) {
  1469. // non-inline mode, or has manual render in normal <script>
  1470. // return bindings from script and script setup
  1471. const allBindings: Record<string, any> = {
  1472. ...scriptBindings,
  1473. ...setupBindings
  1474. }
  1475. for (const key in userImports) {
  1476. if (!userImports[key].isType && userImports[key].isUsedInTemplate) {
  1477. allBindings[key] = true
  1478. }
  1479. }
  1480. returned = `{ `
  1481. for (const key in allBindings) {
  1482. if (
  1483. allBindings[key] === true &&
  1484. userImports[key].source !== 'vue' &&
  1485. !userImports[key].source.endsWith('.vue')
  1486. ) {
  1487. // generate getter for import bindings
  1488. // skip vue imports since we know they will never change
  1489. returned += `get ${key}() { return ${key} }, `
  1490. } else if (bindingMetadata[key] === BindingTypes.SETUP_LET) {
  1491. // local let binding, also add setter
  1492. const setArg = key === 'v' ? `_v` : `v`
  1493. returned +=
  1494. `get ${key}() { return ${key} }, ` +
  1495. `set ${key}(${setArg}) { ${key} = ${setArg} }, `
  1496. } else {
  1497. returned += `${key}, `
  1498. }
  1499. }
  1500. returned = returned.replace(/, $/, '') + ` }`
  1501. } else {
  1502. // inline mode
  1503. if (sfc.template && !sfc.template.src) {
  1504. if (options.templateOptions && options.templateOptions.ssr) {
  1505. hasInlinedSsrRenderFn = true
  1506. }
  1507. // inline render function mode - we are going to compile the template and
  1508. // inline it right here
  1509. const { code, ast, preamble, tips, errors } = compileTemplate({
  1510. filename,
  1511. source: sfc.template.content,
  1512. inMap: sfc.template.map,
  1513. ...options.templateOptions,
  1514. id: scopeId,
  1515. scoped: sfc.styles.some(s => s.scoped),
  1516. isProd: options.isProd,
  1517. ssrCssVars: sfc.cssVars,
  1518. compilerOptions: {
  1519. ...(options.templateOptions &&
  1520. options.templateOptions.compilerOptions),
  1521. inline: true,
  1522. isTS,
  1523. bindingMetadata
  1524. }
  1525. })
  1526. if (tips.length) {
  1527. tips.forEach(warnOnce)
  1528. }
  1529. const err = errors[0]
  1530. if (typeof err === 'string') {
  1531. throw new Error(err)
  1532. } else if (err) {
  1533. if (err.loc) {
  1534. err.message +=
  1535. `\n\n` +
  1536. sfc.filename +
  1537. '\n' +
  1538. generateCodeFrame(
  1539. source,
  1540. err.loc.start.offset,
  1541. err.loc.end.offset
  1542. ) +
  1543. `\n`
  1544. }
  1545. throw err
  1546. }
  1547. if (preamble) {
  1548. s.prepend(preamble)
  1549. }
  1550. // avoid duplicated unref import
  1551. // as this may get injected by the render function preamble OR the
  1552. // css vars codegen
  1553. if (ast && ast.helpers.has(UNREF)) {
  1554. helperImports.delete('unref')
  1555. }
  1556. returned = code
  1557. } else {
  1558. returned = `() => {}`
  1559. }
  1560. }
  1561. if (!options.inlineTemplate && !__TEST__) {
  1562. // in non-inline mode, the `__isScriptSetup: true` flag is used by
  1563. // componentPublicInstance proxy to allow properties that start with $ or _
  1564. s.appendRight(
  1565. endOffset,
  1566. `\nconst __returned__ = ${returned}\n` +
  1567. `Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })\n` +
  1568. `return __returned__` +
  1569. `\n}\n\n`
  1570. )
  1571. } else {
  1572. s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)
  1573. }
  1574. // 11. finalize default export
  1575. let runtimeOptions = ``
  1576. if (!hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {
  1577. const match = filename.match(/([^/\\]+)\.\w+$/)
  1578. if (match) {
  1579. runtimeOptions += `\n __name: '${match[1]}',`
  1580. }
  1581. }
  1582. if (hasInlinedSsrRenderFn) {
  1583. runtimeOptions += `\n __ssrInlineRender: true,`
  1584. }
  1585. if (propsRuntimeDecl) {
  1586. let declCode = scriptSetup.content
  1587. .slice(propsRuntimeDecl.start!, propsRuntimeDecl.end!)
  1588. .trim()
  1589. if (propsDestructureDecl) {
  1590. const defaults: string[] = []
  1591. for (const key in propsDestructuredBindings) {
  1592. const d = genDestructuredDefaultValue(key)
  1593. if (d) defaults.push(`${key}: ${d}`)
  1594. }
  1595. if (defaults.length) {
  1596. declCode = `${helper(
  1597. `mergeDefaults`
  1598. )}(${declCode}, {\n ${defaults.join(',\n ')}\n})`
  1599. }
  1600. }
  1601. runtimeOptions += `\n props: ${declCode},`
  1602. } else if (propsTypeDecl) {
  1603. runtimeOptions += genRuntimeProps(typeDeclaredProps)
  1604. }
  1605. if (emitsRuntimeDecl) {
  1606. runtimeOptions += `\n emits: ${scriptSetup.content
  1607. .slice(emitsRuntimeDecl.start!, emitsRuntimeDecl.end!)
  1608. .trim()},`
  1609. } else if (emitsTypeDecl) {
  1610. runtimeOptions += genRuntimeEmits(typeDeclaredEmits)
  1611. }
  1612. let definedOptions = ''
  1613. if (optionsRuntimeDecl) {
  1614. definedOptions = scriptSetup.content
  1615. .slice(optionsRuntimeDecl.start!, optionsRuntimeDecl.end!)
  1616. .trim()
  1617. }
  1618. // <script setup> components are closed by default. If the user did not
  1619. // explicitly call `defineExpose`, call expose() with no args.
  1620. const exposeCall =
  1621. hasDefineExposeCall || options.inlineTemplate ? `` : ` expose();\n`
  1622. // wrap setup code with function.
  1623. if (isTS) {
  1624. // for TS, make sure the exported type is still valid type with
  1625. // correct props information
  1626. // we have to use object spread for types to be merged properly
  1627. // user's TS setting should compile it down to proper targets
  1628. // export default defineComponent({ ...__default__, ... })
  1629. const def =
  1630. (defaultExport ? `\n ...${DEFAULT_VAR},` : ``) +
  1631. (definedOptions ? `\n ...${definedOptions},` : '')
  1632. s.prependLeft(
  1633. startOffset,
  1634. `\nexport default /*#__PURE__*/${helper(
  1635. `defineComponent`
  1636. )}({${def}${runtimeOptions}\n ${
  1637. hasAwait ? `async ` : ``
  1638. }setup(${args}) {\n${exposeCall}`
  1639. )
  1640. s.appendRight(endOffset, `})`)
  1641. } else {
  1642. if (defaultExport || definedOptions) {
  1643. // without TS, can't rely on rest spread, so we use Object.assign
  1644. // export default Object.assign(__default__, { ... })
  1645. s.prependLeft(
  1646. startOffset,
  1647. `\nexport default /*#__PURE__*/Object.assign(${
  1648. defaultExport ? `${DEFAULT_VAR}, ` : ''
  1649. }${definedOptions ? `${definedOptions}, ` : ''}{${runtimeOptions}\n ` +
  1650. `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`
  1651. )
  1652. s.appendRight(endOffset, `})`)
  1653. } else {
  1654. s.prependLeft(
  1655. startOffset,
  1656. `\nexport default {${runtimeOptions}\n ` +
  1657. `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`
  1658. )
  1659. s.appendRight(endOffset, `}`)
  1660. }
  1661. }
  1662. // 12. finalize Vue helper imports
  1663. if (helperImports.size > 0) {
  1664. s.prepend(
  1665. `import { ${[...helperImports]
  1666. .map(h => `${h} as _${h}`)
  1667. .join(', ')} } from 'vue'\n`
  1668. )
  1669. }
  1670. s.trim()
  1671. return {
  1672. ...scriptSetup,
  1673. s,
  1674. bindings: bindingMetadata,
  1675. imports: userImports,
  1676. content: s.toString(),
  1677. map: genSourceMap
  1678. ? (s.generateMap({
  1679. source: filename,
  1680. hires: true,
  1681. includeContent: true
  1682. }) as unknown as RawSourceMap)
  1683. : undefined,
  1684. scriptAst: scriptAst?.body,
  1685. scriptSetupAst: scriptSetupAst?.body
  1686. }
  1687. }
  1688. function registerBinding(
  1689. bindings: Record<string, BindingTypes>,
  1690. node: Identifier,
  1691. type: BindingTypes
  1692. ) {
  1693. bindings[node.name] = type
  1694. }
  1695. function walkDeclaration(
  1696. node: Declaration,
  1697. bindings: Record<string, BindingTypes>,
  1698. userImportAliases: Record<string, string>
  1699. ): boolean {
  1700. let isAllLiteral = false
  1701. if (node.type === 'VariableDeclaration') {
  1702. const isConst = node.kind === 'const'
  1703. isAllLiteral =
  1704. isConst &&
  1705. node.declarations.every(
  1706. decl => decl.id.type === 'Identifier' && isStaticNode(decl.init!)
  1707. )
  1708. // export const foo = ...
  1709. for (const { id, init: _init } of node.declarations) {
  1710. const init = _init && unwrapTSNode(_init)
  1711. const isDefineCall = !!(
  1712. isConst &&
  1713. isCallOf(
  1714. init,
  1715. c => c === DEFINE_PROPS || c === DEFINE_EMITS || c === WITH_DEFAULTS
  1716. )
  1717. )
  1718. if (id.type === 'Identifier') {
  1719. let bindingType
  1720. const userReactiveBinding = userImportAliases['reactive']
  1721. if (isAllLiteral || (isConst && isStaticNode(init!))) {
  1722. bindingType = BindingTypes.LITERAL_CONST
  1723. } else if (isCallOf(init, userReactiveBinding)) {
  1724. // treat reactive() calls as let since it's meant to be mutable
  1725. bindingType = isConst
  1726. ? BindingTypes.SETUP_REACTIVE_CONST
  1727. : BindingTypes.SETUP_LET
  1728. } else if (
  1729. // if a declaration is a const literal, we can mark it so that
  1730. // the generated render fn code doesn't need to unref() it
  1731. isDefineCall ||
  1732. (isConst && canNeverBeRef(init!, userReactiveBinding))
  1733. ) {
  1734. bindingType = isCallOf(init, DEFINE_PROPS)
  1735. ? BindingTypes.SETUP_REACTIVE_CONST
  1736. : BindingTypes.SETUP_CONST
  1737. } else if (isConst) {
  1738. if (isCallOf(init, userImportAliases['ref'])) {
  1739. bindingType = BindingTypes.SETUP_REF
  1740. } else {
  1741. bindingType = BindingTypes.SETUP_MAYBE_REF
  1742. }
  1743. } else {
  1744. bindingType = BindingTypes.SETUP_LET
  1745. }
  1746. registerBinding(bindings, id, bindingType)
  1747. } else {
  1748. if (isCallOf(init, DEFINE_PROPS)) {
  1749. continue
  1750. }
  1751. if (id.type === 'ObjectPattern') {
  1752. walkObjectPattern(id, bindings, isConst, isDefineCall)
  1753. } else if (id.type === 'ArrayPattern') {
  1754. walkArrayPattern(id, bindings, isConst, isDefineCall)
  1755. }
  1756. }
  1757. }
  1758. } else if (node.type === 'TSEnumDeclaration') {
  1759. isAllLiteral = node.members.every(
  1760. member => !member.initializer || isStaticNode(member.initializer)
  1761. )
  1762. bindings[node.id!.name] = isAllLiteral
  1763. ? BindingTypes.LITERAL_CONST
  1764. : BindingTypes.SETUP_CONST
  1765. } else if (
  1766. node.type === 'FunctionDeclaration' ||
  1767. node.type === 'ClassDeclaration'
  1768. ) {
  1769. // export function foo() {} / export class Foo {}
  1770. // export declarations must be named.
  1771. bindings[node.id!.name] = BindingTypes.SETUP_CONST
  1772. }
  1773. return isAllLiteral
  1774. }
  1775. function walkObjectPattern(
  1776. node: ObjectPattern,
  1777. bindings: Record<string, BindingTypes>,
  1778. isConst: boolean,
  1779. isDefineCall = false
  1780. ) {
  1781. for (const p of node.properties) {
  1782. if (p.type === 'ObjectProperty') {
  1783. if (p.key.type === 'Identifier' && p.key === p.value) {
  1784. // shorthand: const { x } = ...
  1785. const type = isDefineCall
  1786. ? BindingTypes.SETUP_CONST
  1787. : isConst
  1788. ? BindingTypes.SETUP_MAYBE_REF
  1789. : BindingTypes.SETUP_LET
  1790. registerBinding(bindings, p.key, type)
  1791. } else {
  1792. walkPattern(p.value, bindings, isConst, isDefineCall)
  1793. }
  1794. } else {
  1795. // ...rest
  1796. // argument can only be identifier when destructuring
  1797. const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET
  1798. registerBinding(bindings, p.argument as Identifier, type)
  1799. }
  1800. }
  1801. }
  1802. function walkArrayPattern(
  1803. node: ArrayPattern,
  1804. bindings: Record<string, BindingTypes>,
  1805. isConst: boolean,
  1806. isDefineCall = false
  1807. ) {
  1808. for (const e of node.elements) {
  1809. e && walkPattern(e, bindings, isConst, isDefineCall)
  1810. }
  1811. }
  1812. function walkPattern(
  1813. node: Node,
  1814. bindings: Record<string, BindingTypes>,
  1815. isConst: boolean,
  1816. isDefineCall = false
  1817. ) {
  1818. if (node.type === 'Identifier') {
  1819. const type = isDefineCall
  1820. ? BindingTypes.SETUP_CONST
  1821. : isConst
  1822. ? BindingTypes.SETUP_MAYBE_REF
  1823. : BindingTypes.SETUP_LET
  1824. registerBinding(bindings, node, type)
  1825. } else if (node.type === 'RestElement') {
  1826. // argument can only be identifier when destructuring
  1827. const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET
  1828. registerBinding(bindings, node.argument as Identifier, type)
  1829. } else if (node.type === 'ObjectPattern') {
  1830. walkObjectPattern(node, bindings, isConst)
  1831. } else if (node.type === 'ArrayPattern') {
  1832. walkArrayPattern(node, bindings, isConst)
  1833. } else if (node.type === 'AssignmentPattern') {
  1834. if (node.left.type === 'Identifier') {
  1835. const type = isDefineCall
  1836. ? BindingTypes.SETUP_CONST
  1837. : isConst
  1838. ? BindingTypes.SETUP_MAYBE_REF
  1839. : BindingTypes.SETUP_LET
  1840. registerBinding(bindings, node.left, type)
  1841. } else {
  1842. walkPattern(node.left, bindings, isConst)
  1843. }
  1844. }
  1845. }
  1846. interface PropTypeData {
  1847. key: string
  1848. type: string[]
  1849. required: boolean
  1850. }
  1851. function recordType(node: Node, declaredTypes: Record<string, string[]>) {
  1852. if (node.type === 'TSInterfaceDeclaration') {
  1853. declaredTypes[node.id.name] = [`Object`]
  1854. } else if (node.type === 'TSTypeAliasDeclaration') {
  1855. declaredTypes[node.id.name] = inferRuntimeType(
  1856. node.typeAnnotation,
  1857. declaredTypes
  1858. )
  1859. } else if (node.type === 'ExportNamedDeclaration' && node.declaration) {
  1860. recordType(node.declaration, declaredTypes)
  1861. }
  1862. }
  1863. function extractRuntimeProps(
  1864. node: TSTypeLiteral | TSInterfaceBody,
  1865. props: Record<string, PropTypeData>,
  1866. declaredTypes: Record<string, string[]>,
  1867. isProd: boolean
  1868. ) {
  1869. const members = node.type === 'TSTypeLiteral' ? node.members : node.body
  1870. for (const m of members) {
  1871. if (
  1872. (m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature') &&
  1873. m.key.type === 'Identifier'
  1874. ) {
  1875. let type
  1876. if (m.type === 'TSMethodSignature') {
  1877. type = ['Function']
  1878. } else if (m.typeAnnotation) {
  1879. type = inferRuntimeType(m.typeAnnotation.typeAnnotation, declaredTypes)
  1880. }
  1881. props[m.key.name] = {
  1882. key: m.key.name,
  1883. required: !m.optional,
  1884. type: type || [`null`]
  1885. }
  1886. }
  1887. }
  1888. }
  1889. function inferRuntimeType(
  1890. node: TSType,
  1891. declaredTypes: Record<string, string[]>
  1892. ): string[] {
  1893. switch (node.type) {
  1894. case 'TSStringKeyword':
  1895. return ['String']
  1896. case 'TSNumberKeyword':
  1897. return ['Number']
  1898. case 'TSBooleanKeyword':
  1899. return ['Boolean']
  1900. case 'TSObjectKeyword':
  1901. return ['Object']
  1902. case 'TSTypeLiteral': {
  1903. // TODO (nice to have) generate runtime property validation
  1904. const types = new Set<string>()
  1905. for (const m of node.members) {
  1906. switch (m.type) {
  1907. case 'TSCallSignatureDeclaration':
  1908. case 'TSConstructSignatureDeclaration':
  1909. types.add('Function')
  1910. break
  1911. default:
  1912. types.add('Object')
  1913. }
  1914. }
  1915. return Array.from(types)
  1916. }
  1917. case 'TSFunctionType':
  1918. return ['Function']
  1919. case 'TSArrayType':
  1920. case 'TSTupleType':
  1921. // TODO (nice to have) generate runtime element type/length checks
  1922. return ['Array']
  1923. case 'TSLiteralType':
  1924. switch (node.literal.type) {
  1925. case 'StringLiteral':
  1926. return ['String']
  1927. case 'BooleanLiteral':
  1928. return ['Boolean']
  1929. case 'NumericLiteral':
  1930. case 'BigIntLiteral':
  1931. return ['Number']
  1932. default:
  1933. return [`null`]
  1934. }
  1935. case 'TSTypeReference':
  1936. if (node.typeName.type === 'Identifier') {
  1937. if (declaredTypes[node.typeName.name]) {
  1938. return declaredTypes[node.typeName.name]
  1939. }
  1940. switch (node.typeName.name) {
  1941. case 'Array':
  1942. case 'Function':
  1943. case 'Object':
  1944. case 'Set':
  1945. case 'Map':
  1946. case 'WeakSet':
  1947. case 'WeakMap':
  1948. case 'Date':
  1949. case 'Promise':
  1950. return [node.typeName.name]
  1951. // TS built-in utility types
  1952. // https://www.typescriptlang.org/docs/handbook/utility-types.html
  1953. case 'Partial':
  1954. case 'Required':
  1955. case 'Readonly':
  1956. case 'Record':
  1957. case 'Pick':
  1958. case 'Omit':
  1959. case 'InstanceType':
  1960. return ['Object']
  1961. case 'Uppercase':
  1962. case 'Lowercase':
  1963. case 'Capitalize':
  1964. case 'Uncapitalize':
  1965. return ['String']
  1966. case 'Parameters':
  1967. case 'ConstructorParameters':
  1968. return ['Array']
  1969. case 'NonNullable':
  1970. if (node.typeParameters && node.typeParameters.params[0]) {
  1971. return inferRuntimeType(
  1972. node.typeParameters.params[0],
  1973. declaredTypes
  1974. ).filter(t => t !== 'null')
  1975. }
  1976. case 'Extract':
  1977. if (node.typeParameters && node.typeParameters.params[1]) {
  1978. return inferRuntimeType(
  1979. node.typeParameters.params[1],
  1980. declaredTypes
  1981. )
  1982. }
  1983. case 'Exclude':
  1984. case 'OmitThisParameter':
  1985. if (node.typeParameters && node.typeParameters.params[0]) {
  1986. return inferRuntimeType(
  1987. node.typeParameters.params[0],
  1988. declaredTypes
  1989. )
  1990. }
  1991. // cannot infer, fallback to null: ThisParameterType
  1992. }
  1993. }
  1994. return [`null`]
  1995. case 'TSParenthesizedType':
  1996. return inferRuntimeType(node.typeAnnotation, declaredTypes)
  1997. case 'TSUnionType':
  1998. case 'TSIntersectionType':
  1999. return [
  2000. ...new Set(
  2001. [].concat(
  2002. ...(node.types.map(t => inferRuntimeType(t, declaredTypes)) as any)
  2003. )
  2004. )
  2005. ]
  2006. case 'TSSymbolKeyword':
  2007. return ['Symbol']
  2008. default:
  2009. return [`null`] // no runtime check
  2010. }
  2011. }
  2012. function toRuntimeTypeString(types: string[]) {
  2013. return types.length > 1 ? `[${types.join(', ')}]` : types[0]
  2014. }
  2015. function extractRuntimeEmits(
  2016. node: TSFunctionType | TSTypeLiteral | TSInterfaceBody,
  2017. emits: Set<string>
  2018. ) {
  2019. if (node.type === 'TSTypeLiteral' || node.type === 'TSInterfaceBody') {
  2020. const members = node.type === 'TSTypeLiteral' ? node.members : node.body
  2021. for (let t of members) {
  2022. if (t.type === 'TSCallSignatureDeclaration') {
  2023. extractEventNames(t.parameters[0], emits)
  2024. }
  2025. }
  2026. return
  2027. } else {
  2028. extractEventNames(node.parameters[0], emits)
  2029. }
  2030. }
  2031. function extractEventNames(
  2032. eventName: Identifier | RestElement,
  2033. emits: Set<string>
  2034. ) {
  2035. if (
  2036. eventName.type === 'Identifier' &&
  2037. eventName.typeAnnotation &&
  2038. eventName.typeAnnotation.type === 'TSTypeAnnotation'
  2039. ) {
  2040. const typeNode = eventName.typeAnnotation.typeAnnotation
  2041. if (typeNode.type === 'TSLiteralType') {
  2042. if (
  2043. typeNode.literal.type !== 'UnaryExpression' &&
  2044. typeNode.literal.type !== 'TemplateLiteral'
  2045. ) {
  2046. emits.add(String(typeNode.literal.value))
  2047. }
  2048. } else if (typeNode.type === 'TSUnionType') {
  2049. for (const t of typeNode.types) {
  2050. if (
  2051. t.type === 'TSLiteralType' &&
  2052. t.literal.type !== 'UnaryExpression' &&
  2053. t.literal.type !== 'TemplateLiteral'
  2054. ) {
  2055. emits.add(String(t.literal.value))
  2056. }
  2057. }
  2058. }
  2059. }
  2060. }
  2061. function genRuntimeEmits(emits: Set<string>) {
  2062. return emits.size
  2063. ? `\n emits: [${Array.from(emits)
  2064. .map(p => JSON.stringify(p))
  2065. .join(', ')}],`
  2066. : ``
  2067. }
  2068. function isCallOf(
  2069. node: Node | null | undefined,
  2070. test: string | ((id: string) => boolean) | null | undefined
  2071. ): node is CallExpression {
  2072. return !!(
  2073. node &&
  2074. test &&
  2075. node.type === 'CallExpression' &&
  2076. node.callee.type === 'Identifier' &&
  2077. (typeof test === 'string'
  2078. ? node.callee.name === test
  2079. : test(node.callee.name))
  2080. )
  2081. }
  2082. function canNeverBeRef(node: Node, userReactiveImport?: string): boolean {
  2083. if (isCallOf(node, userReactiveImport)) {
  2084. return true
  2085. }
  2086. switch (node.type) {
  2087. case 'UnaryExpression':
  2088. case 'BinaryExpression':
  2089. case 'ArrayExpression':
  2090. case 'ObjectExpression':
  2091. case 'FunctionExpression':
  2092. case 'ArrowFunctionExpression':
  2093. case 'UpdateExpression':
  2094. case 'ClassExpression':
  2095. case 'TaggedTemplateExpression':
  2096. return true
  2097. case 'SequenceExpression':
  2098. return canNeverBeRef(
  2099. node.expressions[node.expressions.length - 1],
  2100. userReactiveImport
  2101. )
  2102. default:
  2103. if (isLiteralNode(node)) {
  2104. return true
  2105. }
  2106. return false
  2107. }
  2108. }
  2109. function isStaticNode(node: Node): boolean {
  2110. switch (node.type) {
  2111. case 'UnaryExpression': // void 0, !true
  2112. return isStaticNode(node.argument)
  2113. case 'LogicalExpression': // 1 > 2
  2114. case 'BinaryExpression': // 1 + 2
  2115. return isStaticNode(node.left) && isStaticNode(node.right)
  2116. case 'ConditionalExpression': {
  2117. // 1 ? 2 : 3
  2118. return (
  2119. isStaticNode(node.test) &&
  2120. isStaticNode(node.consequent) &&
  2121. isStaticNode(node.alternate)
  2122. )
  2123. }
  2124. case 'SequenceExpression': // (1, 2)
  2125. case 'TemplateLiteral': // `foo${1}`
  2126. return node.expressions.every(expr => isStaticNode(expr))
  2127. case 'ParenthesizedExpression': // (1)
  2128. case 'TSNonNullExpression': // 1!
  2129. case 'TSAsExpression': // 1 as number
  2130. case 'TSTypeAssertion': // (<number>2)
  2131. return isStaticNode(node.expression)
  2132. default:
  2133. if (isLiteralNode(node)) {
  2134. return true
  2135. }
  2136. return false
  2137. }
  2138. }
  2139. function isLiteralNode(node: Node) {
  2140. return node.type.endsWith('Literal')
  2141. }
  2142. /**
  2143. * Analyze bindings in normal `<script>`
  2144. * Note that `compileScriptSetup` already analyzes bindings as part of its
  2145. * compilation process so this should only be used on single `<script>` SFCs.
  2146. */
  2147. function analyzeScriptBindings(ast: Statement[]): BindingMetadata {
  2148. for (const node of ast) {
  2149. if (
  2150. node.type === 'ExportDefaultDeclaration' &&
  2151. node.declaration.type === 'ObjectExpression'
  2152. ) {
  2153. return analyzeBindingsFromOptions(node.declaration)
  2154. }
  2155. }
  2156. return {}
  2157. }
  2158. function analyzeBindingsFromOptions(node: ObjectExpression): BindingMetadata {
  2159. const bindings: BindingMetadata = {}
  2160. // #3270, #3275
  2161. // mark non-script-setup so we don't resolve components/directives from these
  2162. Object.defineProperty(bindings, '__isScriptSetup', {
  2163. enumerable: false,
  2164. value: false
  2165. })
  2166. for (const property of node.properties) {
  2167. if (
  2168. property.type === 'ObjectProperty' &&
  2169. !property.computed &&
  2170. property.key.type === 'Identifier'
  2171. ) {
  2172. // props
  2173. if (property.key.name === 'props') {
  2174. // props: ['foo']
  2175. // props: { foo: ... }
  2176. for (const key of getObjectOrArrayExpressionKeys(property.value)) {
  2177. bindings[key] = BindingTypes.PROPS
  2178. }
  2179. }
  2180. // inject
  2181. else if (property.key.name === 'inject') {
  2182. // inject: ['foo']
  2183. // inject: { foo: {} }
  2184. for (const key of getObjectOrArrayExpressionKeys(property.value)) {
  2185. bindings[key] = BindingTypes.OPTIONS
  2186. }
  2187. }
  2188. // computed & methods
  2189. else if (
  2190. property.value.type === 'ObjectExpression' &&
  2191. (property.key.name === 'computed' || property.key.name === 'methods')
  2192. ) {
  2193. // methods: { foo() {} }
  2194. // computed: { foo() {} }
  2195. for (const key of getObjectExpressionKeys(property.value)) {
  2196. bindings[key] = BindingTypes.OPTIONS
  2197. }
  2198. }
  2199. }
  2200. // setup & data
  2201. else if (
  2202. property.type === 'ObjectMethod' &&
  2203. property.key.type === 'Identifier' &&
  2204. (property.key.name === 'setup' || property.key.name === 'data')
  2205. ) {
  2206. for (const bodyItem of property.body.body) {
  2207. // setup() {
  2208. // return {
  2209. // foo: null
  2210. // }
  2211. // }
  2212. if (
  2213. bodyItem.type === 'ReturnStatement' &&
  2214. bodyItem.argument &&
  2215. bodyItem.argument.type === 'ObjectExpression'
  2216. ) {
  2217. for (const key of getObjectExpressionKeys(bodyItem.argument)) {
  2218. bindings[key] =
  2219. property.key.name === 'setup'
  2220. ? BindingTypes.SETUP_MAYBE_REF
  2221. : BindingTypes.DATA
  2222. }
  2223. }
  2224. }
  2225. }
  2226. }
  2227. return bindings
  2228. }
  2229. function getObjectExpressionKeys(node: ObjectExpression): string[] {
  2230. const keys = []
  2231. for (const prop of node.properties) {
  2232. if (prop.type === 'SpreadElement') continue
  2233. const key = resolveObjectKey(prop.key, prop.computed)
  2234. if (key) keys.push(String(key))
  2235. }
  2236. return keys
  2237. }
  2238. function getArrayExpressionKeys(node: ArrayExpression): string[] {
  2239. const keys = []
  2240. for (const element of node.elements) {
  2241. if (element && element.type === 'StringLiteral') {
  2242. keys.push(element.value)
  2243. }
  2244. }
  2245. return keys
  2246. }
  2247. function getObjectOrArrayExpressionKeys(value: Node): string[] {
  2248. if (value.type === 'ArrayExpression') {
  2249. return getArrayExpressionKeys(value)
  2250. }
  2251. if (value.type === 'ObjectExpression') {
  2252. return getObjectExpressionKeys(value)
  2253. }
  2254. return []
  2255. }
  2256. const templateUsageCheckCache = createCache<string>()
  2257. function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
  2258. const { content, ast } = sfc.template!
  2259. const cached = templateUsageCheckCache.get(content)
  2260. if (cached) {
  2261. return cached
  2262. }
  2263. let code = ''
  2264. transform(createRoot([ast]), {
  2265. nodeTransforms: [
  2266. node => {
  2267. if (node.type === NodeTypes.ELEMENT) {
  2268. if (
  2269. !parserOptions.isNativeTag!(node.tag) &&
  2270. !parserOptions.isBuiltInComponent!(node.tag)
  2271. ) {
  2272. code += `,${camelize(node.tag)},${capitalize(camelize(node.tag))}`
  2273. }
  2274. for (let i = 0; i < node.props.length; i++) {
  2275. const prop = node.props[i]
  2276. if (prop.type === NodeTypes.DIRECTIVE) {
  2277. if (!isBuiltInDir(prop.name)) {
  2278. code += `,v${capitalize(camelize(prop.name))}`
  2279. }
  2280. if (prop.exp) {
  2281. code += `,${processExp(
  2282. (prop.exp as SimpleExpressionNode).content,
  2283. prop.name
  2284. )}`
  2285. }
  2286. }
  2287. }
  2288. } else if (node.type === NodeTypes.INTERPOLATION) {
  2289. code += `,${processExp(
  2290. (node.content as SimpleExpressionNode).content
  2291. )}`
  2292. }
  2293. }
  2294. ]
  2295. })
  2296. code += ';'
  2297. templateUsageCheckCache.set(content, code)
  2298. return code
  2299. }
  2300. const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
  2301. function processExp(exp: string, dir?: string): string {
  2302. if (/ as\s+\w|<.*>|:/.test(exp)) {
  2303. if (dir === 'slot') {
  2304. exp = `(${exp})=>{}`
  2305. } else if (dir === 'on') {
  2306. exp = `()=>{return ${exp}}`
  2307. } else if (dir === 'for') {
  2308. const inMatch = exp.match(forAliasRE)
  2309. if (inMatch) {
  2310. const [, LHS, RHS] = inMatch
  2311. return processExp(`(${LHS})=>{}`) + processExp(RHS)
  2312. }
  2313. }
  2314. let ret = ''
  2315. // has potential type cast or generic arguments that uses types
  2316. const ast = parseExpression(exp, { plugins: ['typescript'] })
  2317. walkIdentifiers(ast, node => {
  2318. ret += `,` + node.name
  2319. })
  2320. return ret
  2321. }
  2322. return stripStrings(exp)
  2323. }
  2324. function stripStrings(exp: string) {
  2325. return exp
  2326. .replace(/'[^']*'|"[^"]*"/g, '')
  2327. .replace(/`[^`]+`/g, stripTemplateString)
  2328. }
  2329. function stripTemplateString(str: string): string {
  2330. const interpMatch = str.match(/\${[^}]+}/g)
  2331. if (interpMatch) {
  2332. return interpMatch.map(m => m.slice(2, -1)).join(',')
  2333. }
  2334. return ''
  2335. }
  2336. function isImportUsed(local: string, sfc: SFCDescriptor): boolean {
  2337. return new RegExp(
  2338. // #4274 escape $ since it's a special char in regex
  2339. // (and is the only regex special char that is valid in identifiers)
  2340. `[^\\w$_]${local.replace(/\$/g, '\\$')}[^\\w$_]`
  2341. ).test(resolveTemplateUsageCheckString(sfc))
  2342. }
  2343. /**
  2344. * Note: this comparison assumes the prev/next script are already identical,
  2345. * and only checks the special case where <script setup lang="ts"> unused import
  2346. * pruning result changes due to template changes.
  2347. */
  2348. export function hmrShouldReload(
  2349. prevImports: Record<string, ImportBinding>,
  2350. next: SFCDescriptor
  2351. ): boolean {
  2352. if (
  2353. !next.scriptSetup ||
  2354. (next.scriptSetup.lang !== 'ts' && next.scriptSetup.lang !== 'tsx')
  2355. ) {
  2356. return false
  2357. }
  2358. // for each previous import, check if its used status remain the same based on
  2359. // the next descriptor's template
  2360. for (const key in prevImports) {
  2361. // if an import was previous unused, but now is used, we need to force
  2362. // reload so that the script now includes that import.
  2363. if (!prevImports[key].isUsedInTemplate && isImportUsed(key, next)) {
  2364. return true
  2365. }
  2366. }
  2367. return false
  2368. }
  2369. export function resolveObjectKey(node: Node, computed: boolean) {
  2370. switch (node.type) {
  2371. case 'StringLiteral':
  2372. case 'NumericLiteral':
  2373. return node.value
  2374. case 'Identifier':
  2375. if (!computed) return node.name
  2376. }
  2377. return undefined
  2378. }