Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

126 lines
2.8 KiB

4 years ago
import { SourceMapGenerator } from 'source-map'
import {
RawSourceMap,
VueTemplateCompiler,
VueTemplateCompilerParseOptions
} from './types'
const hash = require('hash-sum')
const cache = new (require('lru-cache'))(100)
const splitRE = /\r?\n/g
const emptyRE = /^(?:\/\/)?\s*$/
export interface ParseOptions {
source: string
filename?: string
compiler: VueTemplateCompiler
compilerParseOptions?: VueTemplateCompilerParseOptions
sourceRoot?: string
needMap?: boolean
}
export interface SFCCustomBlock {
type: string
content: string
attrs: { [key: string]: string | true }
start: number
end: number
map?: RawSourceMap
}
export interface SFCBlock extends SFCCustomBlock {
lang?: string
src?: string
scoped?: boolean
module?: string | boolean
}
export interface SFCDescriptor {
template: SFCBlock | null
script: SFCBlock | null
styles: SFCBlock[]
customBlocks: SFCCustomBlock[]
}
export function parse(options: ParseOptions): SFCDescriptor {
const {
source,
filename = '',
compiler,
compilerParseOptions = { pad: 'line' } as VueTemplateCompilerParseOptions,
sourceRoot = '',
needMap = true
} = options
const cacheKey = hash(
filename + source + JSON.stringify(compilerParseOptions)
)
let output: SFCDescriptor = cache.get(cacheKey)
if (output) return output
output = compiler.parseComponent(source, compilerParseOptions)
if (needMap) {
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'
): 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())
}