compileScript.ts 56 KB

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