+import { RawSourceMap, SourceMapConsumer } from 'source-map-js'
import {
compileTemplate,
SFCTemplateCompileOptions
const template = parse(
`
<template>
- <div><p>{{ render }}</p></div>
+ <div><p>{{ foobar }}</p></div>
</template>
`,
{ filename: 'example.vue', sourceMap: true }
- ).descriptor.template as SFCTemplateBlock
+ ).descriptor.template!
- const result = compile({
+ const { code, map } = compile({
filename: 'example.vue',
source: template.content
})
- expect(result.map).toMatchSnapshot()
+ expect(map!.sources).toEqual([`example.vue`])
+ expect(map!.sourcesContent).toEqual([template.content])
+
+ const consumer = new SourceMapConsumer(map as RawSourceMap)
+ expect(
+ consumer.originalPositionFor(getPositionInCode(code, 'foobar'))
+ ).toMatchObject(getPositionInCode(template.content, `foobar`))
+})
+
+test('should work w/ AST from descriptor', () => {
+ const source = `
+ <template>
+ <div><p>{{ foobar }}</p></div>
+ </template>
+ `
+ const template = parse(source, {
+ filename: 'example.vue',
+ sourceMap: true
+ }).descriptor.template!
+
+ expect(template.ast.source).toBe(source)
+
+ const { code, map } = compile({
+ filename: 'example.vue',
+ source: template.content,
+ ast: template.ast
+ })
+
+ expect(map!.sources).toEqual([`example.vue`])
+ // when reusing AST from SFC parse for template compile,
+ // the source corresponds to the entire SFC
+ expect(map!.sourcesContent).toEqual([source])
+
+ const consumer = new SourceMapConsumer(map as RawSourceMap)
+ expect(
+ consumer.originalPositionFor(getPositionInCode(code, 'foobar'))
+ ).toMatchObject(getPositionInCode(source, `foobar`))
})
test('template errors', () => {
expect(result.code).toMatchSnapshot()
})
+
+interface Pos {
+ line: number
+ column: number
+ name?: string
+}
+
+function getPositionInCode(
+ code: string,
+ token: string,
+ expectName: string | boolean = false
+): Pos {
+ const generatedOffset = code.indexOf(token)
+ let line = 1
+ let lastNewLinePos = -1
+ for (let i = 0; i < generatedOffset; i++) {
+ if (code.charCodeAt(i) === 10 /* newline char code */) {
+ line++
+ lastNewLinePos = i
+ }
+ }
+ const res: Pos = {
+ line,
+ column:
+ lastNewLinePos === -1
+ ? generatedOffset
+ : generatedOffset - lastNewLinePos - 1
+ }
+ if (expectName) {
+ res.name = typeof expectName === 'string' ? expectName : token
+ }
+ return res
+}
import { genCssVarsFromList } from './style/cssVars'
export interface TemplateCompiler {
- compile(template: string, options: CompilerOptions): CodegenResult
+ compile(source: string | RootNode, options: CompilerOptions): CodegenResult
parse(template: string, options: ParserOptions): RootNode
}
export interface SFCTemplateCompileOptions {
source: string
+ ast?: RootNode
filename: string
id: string
scoped?: boolean
slotted,
inMap,
source,
+ ast: inAST,
ssr = false,
ssrCssVars,
isProd = false,
const shortId = id.replace(/^data-v-/, '')
const longId = `data-v-${shortId}`
- let { code, ast, preamble, map } = compiler.compile(source, {
+ let { code, ast, preamble, map } = compiler.compile(inAST || source, {
mode: 'module',
prefixIdentifiers: true,
hoistStatic: true,
let msg = w.message
if (w.loc) {
msg += `\n${generateCodeFrame(
- source,
+ inAST?.source || source,
w.loc.start.offset,
w.loc.end.offset
)}`
ElementNode,
SourceLocation,
CompilerError,
- BindingMetadata
+ BindingMetadata,
+ RootNode,
+ createRoot
} from '@vue/compiler-core'
import * as CompilerDOM from '@vue/compiler-dom'
import { RawSourceMap, SourceMapGenerator } from 'source-map-js'
export interface SFCTemplateBlock extends SFCBlock {
type: 'template'
- ast: ElementNode
+ ast: RootNode
}
export interface SFCScriptBlock extends SFCBlock {
source,
false
) as SFCTemplateBlock)
- templateBlock.ast = node
+ templateBlock.ast = createRoot(node.children, source)
// warn against 2.x <template functional>
if (templateBlock.attrs.functional) {
)
}
}
- genMap(descriptor.template)
+ // no need to genMap for template as its AST already accounts for the
+ // position in the SFC
genMap(descriptor.script)
descriptor.styles.forEach(genMap)
descriptor.customBlocks.forEach(genMap)