context.ts 5.7 KB

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