compileScript.ts 68 KB

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