import { SourceMapGenerator } from 'source-map' import { RawSourceMap, TemplateCompiler } from './types' import { parseComponent, VueTemplateCompilerParseOptions, SFCDescriptor, DEFAULT_FILENAME } from './parseComponent' import hash from 'hash-sum' import LRU from 'lru-cache' import { hmrShouldReload } from './compileScript' import { parseCssVars } from './cssVars' const cache = new LRU(100) const splitRE = /\r?\n/g const emptyRE = /^(?:\/\/)?\s*$/ export interface SFCParseOptions { source: string filename?: string compiler?: TemplateCompiler compilerParseOptions?: VueTemplateCompilerParseOptions sourceRoot?: string sourceMap?: boolean /** * @deprecated use `sourceMap` instead. */ needMap?: boolean } export function parse(options: SFCParseOptions): SFCDescriptor { const { source, filename = DEFAULT_FILENAME, compiler, compilerParseOptions = { pad: false } as VueTemplateCompilerParseOptions, sourceRoot = '', needMap = true, sourceMap = needMap } = options const cacheKey = hash( filename + source + JSON.stringify(compilerParseOptions) ) let output = cache.get(cacheKey) if (output) { return output } if (compiler) { // user-provided compiler output = compiler.parseComponent(source, compilerParseOptions) } else { // use built-in compiler output = parseComponent(source, compilerParseOptions) } output.filename = filename // parse CSS vars output.cssVars = parseCssVars(output) output.shouldForceReload = prevImports => hmrShouldReload(prevImports, output!) if (sourceMap) { if (output.script && !output.script.src) { output.script.map = generateSourceMap( filename, source, output.script.content, sourceRoot, compilerParseOptions.pad ) } if (output.styles) { output.styles.forEach(style => { if (!style.src) { style.map = generateSourceMap( filename, source, style.content, sourceRoot, compilerParseOptions.pad ) } }) } } cache.set(cacheKey, output) return output } function generateSourceMap( filename: string, source: string, generated: string, sourceRoot: string, pad?: 'line' | 'space' | boolean ): RawSourceMap { const map = new SourceMapGenerator({ file: filename.replace(/\\/g, '/'), sourceRoot: sourceRoot.replace(/\\/g, '/') }) let offset = 0 if (!pad) { offset = source.split(generated).shift()!.split(splitRE).length - 1 } map.setSourceContent(filename, source) generated.split(splitRE).forEach((line, index) => { if (!emptyRE.test(line)) { map.addMapping({ source: filename, original: { line: index + 1 + offset, column: 0 }, generated: { line: index + 1, column: 0 } }) } }) return JSON.parse(map.toString()) }