compileScript.ts 53 KB

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