context.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import type { CallExpression, Node, ObjectPattern, Program } from '@babel/types'
  2. import type { SFCDescriptor } from '../parse'
  3. import { generateCodeFrame, isArray } from '@vue/shared'
  4. import { type ParserPlugin, parse as babelParse } from '@babel/parser'
  5. import type { ImportBinding, SFCScriptCompileOptions } from '../compileScript'
  6. import type { PropsDestructureBindings } from './defineProps'
  7. import type { ModelDecl } from './defineModel'
  8. import type { BindingMetadata } from '../../../compiler-core/src'
  9. import MagicString from 'magic-string'
  10. import type { TypeScope } from './resolveType'
  11. export class ScriptCompileContext {
  12. isJS: boolean
  13. isTS: boolean
  14. isCE = false
  15. scriptAst: Program | null
  16. scriptSetupAst: Program | null
  17. source = this.descriptor.source
  18. filename = this.descriptor.filename
  19. s = new MagicString(this.source)
  20. startOffset = this.descriptor.scriptSetup?.loc.start.offset
  21. endOffset = this.descriptor.scriptSetup?.loc.end.offset
  22. // import / type analysis
  23. scope?: TypeScope
  24. globalScopes?: TypeScope[]
  25. userImports: Record<string, ImportBinding> = Object.create(null)
  26. // macros presence check
  27. hasDefinePropsCall = false
  28. hasDefineEmitCall = false
  29. hasDefineExposeCall = false
  30. hasDefaultExportName = false
  31. hasDefaultExportRender = false
  32. hasDefineOptionsCall = false
  33. hasDefineSlotsCall = false
  34. hasDefineModelCall = false
  35. // defineProps
  36. propsCall: CallExpression | undefined
  37. propsDecl: Node | undefined
  38. propsRuntimeDecl: Node | undefined
  39. propsTypeDecl: Node | undefined
  40. propsDestructureDecl: ObjectPattern | undefined
  41. propsDestructuredBindings: PropsDestructureBindings = Object.create(null)
  42. propsDestructureRestId: string | undefined
  43. propsRuntimeDefaults: Node | undefined
  44. // defineEmits
  45. emitsRuntimeDecl: Node | undefined
  46. emitsTypeDecl: Node | undefined
  47. emitDecl: Node | undefined
  48. // defineModel
  49. modelDecls: Record<string, ModelDecl> = Object.create(null)
  50. // defineOptions
  51. optionsRuntimeDecl: Node | undefined
  52. // codegen
  53. bindingMetadata: BindingMetadata = {}
  54. helperImports: Set<string> = new Set()
  55. helper(key: string): string {
  56. this.helperImports.add(key)
  57. return `_${key}`
  58. }
  59. /**
  60. * to be exposed on compiled script block for HMR cache busting
  61. */
  62. deps?: Set<string>
  63. /**
  64. * cache for resolved fs
  65. */
  66. fs?: NonNullable<SFCScriptCompileOptions['fs']>
  67. constructor(
  68. public descriptor: SFCDescriptor,
  69. public options: Partial<SFCScriptCompileOptions>,
  70. ) {
  71. const { script, scriptSetup } = descriptor
  72. const scriptLang = script && script.lang
  73. const scriptSetupLang = scriptSetup && scriptSetup.lang
  74. this.isJS =
  75. scriptLang === 'js' ||
  76. scriptLang === 'jsx' ||
  77. scriptSetupLang === 'js' ||
  78. scriptSetupLang === 'jsx'
  79. this.isTS =
  80. scriptLang === 'ts' ||
  81. scriptLang === 'tsx' ||
  82. scriptSetupLang === 'ts' ||
  83. scriptSetupLang === 'tsx'
  84. const customElement = options.customElement
  85. const filename = this.descriptor.filename
  86. if (customElement) {
  87. this.isCE =
  88. typeof customElement === 'boolean'
  89. ? customElement
  90. : customElement(filename)
  91. }
  92. // resolve parser plugins
  93. const plugins: ParserPlugin[] = resolveParserPlugins(
  94. (scriptLang || scriptSetupLang)!,
  95. options.babelParserPlugins,
  96. )
  97. function parse(input: string, offset: number): Program {
  98. try {
  99. return babelParse(input, {
  100. plugins,
  101. sourceType: 'module',
  102. }).program
  103. } catch (e: any) {
  104. e.message = `[vue/compiler-sfc] ${e.message}\n\n${
  105. descriptor.filename
  106. }\n${generateCodeFrame(
  107. descriptor.source,
  108. e.pos + offset,
  109. e.pos + offset + 1,
  110. )}`
  111. throw e
  112. }
  113. }
  114. this.scriptAst =
  115. descriptor.script &&
  116. parse(descriptor.script.content, descriptor.script.loc.start.offset)
  117. this.scriptSetupAst =
  118. descriptor.scriptSetup &&
  119. parse(descriptor.scriptSetup!.content, this.startOffset!)
  120. }
  121. getString(node: Node, scriptSetup = true): string {
  122. const block = scriptSetup
  123. ? this.descriptor.scriptSetup!
  124. : this.descriptor.script!
  125. return block.content.slice(node.start!, node.end!)
  126. }
  127. error(msg: string, node: Node, scope?: TypeScope): never {
  128. const offset = scope ? scope.offset : this.startOffset!
  129. throw new Error(
  130. `[@vue/compiler-sfc] ${msg}\n\n${
  131. (scope || this.descriptor).filename
  132. }\n${generateCodeFrame(
  133. (scope || this.descriptor).source,
  134. node.start! + offset,
  135. node.end! + offset,
  136. )}`,
  137. )
  138. }
  139. }
  140. export function resolveParserPlugins(
  141. lang: string,
  142. userPlugins?: ParserPlugin[],
  143. dts = false,
  144. ) {
  145. const plugins: ParserPlugin[] = []
  146. if (
  147. !userPlugins ||
  148. !userPlugins.some(
  149. p =>
  150. p === 'importAssertions' ||
  151. p === 'importAttributes' ||
  152. (isArray(p) && p[0] === 'importAttributes'),
  153. )
  154. ) {
  155. plugins.push('importAttributes')
  156. }
  157. if (lang === 'jsx' || lang === 'tsx' || lang === 'mtsx') {
  158. plugins.push('jsx')
  159. } else if (userPlugins) {
  160. // If don't match the case of adding jsx
  161. // should remove the jsx from user options
  162. userPlugins = userPlugins.filter(p => p !== 'jsx')
  163. }
  164. if (lang === 'ts' || lang === 'mts' || lang === 'tsx' || lang === 'mtsx') {
  165. plugins.push(['typescript', { dts }], 'explicitResourceManagement')
  166. if (!userPlugins || !userPlugins.includes('decorators')) {
  167. plugins.push('decorators-legacy')
  168. }
  169. }
  170. if (userPlugins) {
  171. plugins.push(...userPlugins)
  172. }
  173. return plugins
  174. }