import { NodeTypes, ElementNode, SourceLocation, CompilerError, TextModes, BindingMetadata } from '@vue/compiler-core' import * as CompilerDOM from '@vue/compiler-dom' import { RawSourceMap, SourceMapGenerator } from 'source-map' import { TemplateCompiler } from './compileTemplate' import { Statement } from '@babel/types' import { parseCssVars } from './cssVars' import { createCache } from './cache' export interface SFCParseOptions { filename?: string sourceMap?: boolean sourceRoot?: string pad?: boolean | 'line' | 'space' ignoreEmpty?: boolean compiler?: TemplateCompiler } export interface SFCBlock { type: string content: string attrs: Record loc: SourceLocation map?: RawSourceMap lang?: string src?: string } export interface SFCTemplateBlock extends SFCBlock { type: 'template' ast: ElementNode } export interface SFCScriptBlock extends SFCBlock { type: 'script' setup?: string | boolean bindings?: BindingMetadata scriptAst?: Statement[] scriptSetupAst?: Statement[] ranges?: ScriptSetupTextRanges } /** * Text range data for IDE support */ export interface ScriptSetupTextRanges { scriptBindings: TextRange[] scriptSetupBindings: TextRange[] propsTypeArg?: TextRange propsRuntimeArg?: TextRange emitsTypeArg?: TextRange emitsRuntimeArg?: TextRange withDefaultsArg?: TextRange } export interface TextRange { start: number end: number } export interface SFCStyleBlock extends SFCBlock { type: 'style' scoped?: boolean module?: string | boolean } export interface SFCDescriptor { filename: string source: string template: SFCTemplateBlock | null script: SFCScriptBlock | null scriptSetup: SFCScriptBlock | null styles: SFCStyleBlock[] customBlocks: SFCBlock[] cssVars: string[] // whether the SFC uses :slotted() modifier. // this is used as a compiler optimization hint. slotted: boolean } export interface SFCParseResult { descriptor: SFCDescriptor errors: (CompilerError | SyntaxError)[] } const sourceToSFC = createCache() export function parse( source: string, { sourceMap = true, filename = 'anonymous.vue', sourceRoot = '', pad = false, ignoreEmpty = true, compiler = CompilerDOM }: SFCParseOptions = {} ): SFCParseResult { const sourceKey = source + sourceMap + filename + sourceRoot + pad + compiler.parse const cache = sourceToSFC.get(sourceKey) if (cache) { return cache } const descriptor: SFCDescriptor = { filename, source, template: null, script: null, scriptSetup: null, styles: [], customBlocks: [], cssVars: [], slotted: false } const errors: (CompilerError | SyntaxError)[] = [] const ast = compiler.parse(source, { // there are no components at SFC parsing level isNativeTag: () => true, // preserve all whitespaces isPreTag: () => true, getTextMode: ({ tag, props }, parent) => { // all top level elements except