context.ts 5.9 KB

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