context.ts 4.6 KB

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