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
126 lines
2.8 KiB
4 weeks 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())
|
||
|
}
|