feat(sfc): accept inMap in compileTemplate()

This commit is contained in:
Evan You 2019-12-19 16:25:05 -05:00
parent 02c6d5c4e3
commit 3a3a24d621
3 changed files with 94 additions and 6 deletions

View File

@ -145,7 +145,7 @@ export const errorMessages: { [code: number]: string } = {
// Vue-specific parse errors // Vue-specific parse errors
[ErrorCodes.X_INVALID_END_TAG]: 'Invalid end tag.', [ErrorCodes.X_INVALID_END_TAG]: 'Invalid end tag.',
[ErrorCodes.X_MISSING_END_TAG]: 'End tag was not found.', [ErrorCodes.X_MISSING_END_TAG]: 'Element is missing end tag.',
[ErrorCodes.X_MISSING_INTERPOLATION_END]: [ErrorCodes.X_MISSING_INTERPOLATION_END]:
'Interpolation end sign was not found.', 'Interpolation end sign was not found.',
[ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END]: [ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END]:

View File

@ -372,7 +372,7 @@ function parseElement(
if (startsWithEndTagOpen(context.source, element.tag)) { if (startsWithEndTagOpen(context.source, element.tag)) {
parseTag(context, TagType.End, parent) parseTag(context, TagType.End, parent)
} else { } else {
emitError(context, ErrorCodes.X_MISSING_END_TAG) emitError(context, ErrorCodes.X_MISSING_END_TAG, 0, element.loc.start)
if (context.source.length === 0 && element.tag.toLowerCase() === 'script') { if (context.source.length === 0 && element.tag.toLowerCase() === 'script') {
const first = children[0] const first = children[0]
if (first && startsWith(first.loc.source, '<!--')) { if (first && startsWith(first.loc.source, '<!--')) {
@ -963,9 +963,9 @@ function getNewPosition(
function emitError( function emitError(
context: ParserContext, context: ParserContext,
code: ErrorCodes, code: ErrorCodes,
offset?: number offset?: number,
loc: Position = getCursor(context)
): void { ): void {
const loc = getCursor(context)
if (offset) { if (offset) {
loc.offset += offset loc.offset += offset
loc.column += offset loc.column += offset

View File

@ -4,7 +4,7 @@ import {
CompilerError, CompilerError,
NodeTransform NodeTransform
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { RawSourceMap } from 'source-map' import { SourceMapConsumer, SourceMapGenerator, RawSourceMap } from 'source-map'
import { import {
transformAssetUrl, transformAssetUrl,
AssetURLOptions, AssetURLOptions,
@ -29,6 +29,7 @@ export interface SFCTemplateCompileResults {
export interface SFCTemplateCompileOptions { export interface SFCTemplateCompileOptions {
source: string source: string
filename: string filename: string
inMap?: RawSourceMap
compiler?: TemplateCompiler compiler?: TemplateCompiler
compilerOptions?: CompilerOptions compilerOptions?: CompilerOptions
preprocessLang?: string preprocessLang?: string
@ -100,6 +101,7 @@ export function compileTemplate(
function doCompileTemplate({ function doCompileTemplate({
filename, filename,
inMap,
source, source,
compiler = require('@vue/compiler-dom'), compiler = require('@vue/compiler-dom'),
compilerOptions = {}, compilerOptions = {},
@ -117,7 +119,7 @@ function doCompileTemplate({
nodeTransforms = [transformAssetUrl, transformSrcset] nodeTransforms = [transformAssetUrl, transformSrcset]
} }
const { code, map } = compiler.compile(source, { let { code, map } = compiler.compile(source, {
mode: 'module', mode: 'module',
prefixIdentifiers: true, prefixIdentifiers: true,
hoistStatic: true, hoistStatic: true,
@ -128,5 +130,91 @@ function doCompileTemplate({
sourceMap: true, sourceMap: true,
onError: e => errors.push(e) onError: e => errors.push(e)
}) })
// inMap should be the map produced by ./parse.ts which is a simple line-only
// mapping. If it is present, we need to adjust the final map and errors to
// reflect the original line numbers.
if (inMap) {
if (map) {
map = mapLines(inMap, map)
}
if (errors.length) {
patchErrors(errors, source, inMap)
}
}
return { code, source, errors, tips: [], map } return { code, source, errors, tips: [], map }
} }
function mapLines(oldMap: RawSourceMap, newMap: RawSourceMap): RawSourceMap {
if (!oldMap) return newMap
if (!newMap) return oldMap
const oldMapConsumer = new SourceMapConsumer(oldMap)
const newMapConsumer = new SourceMapConsumer(newMap)
const mergedMapGenerator = new SourceMapGenerator()
newMapConsumer.eachMapping(m => {
if (m.originalLine == null) {
return
}
const origPosInOldMap = oldMapConsumer.originalPositionFor({
line: m.originalLine,
column: m.originalColumn
})
if (origPosInOldMap.source == null) {
return
}
mergedMapGenerator.addMapping({
generated: {
line: m.generatedLine,
column: m.generatedColumn
},
original: {
line: origPosInOldMap.line, // map line
// use current column, since the oldMap produced by @vue/compiler-sfc
// does not
column: m.originalColumn
},
source: origPosInOldMap.source,
name: origPosInOldMap.name
})
})
// source-map's type definition is incomplete
const generator = mergedMapGenerator as any
;(oldMapConsumer as any).sources.forEach((sourceFile: string) => {
generator._sources.add(sourceFile)
const sourceContent = oldMapConsumer.sourceContentFor(sourceFile)
if (sourceContent != null) {
mergedMapGenerator.setSourceContent(sourceFile, sourceContent)
}
})
generator._sourceRoot = oldMap.sourceRoot
generator._file = oldMap.file
return generator.toJSON()
}
function patchErrors(
errors: CompilerError[],
source: string,
inMap: RawSourceMap
) {
const originalSource = inMap.sourcesContent![0]
const offset = originalSource.indexOf(source)
const lineOffset = originalSource.slice(0, offset).split(/\r?\n/).length - 1
errors.forEach(err => {
if (err.loc) {
err.loc.start.line += lineOffset
err.loc.start.offset += offset
if (err.loc.end !== err.loc.start) {
err.loc.end.line += lineOffset
err.loc.end.offset += offset
}
}
})
}