compileScript.ts 75 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540
  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] &&
  1049. defaultExport.declaration.arguments[0].type === 'ObjectExpression'
  1050. ) {
  1051. optionProperties = defaultExport.declaration.arguments[0].properties
  1052. }
  1053. if (optionProperties) {
  1054. for (const s of optionProperties) {
  1055. if (
  1056. s.type === 'ObjectProperty' &&
  1057. s.key.type === 'Identifier' &&
  1058. s.key.name === 'name'
  1059. ) {
  1060. hasDefaultExportName = true
  1061. }
  1062. if (
  1063. (s.type === 'ObjectMethod' || s.type === 'ObjectProperty') &&
  1064. s.key.type === 'Identifier' &&
  1065. s.key.name === 'render'
  1066. ) {
  1067. // TODO warn when we provide a better way to do it?
  1068. hasDefaultExportRender = true
  1069. }
  1070. }
  1071. }
  1072. // export default { ... } --> const __default__ = { ... }
  1073. const start = node.start! + scriptStartOffset!
  1074. const end = node.declaration.start! + scriptStartOffset!
  1075. s.overwrite(start, end, `const ${DEFAULT_VAR} = `)
  1076. } else if (node.type === 'ExportNamedDeclaration') {
  1077. const defaultSpecifier = node.specifiers.find(
  1078. s => s.exported.type === 'Identifier' && s.exported.name === 'default'
  1079. ) as ExportSpecifier
  1080. if (defaultSpecifier) {
  1081. defaultExport = node
  1082. // 1. remove specifier
  1083. if (node.specifiers.length > 1) {
  1084. s.remove(
  1085. defaultSpecifier.start! + scriptStartOffset!,
  1086. defaultSpecifier.end! + scriptStartOffset!
  1087. )
  1088. } else {
  1089. s.remove(
  1090. node.start! + scriptStartOffset!,
  1091. node.end! + scriptStartOffset!
  1092. )
  1093. }
  1094. if (node.source) {
  1095. // export { x as default } from './x'
  1096. // rewrite to `import { x as __default__ } from './x'` and
  1097. // add to top
  1098. s.prepend(
  1099. `import { ${defaultSpecifier.local.name} as ${DEFAULT_VAR} } from '${node.source.value}'\n`
  1100. )
  1101. } else {
  1102. // export { x as default }
  1103. // rewrite to `const __default__ = x` and move to end
  1104. s.appendLeft(
  1105. scriptEndOffset!,
  1106. `\nconst ${DEFAULT_VAR} = ${defaultSpecifier.local.name}\n`
  1107. )
  1108. }
  1109. }
  1110. if (node.declaration) {
  1111. walkDeclaration(node.declaration, scriptBindings, vueImportAliases)
  1112. }
  1113. } else if (
  1114. (node.type === 'VariableDeclaration' ||
  1115. node.type === 'FunctionDeclaration' ||
  1116. node.type === 'ClassDeclaration' ||
  1117. node.type === 'TSEnumDeclaration') &&
  1118. !node.declare
  1119. ) {
  1120. walkDeclaration(node, scriptBindings, vueImportAliases)
  1121. }
  1122. }
  1123. // apply reactivity transform
  1124. if (enableReactivityTransform && shouldTransform(script.content)) {
  1125. const { rootRefs, importedHelpers } = transformAST(
  1126. scriptAst,
  1127. s,
  1128. scriptStartOffset!
  1129. )
  1130. refBindings = rootRefs
  1131. for (const h of importedHelpers) {
  1132. helperImports.add(h)
  1133. }
  1134. }
  1135. // <script> after <script setup>
  1136. // we need to move the block up so that `const __default__` is
  1137. // declared before being used in the actual component definition
  1138. if (scriptStartOffset! > startOffset) {
  1139. // if content doesn't end with newline, add one
  1140. if (!/\n$/.test(script.content.trim())) {
  1141. s.appendLeft(scriptEndOffset!, `\n`)
  1142. }
  1143. s.move(scriptStartOffset!, scriptEndOffset!, 0)
  1144. }
  1145. }
  1146. // 2.2 process <script setup> body
  1147. for (const node of scriptSetupAst.body) {
  1148. // (Dropped) `ref: x` bindings
  1149. // TODO remove when out of experimental
  1150. if (
  1151. node.type === 'LabeledStatement' &&
  1152. node.label.name === 'ref' &&
  1153. node.body.type === 'ExpressionStatement'
  1154. ) {
  1155. error(
  1156. `ref sugar using the label syntax was an experimental proposal and ` +
  1157. `has been dropped based on community feedback. Please check out ` +
  1158. `the new proposal at https://github.com/vuejs/rfcs/discussions/369`,
  1159. node
  1160. )
  1161. }
  1162. if (node.type === 'ExpressionStatement') {
  1163. const expr = unwrapTSNode(node.expression)
  1164. // process `defineProps` and `defineEmit(s)` calls
  1165. if (
  1166. processDefineProps(expr) ||
  1167. processDefineEmits(expr) ||
  1168. processDefineOptions(expr) ||
  1169. processWithDefaults(expr)
  1170. ) {
  1171. s.remove(node.start! + startOffset, node.end! + startOffset)
  1172. } else if (processDefineExpose(expr)) {
  1173. // defineExpose({}) -> expose({})
  1174. const callee = (expr as CallExpression).callee
  1175. s.overwrite(
  1176. callee.start! + startOffset,
  1177. callee.end! + startOffset,
  1178. 'expose'
  1179. )
  1180. }
  1181. }
  1182. if (node.type === 'VariableDeclaration' && !node.declare) {
  1183. const total = node.declarations.length
  1184. let left = total
  1185. let lastNonRemoved: number | undefined
  1186. for (let i = 0; i < total; i++) {
  1187. const decl = node.declarations[i]
  1188. const init = decl.init && unwrapTSNode(decl.init)
  1189. if (init) {
  1190. if (processDefineOptions(init)) {
  1191. error(
  1192. `${DEFINE_OPTIONS}() has no returning value, it cannot be assigned.`,
  1193. node
  1194. )
  1195. }
  1196. // defineProps / defineEmits
  1197. const isDefineProps =
  1198. processDefineProps(init, decl.id, node.kind) ||
  1199. processWithDefaults(init, decl.id, node.kind)
  1200. const isDefineEmits = processDefineEmits(init, decl.id)
  1201. if (isDefineProps || isDefineEmits) {
  1202. if (left === 1) {
  1203. s.remove(node.start! + startOffset, node.end! + startOffset)
  1204. } else {
  1205. let start = decl.start! + startOffset
  1206. let end = decl.end! + startOffset
  1207. if (i === total - 1) {
  1208. // last one, locate the end of the last one that is not removed
  1209. // if we arrive at this branch, there must have been a
  1210. // non-removed decl before us, so lastNonRemoved is non-null.
  1211. start = node.declarations[lastNonRemoved!].end! + startOffset
  1212. } else {
  1213. // not the last one, locate the start of the next
  1214. end = node.declarations[i + 1].start! + startOffset
  1215. }
  1216. s.remove(start, end)
  1217. left--
  1218. }
  1219. } else {
  1220. lastNonRemoved = i
  1221. }
  1222. }
  1223. }
  1224. }
  1225. let isAllLiteral = false
  1226. // walk declarations to record declared bindings
  1227. if (
  1228. (node.type === 'VariableDeclaration' ||
  1229. node.type === 'FunctionDeclaration' ||
  1230. node.type === 'ClassDeclaration' ||
  1231. node.type === 'TSEnumDeclaration') &&
  1232. !node.declare
  1233. ) {
  1234. isAllLiteral = walkDeclaration(node, setupBindings, vueImportAliases)
  1235. }
  1236. // hoist literal constants
  1237. if (hoistStatic && isAllLiteral) {
  1238. hoistNode(node)
  1239. }
  1240. // walk statements & named exports / variable declarations for top level
  1241. // await
  1242. if (
  1243. (node.type === 'VariableDeclaration' && !node.declare) ||
  1244. node.type.endsWith('Statement')
  1245. ) {
  1246. const scope: Statement[][] = [scriptSetupAst.body]
  1247. ;(walk as any)(node, {
  1248. enter(child: Node, parent: Node) {
  1249. if (isFunctionType(child)) {
  1250. this.skip()
  1251. }
  1252. if (child.type === 'BlockStatement') {
  1253. scope.push(child.body)
  1254. }
  1255. if (child.type === 'AwaitExpression') {
  1256. hasAwait = true
  1257. // if the await expression is an expression statement and
  1258. // - is in the root scope
  1259. // - or is not the first statement in a nested block scope
  1260. // then it needs a semicolon before the generated code.
  1261. const currentScope = scope[scope.length - 1]
  1262. const needsSemi = currentScope.some((n, i) => {
  1263. return (
  1264. (scope.length === 1 || i > 0) &&
  1265. n.type === 'ExpressionStatement' &&
  1266. n.start === child.start
  1267. )
  1268. })
  1269. processAwait(
  1270. child,
  1271. needsSemi,
  1272. parent.type === 'ExpressionStatement'
  1273. )
  1274. }
  1275. },
  1276. exit(node: Node) {
  1277. if (node.type === 'BlockStatement') scope.pop()
  1278. }
  1279. })
  1280. }
  1281. if (
  1282. (node.type === 'ExportNamedDeclaration' && node.exportKind !== 'type') ||
  1283. node.type === 'ExportAllDeclaration' ||
  1284. node.type === 'ExportDefaultDeclaration'
  1285. ) {
  1286. error(
  1287. `<script setup> cannot contain ES module exports. ` +
  1288. `If you are using a previous version of <script setup>, please ` +
  1289. `consult the updated RFC at https://github.com/vuejs/rfcs/pull/227.`,
  1290. node
  1291. )
  1292. }
  1293. if (isTS) {
  1294. // move all Type declarations to outer scope
  1295. if (
  1296. (node.type.startsWith('TS') ||
  1297. (node.type === 'ExportNamedDeclaration' &&
  1298. node.exportKind === 'type') ||
  1299. (node.type === 'VariableDeclaration' && node.declare)) &&
  1300. node.type !== 'TSEnumDeclaration'
  1301. ) {
  1302. recordType(node, declaredTypes)
  1303. hoistNode(node)
  1304. }
  1305. }
  1306. }
  1307. // 3. Apply reactivity transform
  1308. if (
  1309. (enableReactivityTransform &&
  1310. // normal <script> had ref bindings that maybe used in <script setup>
  1311. (refBindings || shouldTransform(scriptSetup.content))) ||
  1312. propsDestructureDecl
  1313. ) {
  1314. const { rootRefs, importedHelpers } = transformAST(
  1315. scriptSetupAst,
  1316. s,
  1317. startOffset,
  1318. refBindings,
  1319. propsDestructuredBindings
  1320. )
  1321. refBindings = refBindings ? [...refBindings, ...rootRefs] : rootRefs
  1322. for (const h of importedHelpers) {
  1323. helperImports.add(h)
  1324. }
  1325. }
  1326. // 4. extract runtime props/emits code from setup context type
  1327. if (propsTypeDecl) {
  1328. extractRuntimeProps(propsTypeDecl, typeDeclaredProps, declaredTypes, isProd)
  1329. }
  1330. if (emitsTypeDecl) {
  1331. extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits)
  1332. }
  1333. // 5. check useOptions args to make sure it doesn't reference setup scope
  1334. // variables
  1335. checkInvalidScopeReference(propsRuntimeDecl, DEFINE_PROPS)
  1336. checkInvalidScopeReference(propsRuntimeDefaults, DEFINE_PROPS)
  1337. checkInvalidScopeReference(propsDestructureDecl, DEFINE_PROPS)
  1338. checkInvalidScopeReference(emitsRuntimeDecl, DEFINE_EMITS)
  1339. checkInvalidScopeReference(optionsRuntimeDecl, DEFINE_OPTIONS)
  1340. // 6. remove non-script content
  1341. if (script) {
  1342. if (startOffset < scriptStartOffset!) {
  1343. // <script setup> before <script>
  1344. s.remove(0, startOffset)
  1345. s.remove(endOffset, scriptStartOffset!)
  1346. s.remove(scriptEndOffset!, source.length)
  1347. } else {
  1348. // <script> before <script setup>
  1349. s.remove(0, scriptStartOffset!)
  1350. s.remove(scriptEndOffset!, startOffset)
  1351. s.remove(endOffset, source.length)
  1352. }
  1353. } else {
  1354. // only <script setup>
  1355. s.remove(0, startOffset)
  1356. s.remove(endOffset, source.length)
  1357. }
  1358. // 7. analyze binding metadata
  1359. if (scriptAst) {
  1360. Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst.body))
  1361. }
  1362. if (propsRuntimeDecl) {
  1363. for (const key of getObjectOrArrayExpressionKeys(propsRuntimeDecl)) {
  1364. bindingMetadata[key] = BindingTypes.PROPS
  1365. }
  1366. }
  1367. for (const key in typeDeclaredProps) {
  1368. bindingMetadata[key] = BindingTypes.PROPS
  1369. }
  1370. // props aliases
  1371. if (propsDestructureDecl) {
  1372. if (propsDestructureRestId) {
  1373. bindingMetadata[propsDestructureRestId] =
  1374. BindingTypes.SETUP_REACTIVE_CONST
  1375. }
  1376. for (const key in propsDestructuredBindings) {
  1377. const { local } = propsDestructuredBindings[key]
  1378. if (local !== key) {
  1379. bindingMetadata[local] = BindingTypes.PROPS_ALIASED
  1380. ;(bindingMetadata.__propsAliases ||
  1381. (bindingMetadata.__propsAliases = {}))[local] = key
  1382. }
  1383. }
  1384. }
  1385. for (const [key, { isType, imported, source }] of Object.entries(
  1386. userImports
  1387. )) {
  1388. if (isType) continue
  1389. bindingMetadata[key] =
  1390. imported === '*' ||
  1391. (imported === 'default' && source.endsWith('.vue')) ||
  1392. source === 'vue'
  1393. ? BindingTypes.SETUP_CONST
  1394. : BindingTypes.SETUP_MAYBE_REF
  1395. }
  1396. for (const key in scriptBindings) {
  1397. bindingMetadata[key] = scriptBindings[key]
  1398. }
  1399. for (const key in setupBindings) {
  1400. bindingMetadata[key] = setupBindings[key]
  1401. }
  1402. // known ref bindings
  1403. if (refBindings) {
  1404. for (const key of refBindings) {
  1405. bindingMetadata[key] = BindingTypes.SETUP_REF
  1406. }
  1407. }
  1408. // 8. inject `useCssVars` calls
  1409. if (
  1410. cssVars.length &&
  1411. // no need to do this when targeting SSR
  1412. !(options.inlineTemplate && options.templateOptions?.ssr)
  1413. ) {
  1414. helperImports.add(CSS_VARS_HELPER)
  1415. helperImports.add('unref')
  1416. s.prependLeft(
  1417. startOffset,
  1418. `\n${genCssVarsCode(cssVars, bindingMetadata, scopeId, isProd)}\n`
  1419. )
  1420. }
  1421. // 9. finalize setup() argument signature
  1422. let args = `__props`
  1423. if (propsTypeDecl) {
  1424. // mark as any and only cast on assignment
  1425. // since the user defined complex types may be incompatible with the
  1426. // inferred type from generated runtime declarations
  1427. args += `: any`
  1428. }
  1429. // inject user assignment of props
  1430. // we use a default __props so that template expressions referencing props
  1431. // can use it directly
  1432. if (propsIdentifier) {
  1433. s.prependLeft(
  1434. startOffset,
  1435. `\nconst ${propsIdentifier} = __props${
  1436. propsTypeDecl ? ` as ${genSetupPropsType(propsTypeDecl)}` : ``
  1437. };\n`
  1438. )
  1439. }
  1440. if (propsDestructureRestId) {
  1441. s.prependLeft(
  1442. startOffset,
  1443. `\nconst ${propsDestructureRestId} = ${helper(
  1444. `createPropsRestProxy`
  1445. )}(__props, ${JSON.stringify(Object.keys(propsDestructuredBindings))});\n`
  1446. )
  1447. }
  1448. // inject temp variables for async context preservation
  1449. if (hasAwait) {
  1450. const any = isTS ? `: any` : ``
  1451. s.prependLeft(startOffset, `\nlet __temp${any}, __restore${any}\n`)
  1452. }
  1453. const destructureElements =
  1454. hasDefineExposeCall || !options.inlineTemplate ? [`expose`] : []
  1455. if (emitIdentifier) {
  1456. destructureElements.push(
  1457. emitIdentifier === `emit` ? `emit` : `emit: ${emitIdentifier}`
  1458. )
  1459. }
  1460. if (destructureElements.length) {
  1461. args += `, { ${destructureElements.join(', ')} }`
  1462. if (emitsTypeDecl) {
  1463. const content = emitsTypeDecl.__fromNormalScript
  1464. ? script!.content
  1465. : scriptSetup.content
  1466. args += `: { emit: (${content.slice(
  1467. emitsTypeDecl.start!,
  1468. emitsTypeDecl.end!
  1469. )}), expose: any, slots: any, attrs: any }`
  1470. }
  1471. }
  1472. // 10. generate return statement
  1473. let returned
  1474. if (!options.inlineTemplate || (!sfc.template && hasDefaultExportRender)) {
  1475. // non-inline mode, or has manual render in normal <script>
  1476. // return bindings from script and script setup
  1477. const allBindings: Record<string, any> = {
  1478. ...scriptBindings,
  1479. ...setupBindings
  1480. }
  1481. for (const key in userImports) {
  1482. if (!userImports[key].isType && userImports[key].isUsedInTemplate) {
  1483. allBindings[key] = true
  1484. }
  1485. }
  1486. returned = `{ `
  1487. for (const key in allBindings) {
  1488. if (
  1489. allBindings[key] === true &&
  1490. userImports[key].source !== 'vue' &&
  1491. !userImports[key].source.endsWith('.vue')
  1492. ) {
  1493. // generate getter for import bindings
  1494. // skip vue imports since we know they will never change
  1495. returned += `get ${key}() { return ${key} }, `
  1496. } else if (bindingMetadata[key] === BindingTypes.SETUP_LET) {
  1497. // local let binding, also add setter
  1498. const setArg = key === 'v' ? `_v` : `v`
  1499. returned +=
  1500. `get ${key}() { return ${key} }, ` +
  1501. `set ${key}(${setArg}) { ${key} = ${setArg} }, `
  1502. } else {
  1503. returned += `${key}, `
  1504. }
  1505. }
  1506. returned = returned.replace(/, $/, '') + ` }`
  1507. } else {
  1508. // inline mode
  1509. if (sfc.template && !sfc.template.src) {
  1510. if (options.templateOptions && options.templateOptions.ssr) {
  1511. hasInlinedSsrRenderFn = true
  1512. }
  1513. // inline render function mode - we are going to compile the template and
  1514. // inline it right here
  1515. const { code, ast, preamble, tips, errors } = compileTemplate({
  1516. filename,
  1517. source: sfc.template.content,
  1518. inMap: sfc.template.map,
  1519. ...options.templateOptions,
  1520. id: scopeId,
  1521. scoped: sfc.styles.some(s => s.scoped),
  1522. isProd: options.isProd,
  1523. ssrCssVars: sfc.cssVars,
  1524. compilerOptions: {
  1525. ...(options.templateOptions &&
  1526. options.templateOptions.compilerOptions),
  1527. inline: true,
  1528. isTS,
  1529. bindingMetadata
  1530. }
  1531. })
  1532. if (tips.length) {
  1533. tips.forEach(warnOnce)
  1534. }
  1535. const err = errors[0]
  1536. if (typeof err === 'string') {
  1537. throw new Error(err)
  1538. } else if (err) {
  1539. if (err.loc) {
  1540. err.message +=
  1541. `\n\n` +
  1542. sfc.filename +
  1543. '\n' +
  1544. generateCodeFrame(
  1545. source,
  1546. err.loc.start.offset,
  1547. err.loc.end.offset
  1548. ) +
  1549. `\n`
  1550. }
  1551. throw err
  1552. }
  1553. if (preamble) {
  1554. s.prepend(preamble)
  1555. }
  1556. // avoid duplicated unref import
  1557. // as this may get injected by the render function preamble OR the
  1558. // css vars codegen
  1559. if (ast && ast.helpers.has(UNREF)) {
  1560. helperImports.delete('unref')
  1561. }
  1562. returned = code
  1563. } else {
  1564. returned = `() => {}`
  1565. }
  1566. }
  1567. if (!options.inlineTemplate && !__TEST__) {
  1568. // in non-inline mode, the `__isScriptSetup: true` flag is used by
  1569. // componentPublicInstance proxy to allow properties that start with $ or _
  1570. s.appendRight(
  1571. endOffset,
  1572. `\nconst __returned__ = ${returned}\n` +
  1573. `Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })\n` +
  1574. `return __returned__` +
  1575. `\n}\n\n`
  1576. )
  1577. } else {
  1578. s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)
  1579. }
  1580. // 11. finalize default export
  1581. let runtimeOptions = ``
  1582. if (!hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {
  1583. const match = filename.match(/([^/\\]+)\.\w+$/)
  1584. if (match) {
  1585. runtimeOptions += `\n __name: '${match[1]}',`
  1586. }
  1587. }
  1588. if (hasInlinedSsrRenderFn) {
  1589. runtimeOptions += `\n __ssrInlineRender: true,`
  1590. }
  1591. if (propsRuntimeDecl) {
  1592. let declCode = scriptSetup.content
  1593. .slice(propsRuntimeDecl.start!, propsRuntimeDecl.end!)
  1594. .trim()
  1595. if (propsDestructureDecl) {
  1596. const defaults: string[] = []
  1597. for (const key in propsDestructuredBindings) {
  1598. const d = genDestructuredDefaultValue(key)
  1599. if (d) defaults.push(`${key}: ${d}`)
  1600. }
  1601. if (defaults.length) {
  1602. declCode = `${helper(
  1603. `mergeDefaults`
  1604. )}(${declCode}, {\n ${defaults.join(',\n ')}\n})`
  1605. }
  1606. }
  1607. runtimeOptions += `\n props: ${declCode},`
  1608. } else if (propsTypeDecl) {
  1609. runtimeOptions += genRuntimeProps(typeDeclaredProps)
  1610. }
  1611. if (emitsRuntimeDecl) {
  1612. runtimeOptions += `\n emits: ${scriptSetup.content
  1613. .slice(emitsRuntimeDecl.start!, emitsRuntimeDecl.end!)
  1614. .trim()},`
  1615. } else if (emitsTypeDecl) {
  1616. runtimeOptions += genRuntimeEmits(typeDeclaredEmits)
  1617. }
  1618. let definedOptions = ''
  1619. if (optionsRuntimeDecl) {
  1620. definedOptions = scriptSetup.content
  1621. .slice(optionsRuntimeDecl.start!, optionsRuntimeDecl.end!)
  1622. .trim()
  1623. }
  1624. // <script setup> components are closed by default. If the user did not
  1625. // explicitly call `defineExpose`, call expose() with no args.
  1626. const exposeCall =
  1627. hasDefineExposeCall || options.inlineTemplate ? `` : ` expose();\n`
  1628. // wrap setup code with function.
  1629. if (isTS) {
  1630. // for TS, make sure the exported type is still valid type with
  1631. // correct props information
  1632. // we have to use object spread for types to be merged properly
  1633. // user's TS setting should compile it down to proper targets
  1634. // export default defineComponent({ ...__default__, ... })
  1635. const def =
  1636. (defaultExport ? `\n ...${DEFAULT_VAR},` : ``) +
  1637. (definedOptions ? `\n ...${definedOptions},` : '')
  1638. s.prependLeft(
  1639. startOffset,
  1640. `\nexport default /*#__PURE__*/${helper(
  1641. `defineComponent`
  1642. )}({${def}${runtimeOptions}\n ${
  1643. hasAwait ? `async ` : ``
  1644. }setup(${args}) {\n${exposeCall}`
  1645. )
  1646. s.appendRight(endOffset, `})`)
  1647. } else {
  1648. if (defaultExport || definedOptions) {
  1649. // without TS, can't rely on rest spread, so we use Object.assign
  1650. // export default Object.assign(__default__, { ... })
  1651. s.prependLeft(
  1652. startOffset,
  1653. `\nexport default /*#__PURE__*/Object.assign(${
  1654. defaultExport ? `${DEFAULT_VAR}, ` : ''
  1655. }${definedOptions ? `${definedOptions}, ` : ''}{${runtimeOptions}\n ` +
  1656. `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`
  1657. )
  1658. s.appendRight(endOffset, `})`)
  1659. } else {
  1660. s.prependLeft(
  1661. startOffset,
  1662. `\nexport default {${runtimeOptions}\n ` +
  1663. `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`
  1664. )
  1665. s.appendRight(endOffset, `}`)
  1666. }
  1667. }
  1668. // 12. finalize Vue helper imports
  1669. if (helperImports.size > 0) {
  1670. s.prepend(
  1671. `import { ${[...helperImports]
  1672. .map(h => `${h} as _${h}`)
  1673. .join(', ')} } from 'vue'\n`
  1674. )
  1675. }
  1676. s.trim()
  1677. return {
  1678. ...scriptSetup,
  1679. s,
  1680. bindings: bindingMetadata,
  1681. imports: userImports,
  1682. content: s.toString(),
  1683. map: genSourceMap
  1684. ? (s.generateMap({
  1685. source: filename,
  1686. hires: true,
  1687. includeContent: true
  1688. }) as unknown as RawSourceMap)
  1689. : undefined,
  1690. scriptAst: scriptAst?.body,
  1691. scriptSetupAst: scriptSetupAst?.body
  1692. }
  1693. }
  1694. function registerBinding(
  1695. bindings: Record<string, BindingTypes>,
  1696. node: Identifier,
  1697. type: BindingTypes
  1698. ) {
  1699. bindings[node.name] = type
  1700. }
  1701. function walkDeclaration(
  1702. node: Declaration,
  1703. bindings: Record<string, BindingTypes>,
  1704. userImportAliases: Record<string, string>
  1705. ): boolean {
  1706. let isAllLiteral = false
  1707. if (node.type === 'VariableDeclaration') {
  1708. const isConst = node.kind === 'const'
  1709. isAllLiteral =
  1710. isConst &&
  1711. node.declarations.every(
  1712. decl => decl.id.type === 'Identifier' && isStaticNode(decl.init!)
  1713. )
  1714. // export const foo = ...
  1715. for (const { id, init: _init } of node.declarations) {
  1716. const init = _init && unwrapTSNode(_init)
  1717. const isDefineCall = !!(
  1718. isConst &&
  1719. isCallOf(
  1720. init,
  1721. c => c === DEFINE_PROPS || c === DEFINE_EMITS || c === WITH_DEFAULTS
  1722. )
  1723. )
  1724. if (id.type === 'Identifier') {
  1725. let bindingType
  1726. const userReactiveBinding = userImportAliases['reactive']
  1727. if (isAllLiteral || (isConst && isStaticNode(init!))) {
  1728. bindingType = BindingTypes.LITERAL_CONST
  1729. } else if (isCallOf(init, userReactiveBinding)) {
  1730. // treat reactive() calls as let since it's meant to be mutable
  1731. bindingType = isConst
  1732. ? BindingTypes.SETUP_REACTIVE_CONST
  1733. : BindingTypes.SETUP_LET
  1734. } else if (
  1735. // if a declaration is a const literal, we can mark it so that
  1736. // the generated render fn code doesn't need to unref() it
  1737. isDefineCall ||
  1738. (isConst && canNeverBeRef(init!, userReactiveBinding))
  1739. ) {
  1740. bindingType = isCallOf(init, DEFINE_PROPS)
  1741. ? BindingTypes.SETUP_REACTIVE_CONST
  1742. : BindingTypes.SETUP_CONST
  1743. } else if (isConst) {
  1744. if (isCallOf(init, userImportAliases['ref'])) {
  1745. bindingType = BindingTypes.SETUP_REF
  1746. } else {
  1747. bindingType = BindingTypes.SETUP_MAYBE_REF
  1748. }
  1749. } else {
  1750. bindingType = BindingTypes.SETUP_LET
  1751. }
  1752. registerBinding(bindings, id, bindingType)
  1753. } else {
  1754. if (isCallOf(init, DEFINE_PROPS)) {
  1755. continue
  1756. }
  1757. if (id.type === 'ObjectPattern') {
  1758. walkObjectPattern(id, bindings, isConst, isDefineCall)
  1759. } else if (id.type === 'ArrayPattern') {
  1760. walkArrayPattern(id, bindings, isConst, isDefineCall)
  1761. }
  1762. }
  1763. }
  1764. } else if (node.type === 'TSEnumDeclaration') {
  1765. isAllLiteral = node.members.every(
  1766. member => !member.initializer || isStaticNode(member.initializer)
  1767. )
  1768. bindings[node.id!.name] = isAllLiteral
  1769. ? BindingTypes.LITERAL_CONST
  1770. : BindingTypes.SETUP_CONST
  1771. } else if (
  1772. node.type === 'FunctionDeclaration' ||
  1773. node.type === 'ClassDeclaration'
  1774. ) {
  1775. // export function foo() {} / export class Foo {}
  1776. // export declarations must be named.
  1777. bindings[node.id!.name] = BindingTypes.SETUP_CONST
  1778. }
  1779. return isAllLiteral
  1780. }
  1781. function walkObjectPattern(
  1782. node: ObjectPattern,
  1783. bindings: Record<string, BindingTypes>,
  1784. isConst: boolean,
  1785. isDefineCall = false
  1786. ) {
  1787. for (const p of node.properties) {
  1788. if (p.type === 'ObjectProperty') {
  1789. if (p.key.type === 'Identifier' && p.key === p.value) {
  1790. // shorthand: const { x } = ...
  1791. const type = isDefineCall
  1792. ? BindingTypes.SETUP_CONST
  1793. : isConst
  1794. ? BindingTypes.SETUP_MAYBE_REF
  1795. : BindingTypes.SETUP_LET
  1796. registerBinding(bindings, p.key, type)
  1797. } else {
  1798. walkPattern(p.value, bindings, isConst, isDefineCall)
  1799. }
  1800. } else {
  1801. // ...rest
  1802. // argument can only be identifier when destructuring
  1803. const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET
  1804. registerBinding(bindings, p.argument as Identifier, type)
  1805. }
  1806. }
  1807. }
  1808. function walkArrayPattern(
  1809. node: ArrayPattern,
  1810. bindings: Record<string, BindingTypes>,
  1811. isConst: boolean,
  1812. isDefineCall = false
  1813. ) {
  1814. for (const e of node.elements) {
  1815. e && walkPattern(e, bindings, isConst, isDefineCall)
  1816. }
  1817. }
  1818. function walkPattern(
  1819. node: Node,
  1820. bindings: Record<string, BindingTypes>,
  1821. isConst: boolean,
  1822. isDefineCall = false
  1823. ) {
  1824. if (node.type === 'Identifier') {
  1825. const type = isDefineCall
  1826. ? BindingTypes.SETUP_CONST
  1827. : isConst
  1828. ? BindingTypes.SETUP_MAYBE_REF
  1829. : BindingTypes.SETUP_LET
  1830. registerBinding(bindings, node, type)
  1831. } else if (node.type === 'RestElement') {
  1832. // argument can only be identifier when destructuring
  1833. const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET
  1834. registerBinding(bindings, node.argument as Identifier, type)
  1835. } else if (node.type === 'ObjectPattern') {
  1836. walkObjectPattern(node, bindings, isConst)
  1837. } else if (node.type === 'ArrayPattern') {
  1838. walkArrayPattern(node, bindings, isConst)
  1839. } else if (node.type === 'AssignmentPattern') {
  1840. if (node.left.type === 'Identifier') {
  1841. const type = isDefineCall
  1842. ? BindingTypes.SETUP_CONST
  1843. : isConst
  1844. ? BindingTypes.SETUP_MAYBE_REF
  1845. : BindingTypes.SETUP_LET
  1846. registerBinding(bindings, node.left, type)
  1847. } else {
  1848. walkPattern(node.left, bindings, isConst)
  1849. }
  1850. }
  1851. }
  1852. interface PropTypeData {
  1853. key: string
  1854. type: string[]
  1855. required: boolean
  1856. }
  1857. function recordType(node: Node, declaredTypes: Record<string, string[]>) {
  1858. if (node.type === 'TSInterfaceDeclaration') {
  1859. declaredTypes[node.id.name] = [`Object`]
  1860. } else if (node.type === 'TSTypeAliasDeclaration') {
  1861. declaredTypes[node.id.name] = inferRuntimeType(
  1862. node.typeAnnotation,
  1863. declaredTypes
  1864. )
  1865. } else if (node.type === 'ExportNamedDeclaration' && node.declaration) {
  1866. recordType(node.declaration, declaredTypes)
  1867. }
  1868. }
  1869. function extractRuntimeProps(
  1870. node: TSTypeLiteral | TSInterfaceBody,
  1871. props: Record<string, PropTypeData>,
  1872. declaredTypes: Record<string, string[]>,
  1873. isProd: boolean
  1874. ) {
  1875. const members = node.type === 'TSTypeLiteral' ? node.members : node.body
  1876. for (const m of members) {
  1877. if (
  1878. (m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature') &&
  1879. m.key.type === 'Identifier'
  1880. ) {
  1881. let type
  1882. if (m.type === 'TSMethodSignature') {
  1883. type = ['Function']
  1884. } else if (m.typeAnnotation) {
  1885. type = inferRuntimeType(m.typeAnnotation.typeAnnotation, declaredTypes)
  1886. }
  1887. props[m.key.name] = {
  1888. key: m.key.name,
  1889. required: !m.optional,
  1890. type: type || [`null`]
  1891. }
  1892. }
  1893. }
  1894. }
  1895. function inferRuntimeType(
  1896. node: TSType,
  1897. declaredTypes: Record<string, string[]>
  1898. ): string[] {
  1899. switch (node.type) {
  1900. case 'TSStringKeyword':
  1901. return ['String']
  1902. case 'TSNumberKeyword':
  1903. return ['Number']
  1904. case 'TSBooleanKeyword':
  1905. return ['Boolean']
  1906. case 'TSObjectKeyword':
  1907. return ['Object']
  1908. case 'TSTypeLiteral': {
  1909. // TODO (nice to have) generate runtime property validation
  1910. const types = new Set<string>()
  1911. for (const m of node.members) {
  1912. switch (m.type) {
  1913. case 'TSCallSignatureDeclaration':
  1914. case 'TSConstructSignatureDeclaration':
  1915. types.add('Function')
  1916. break
  1917. default:
  1918. types.add('Object')
  1919. }
  1920. }
  1921. return Array.from(types)
  1922. }
  1923. case 'TSFunctionType':
  1924. return ['Function']
  1925. case 'TSArrayType':
  1926. case 'TSTupleType':
  1927. // TODO (nice to have) generate runtime element type/length checks
  1928. return ['Array']
  1929. case 'TSLiteralType':
  1930. switch (node.literal.type) {
  1931. case 'StringLiteral':
  1932. return ['String']
  1933. case 'BooleanLiteral':
  1934. return ['Boolean']
  1935. case 'NumericLiteral':
  1936. case 'BigIntLiteral':
  1937. return ['Number']
  1938. default:
  1939. return [`null`]
  1940. }
  1941. case 'TSTypeReference':
  1942. if (node.typeName.type === 'Identifier') {
  1943. if (declaredTypes[node.typeName.name]) {
  1944. return declaredTypes[node.typeName.name]
  1945. }
  1946. switch (node.typeName.name) {
  1947. case 'Array':
  1948. case 'Function':
  1949. case 'Object':
  1950. case 'Set':
  1951. case 'Map':
  1952. case 'WeakSet':
  1953. case 'WeakMap':
  1954. case 'Date':
  1955. case 'Promise':
  1956. return [node.typeName.name]
  1957. // TS built-in utility types
  1958. // https://www.typescriptlang.org/docs/handbook/utility-types.html
  1959. case 'Partial':
  1960. case 'Required':
  1961. case 'Readonly':
  1962. case 'Record':
  1963. case 'Pick':
  1964. case 'Omit':
  1965. case 'InstanceType':
  1966. return ['Object']
  1967. case 'Uppercase':
  1968. case 'Lowercase':
  1969. case 'Capitalize':
  1970. case 'Uncapitalize':
  1971. return ['String']
  1972. case 'Parameters':
  1973. case 'ConstructorParameters':
  1974. return ['Array']
  1975. case 'NonNullable':
  1976. if (node.typeParameters && node.typeParameters.params[0]) {
  1977. return inferRuntimeType(
  1978. node.typeParameters.params[0],
  1979. declaredTypes
  1980. ).filter(t => t !== 'null')
  1981. }
  1982. case 'Extract':
  1983. if (node.typeParameters && node.typeParameters.params[1]) {
  1984. return inferRuntimeType(
  1985. node.typeParameters.params[1],
  1986. declaredTypes
  1987. )
  1988. }
  1989. case 'Exclude':
  1990. case 'OmitThisParameter':
  1991. if (node.typeParameters && node.typeParameters.params[0]) {
  1992. return inferRuntimeType(
  1993. node.typeParameters.params[0],
  1994. declaredTypes
  1995. )
  1996. }
  1997. // cannot infer, fallback to null: ThisParameterType
  1998. }
  1999. }
  2000. return [`null`]
  2001. case 'TSParenthesizedType':
  2002. return inferRuntimeType(node.typeAnnotation, declaredTypes)
  2003. case 'TSUnionType':
  2004. case 'TSIntersectionType':
  2005. return [
  2006. ...new Set(
  2007. [].concat(
  2008. ...(node.types.map(t => inferRuntimeType(t, declaredTypes)) as any)
  2009. )
  2010. )
  2011. ]
  2012. case 'TSSymbolKeyword':
  2013. return ['Symbol']
  2014. default:
  2015. return [`null`] // no runtime check
  2016. }
  2017. }
  2018. function toRuntimeTypeString(types: string[]) {
  2019. return types.length > 1 ? `[${types.join(', ')}]` : types[0]
  2020. }
  2021. function extractRuntimeEmits(
  2022. node: TSFunctionType | TSTypeLiteral | TSInterfaceBody,
  2023. emits: Set<string>
  2024. ) {
  2025. if (node.type === 'TSTypeLiteral' || node.type === 'TSInterfaceBody') {
  2026. const members = node.type === 'TSTypeLiteral' ? node.members : node.body
  2027. for (let t of members) {
  2028. if (t.type === 'TSCallSignatureDeclaration') {
  2029. extractEventNames(t.parameters[0], emits)
  2030. }
  2031. }
  2032. return
  2033. } else {
  2034. extractEventNames(node.parameters[0], emits)
  2035. }
  2036. }
  2037. function extractEventNames(
  2038. eventName: Identifier | RestElement,
  2039. emits: Set<string>
  2040. ) {
  2041. if (
  2042. eventName.type === 'Identifier' &&
  2043. eventName.typeAnnotation &&
  2044. eventName.typeAnnotation.type === 'TSTypeAnnotation'
  2045. ) {
  2046. const typeNode = eventName.typeAnnotation.typeAnnotation
  2047. if (typeNode.type === 'TSLiteralType') {
  2048. if (
  2049. typeNode.literal.type !== 'UnaryExpression' &&
  2050. typeNode.literal.type !== 'TemplateLiteral'
  2051. ) {
  2052. emits.add(String(typeNode.literal.value))
  2053. }
  2054. } else if (typeNode.type === 'TSUnionType') {
  2055. for (const t of typeNode.types) {
  2056. if (
  2057. t.type === 'TSLiteralType' &&
  2058. t.literal.type !== 'UnaryExpression' &&
  2059. t.literal.type !== 'TemplateLiteral'
  2060. ) {
  2061. emits.add(String(t.literal.value))
  2062. }
  2063. }
  2064. }
  2065. }
  2066. }
  2067. function genRuntimeEmits(emits: Set<string>) {
  2068. return emits.size
  2069. ? `\n emits: [${Array.from(emits)
  2070. .map(p => JSON.stringify(p))
  2071. .join(', ')}],`
  2072. : ``
  2073. }
  2074. function isCallOf(
  2075. node: Node | null | undefined,
  2076. test: string | ((id: string) => boolean) | null | undefined
  2077. ): node is CallExpression {
  2078. return !!(
  2079. node &&
  2080. test &&
  2081. node.type === 'CallExpression' &&
  2082. node.callee.type === 'Identifier' &&
  2083. (typeof test === 'string'
  2084. ? node.callee.name === test
  2085. : test(node.callee.name))
  2086. )
  2087. }
  2088. function canNeverBeRef(node: Node, userReactiveImport?: string): boolean {
  2089. if (isCallOf(node, userReactiveImport)) {
  2090. return true
  2091. }
  2092. switch (node.type) {
  2093. case 'UnaryExpression':
  2094. case 'BinaryExpression':
  2095. case 'ArrayExpression':
  2096. case 'ObjectExpression':
  2097. case 'FunctionExpression':
  2098. case 'ArrowFunctionExpression':
  2099. case 'UpdateExpression':
  2100. case 'ClassExpression':
  2101. case 'TaggedTemplateExpression':
  2102. return true
  2103. case 'SequenceExpression':
  2104. return canNeverBeRef(
  2105. node.expressions[node.expressions.length - 1],
  2106. userReactiveImport
  2107. )
  2108. default:
  2109. if (isLiteralNode(node)) {
  2110. return true
  2111. }
  2112. return false
  2113. }
  2114. }
  2115. function isStaticNode(node: Node): boolean {
  2116. switch (node.type) {
  2117. case 'UnaryExpression': // void 0, !true
  2118. return isStaticNode(node.argument)
  2119. case 'LogicalExpression': // 1 > 2
  2120. case 'BinaryExpression': // 1 + 2
  2121. return isStaticNode(node.left) && isStaticNode(node.right)
  2122. case 'ConditionalExpression': {
  2123. // 1 ? 2 : 3
  2124. return (
  2125. isStaticNode(node.test) &&
  2126. isStaticNode(node.consequent) &&
  2127. isStaticNode(node.alternate)
  2128. )
  2129. }
  2130. case 'SequenceExpression': // (1, 2)
  2131. case 'TemplateLiteral': // `foo${1}`
  2132. return node.expressions.every(expr => isStaticNode(expr))
  2133. case 'ParenthesizedExpression': // (1)
  2134. case 'TSNonNullExpression': // 1!
  2135. case 'TSAsExpression': // 1 as number
  2136. case 'TSTypeAssertion': // (<number>2)
  2137. return isStaticNode(node.expression)
  2138. default:
  2139. if (isLiteralNode(node)) {
  2140. return true
  2141. }
  2142. return false
  2143. }
  2144. }
  2145. function isLiteralNode(node: Node) {
  2146. return node.type.endsWith('Literal')
  2147. }
  2148. /**
  2149. * Analyze bindings in normal `<script>`
  2150. * Note that `compileScriptSetup` already analyzes bindings as part of its
  2151. * compilation process so this should only be used on single `<script>` SFCs.
  2152. */
  2153. function analyzeScriptBindings(ast: Statement[]): BindingMetadata {
  2154. for (const node of ast) {
  2155. if (
  2156. node.type === 'ExportDefaultDeclaration' &&
  2157. node.declaration.type === 'ObjectExpression'
  2158. ) {
  2159. return analyzeBindingsFromOptions(node.declaration)
  2160. }
  2161. }
  2162. return {}
  2163. }
  2164. function analyzeBindingsFromOptions(node: ObjectExpression): BindingMetadata {
  2165. const bindings: BindingMetadata = {}
  2166. // #3270, #3275
  2167. // mark non-script-setup so we don't resolve components/directives from these
  2168. Object.defineProperty(bindings, '__isScriptSetup', {
  2169. enumerable: false,
  2170. value: false
  2171. })
  2172. for (const property of node.properties) {
  2173. if (
  2174. property.type === 'ObjectProperty' &&
  2175. !property.computed &&
  2176. property.key.type === 'Identifier'
  2177. ) {
  2178. // props
  2179. if (property.key.name === 'props') {
  2180. // props: ['foo']
  2181. // props: { foo: ... }
  2182. for (const key of getObjectOrArrayExpressionKeys(property.value)) {
  2183. bindings[key] = BindingTypes.PROPS
  2184. }
  2185. }
  2186. // inject
  2187. else if (property.key.name === 'inject') {
  2188. // inject: ['foo']
  2189. // inject: { foo: {} }
  2190. for (const key of getObjectOrArrayExpressionKeys(property.value)) {
  2191. bindings[key] = BindingTypes.OPTIONS
  2192. }
  2193. }
  2194. // computed & methods
  2195. else if (
  2196. property.value.type === 'ObjectExpression' &&
  2197. (property.key.name === 'computed' || property.key.name === 'methods')
  2198. ) {
  2199. // methods: { foo() {} }
  2200. // computed: { foo() {} }
  2201. for (const key of getObjectExpressionKeys(property.value)) {
  2202. bindings[key] = BindingTypes.OPTIONS
  2203. }
  2204. }
  2205. }
  2206. // setup & data
  2207. else if (
  2208. property.type === 'ObjectMethod' &&
  2209. property.key.type === 'Identifier' &&
  2210. (property.key.name === 'setup' || property.key.name === 'data')
  2211. ) {
  2212. for (const bodyItem of property.body.body) {
  2213. // setup() {
  2214. // return {
  2215. // foo: null
  2216. // }
  2217. // }
  2218. if (
  2219. bodyItem.type === 'ReturnStatement' &&
  2220. bodyItem.argument &&
  2221. bodyItem.argument.type === 'ObjectExpression'
  2222. ) {
  2223. for (const key of getObjectExpressionKeys(bodyItem.argument)) {
  2224. bindings[key] =
  2225. property.key.name === 'setup'
  2226. ? BindingTypes.SETUP_MAYBE_REF
  2227. : BindingTypes.DATA
  2228. }
  2229. }
  2230. }
  2231. }
  2232. }
  2233. return bindings
  2234. }
  2235. function getObjectExpressionKeys(node: ObjectExpression): string[] {
  2236. const keys = []
  2237. for (const prop of node.properties) {
  2238. if (prop.type === 'SpreadElement') continue
  2239. const key = resolveObjectKey(prop.key, prop.computed)
  2240. if (key) keys.push(String(key))
  2241. }
  2242. return keys
  2243. }
  2244. function getArrayExpressionKeys(node: ArrayExpression): string[] {
  2245. const keys = []
  2246. for (const element of node.elements) {
  2247. if (element && element.type === 'StringLiteral') {
  2248. keys.push(element.value)
  2249. }
  2250. }
  2251. return keys
  2252. }
  2253. function getObjectOrArrayExpressionKeys(value: Node): string[] {
  2254. if (value.type === 'ArrayExpression') {
  2255. return getArrayExpressionKeys(value)
  2256. }
  2257. if (value.type === 'ObjectExpression') {
  2258. return getObjectExpressionKeys(value)
  2259. }
  2260. return []
  2261. }
  2262. const templateUsageCheckCache = createCache<string>()
  2263. function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
  2264. const { content, ast } = sfc.template!
  2265. const cached = templateUsageCheckCache.get(content)
  2266. if (cached) {
  2267. return cached
  2268. }
  2269. let code = ''
  2270. transform(createRoot([ast]), {
  2271. nodeTransforms: [
  2272. node => {
  2273. if (node.type === NodeTypes.ELEMENT) {
  2274. if (
  2275. !parserOptions.isNativeTag!(node.tag) &&
  2276. !parserOptions.isBuiltInComponent!(node.tag)
  2277. ) {
  2278. code += `,${camelize(node.tag)},${capitalize(camelize(node.tag))}`
  2279. }
  2280. for (let i = 0; i < node.props.length; i++) {
  2281. const prop = node.props[i]
  2282. if (prop.type === NodeTypes.DIRECTIVE) {
  2283. if (!isBuiltInDir(prop.name)) {
  2284. code += `,v${capitalize(camelize(prop.name))}`
  2285. }
  2286. if (prop.exp) {
  2287. code += `,${processExp(
  2288. (prop.exp as SimpleExpressionNode).content,
  2289. prop.name
  2290. )}`
  2291. }
  2292. }
  2293. }
  2294. } else if (node.type === NodeTypes.INTERPOLATION) {
  2295. code += `,${processExp(
  2296. (node.content as SimpleExpressionNode).content
  2297. )}`
  2298. }
  2299. }
  2300. ]
  2301. })
  2302. code += ';'
  2303. templateUsageCheckCache.set(content, code)
  2304. return code
  2305. }
  2306. const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
  2307. function processExp(exp: string, dir?: string): string {
  2308. if (/ as\s+\w|<.*>|:/.test(exp)) {
  2309. if (dir === 'slot') {
  2310. exp = `(${exp})=>{}`
  2311. } else if (dir === 'on') {
  2312. exp = `()=>{return ${exp}}`
  2313. } else if (dir === 'for') {
  2314. const inMatch = exp.match(forAliasRE)
  2315. if (inMatch) {
  2316. const [, LHS, RHS] = inMatch
  2317. return processExp(`(${LHS})=>{}`) + processExp(RHS)
  2318. }
  2319. }
  2320. let ret = ''
  2321. // has potential type cast or generic arguments that uses types
  2322. const ast = parseExpression(exp, { plugins: ['typescript'] })
  2323. walkIdentifiers(ast, node => {
  2324. ret += `,` + node.name
  2325. })
  2326. return ret
  2327. }
  2328. return stripStrings(exp)
  2329. }
  2330. function stripStrings(exp: string) {
  2331. return exp
  2332. .replace(/'[^']*'|"[^"]*"/g, '')
  2333. .replace(/`[^`]+`/g, stripTemplateString)
  2334. }
  2335. function stripTemplateString(str: string): string {
  2336. const interpMatch = str.match(/\${[^}]+}/g)
  2337. if (interpMatch) {
  2338. return interpMatch.map(m => m.slice(2, -1)).join(',')
  2339. }
  2340. return ''
  2341. }
  2342. function isImportUsed(local: string, sfc: SFCDescriptor): boolean {
  2343. return new RegExp(
  2344. // #4274 escape $ since it's a special char in regex
  2345. // (and is the only regex special char that is valid in identifiers)
  2346. `[^\\w$_]${local.replace(/\$/g, '\\$')}[^\\w$_]`
  2347. ).test(resolveTemplateUsageCheckString(sfc))
  2348. }
  2349. /**
  2350. * Note: this comparison assumes the prev/next script are already identical,
  2351. * and only checks the special case where <script setup lang="ts"> unused import
  2352. * pruning result changes due to template changes.
  2353. */
  2354. export function hmrShouldReload(
  2355. prevImports: Record<string, ImportBinding>,
  2356. next: SFCDescriptor
  2357. ): boolean {
  2358. if (
  2359. !next.scriptSetup ||
  2360. (next.scriptSetup.lang !== 'ts' && next.scriptSetup.lang !== 'tsx')
  2361. ) {
  2362. return false
  2363. }
  2364. // for each previous import, check if its used status remain the same based on
  2365. // the next descriptor's template
  2366. for (const key in prevImports) {
  2367. // if an import was previous unused, but now is used, we need to force
  2368. // reload so that the script now includes that import.
  2369. if (!prevImports[key].isUsedInTemplate && isImportUsed(key, next)) {
  2370. return true
  2371. }
  2372. }
  2373. return false
  2374. }
  2375. export function resolveObjectKey(node: Node, computed: boolean) {
  2376. switch (node.type) {
  2377. case 'StringLiteral':
  2378. case 'NumericLiteral':
  2379. return node.value
  2380. case 'Identifier':
  2381. if (!computed) return node.name
  2382. }
  2383. return undefined
  2384. }