compileScript.ts 65 KB

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