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.
125 lines
2.8 KiB
125 lines
2.8 KiB
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()) |
|
}
|
|
|