compileScript.ts 79 KB

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