mirror of https://github.com/vuejs/core.git
wip: support reusing template ast from sfc descriptor
This commit is contained in:
parent
fc4f801070
commit
5fc695be26
|
@ -38,11 +38,11 @@ export const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compile(
|
export function compile(
|
||||||
template: string,
|
src: string | RootNode,
|
||||||
options: CompilerOptions = {}
|
options: CompilerOptions = {}
|
||||||
): CodegenResult {
|
): CodegenResult {
|
||||||
return baseCompile(
|
return baseCompile(
|
||||||
template,
|
src,
|
||||||
extend({}, parserOptions, options, {
|
extend({}, parserOptions, options, {
|
||||||
nodeTransforms: [
|
nodeTransforms: [
|
||||||
// ignore <script> and <tag>
|
// ignore <script> and <tag>
|
||||||
|
|
|
@ -58,24 +58,6 @@ export function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`source map 1`] = `
|
|
||||||
{
|
|
||||||
"mappings": ";;;wBACE,oBAA8B;IAAzB,oBAAmB,4BAAbA,WAAM",
|
|
||||||
"names": [
|
|
||||||
"render",
|
|
||||||
],
|
|
||||||
"sources": [
|
|
||||||
"example.vue",
|
|
||||||
],
|
|
||||||
"sourcesContent": [
|
|
||||||
"
|
|
||||||
<div><p>{{ render }}</p></div>
|
|
||||||
",
|
|
||||||
],
|
|
||||||
"version": 3,
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`template errors 1`] = `
|
exports[`template errors 1`] = `
|
||||||
[
|
[
|
||||||
[SyntaxError: Error parsing JavaScript expression: Unexpected token (1:3)],
|
[SyntaxError: Error parsing JavaScript expression: Unexpected token (1:3)],
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RawSourceMap, SourceMapConsumer } from 'source-map-js'
|
||||||
import {
|
import {
|
||||||
compileTemplate,
|
compileTemplate,
|
||||||
SFCTemplateCompileOptions
|
SFCTemplateCompileOptions
|
||||||
|
@ -107,18 +108,54 @@ test('source map', () => {
|
||||||
const template = parse(
|
const template = parse(
|
||||||
`
|
`
|
||||||
<template>
|
<template>
|
||||||
<div><p>{{ render }}</p></div>
|
<div><p>{{ foobar }}</p></div>
|
||||||
</template>
|
</template>
|
||||||
`,
|
`,
|
||||||
{ filename: 'example.vue', sourceMap: true }
|
{ filename: 'example.vue', sourceMap: true }
|
||||||
).descriptor.template as SFCTemplateBlock
|
).descriptor.template!
|
||||||
|
|
||||||
const result = compile({
|
const { code, map } = compile({
|
||||||
filename: 'example.vue',
|
filename: 'example.vue',
|
||||||
source: template.content
|
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', () => {
|
test('template errors', () => {
|
||||||
|
@ -199,3 +236,36 @@ test('dynamic v-on + static v-on should merged', () => {
|
||||||
|
|
||||||
expect(result.code).toMatchSnapshot()
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ import { warnOnce } from './warn'
|
||||||
import { genCssVarsFromList } from './style/cssVars'
|
import { genCssVarsFromList } from './style/cssVars'
|
||||||
|
|
||||||
export interface TemplateCompiler {
|
export interface TemplateCompiler {
|
||||||
compile(template: string, options: CompilerOptions): CodegenResult
|
compile(source: string | RootNode, options: CompilerOptions): CodegenResult
|
||||||
parse(template: string, options: ParserOptions): RootNode
|
parse(template: string, options: ParserOptions): RootNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ export interface SFCTemplateCompileResults {
|
||||||
|
|
||||||
export interface SFCTemplateCompileOptions {
|
export interface SFCTemplateCompileOptions {
|
||||||
source: string
|
source: string
|
||||||
|
ast?: RootNode
|
||||||
filename: string
|
filename: string
|
||||||
id: string
|
id: string
|
||||||
scoped?: boolean
|
scoped?: boolean
|
||||||
|
@ -164,6 +165,7 @@ function doCompileTemplate({
|
||||||
slotted,
|
slotted,
|
||||||
inMap,
|
inMap,
|
||||||
source,
|
source,
|
||||||
|
ast: inAST,
|
||||||
ssr = false,
|
ssr = false,
|
||||||
ssrCssVars,
|
ssrCssVars,
|
||||||
isProd = false,
|
isProd = false,
|
||||||
|
@ -199,7 +201,7 @@ function doCompileTemplate({
|
||||||
const shortId = id.replace(/^data-v-/, '')
|
const shortId = id.replace(/^data-v-/, '')
|
||||||
const longId = `data-v-${shortId}`
|
const longId = `data-v-${shortId}`
|
||||||
|
|
||||||
let { code, ast, preamble, map } = compiler.compile(source, {
|
let { code, ast, preamble, map } = compiler.compile(inAST || source, {
|
||||||
mode: 'module',
|
mode: 'module',
|
||||||
prefixIdentifiers: true,
|
prefixIdentifiers: true,
|
||||||
hoistStatic: true,
|
hoistStatic: true,
|
||||||
|
@ -235,7 +237,7 @@ function doCompileTemplate({
|
||||||
let msg = w.message
|
let msg = w.message
|
||||||
if (w.loc) {
|
if (w.loc) {
|
||||||
msg += `\n${generateCodeFrame(
|
msg += `\n${generateCodeFrame(
|
||||||
source,
|
inAST?.source || source,
|
||||||
w.loc.start.offset,
|
w.loc.start.offset,
|
||||||
w.loc.end.offset
|
w.loc.end.offset
|
||||||
)}`
|
)}`
|
||||||
|
|
|
@ -3,7 +3,9 @@ import {
|
||||||
ElementNode,
|
ElementNode,
|
||||||
SourceLocation,
|
SourceLocation,
|
||||||
CompilerError,
|
CompilerError,
|
||||||
BindingMetadata
|
BindingMetadata,
|
||||||
|
RootNode,
|
||||||
|
createRoot
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import * as CompilerDOM from '@vue/compiler-dom'
|
import * as CompilerDOM from '@vue/compiler-dom'
|
||||||
import { RawSourceMap, SourceMapGenerator } from 'source-map-js'
|
import { RawSourceMap, SourceMapGenerator } from 'source-map-js'
|
||||||
|
@ -36,7 +38,7 @@ export interface SFCBlock {
|
||||||
|
|
||||||
export interface SFCTemplateBlock extends SFCBlock {
|
export interface SFCTemplateBlock extends SFCBlock {
|
||||||
type: 'template'
|
type: 'template'
|
||||||
ast: ElementNode
|
ast: RootNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SFCScriptBlock extends SFCBlock {
|
export interface SFCScriptBlock extends SFCBlock {
|
||||||
|
@ -154,7 +156,7 @@ export function parse(
|
||||||
source,
|
source,
|
||||||
false
|
false
|
||||||
) as SFCTemplateBlock)
|
) as SFCTemplateBlock)
|
||||||
templateBlock.ast = node
|
templateBlock.ast = createRoot(node.children, source)
|
||||||
|
|
||||||
// warn against 2.x <template functional>
|
// warn against 2.x <template functional>
|
||||||
if (templateBlock.attrs.functional) {
|
if (templateBlock.attrs.functional) {
|
||||||
|
@ -243,7 +245,8 @@ export function parse(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
genMap(descriptor.template)
|
// no need to genMap for template as its AST already accounts for the
|
||||||
|
// position in the SFC
|
||||||
genMap(descriptor.script)
|
genMap(descriptor.script)
|
||||||
descriptor.styles.forEach(genMap)
|
descriptor.styles.forEach(genMap)
|
||||||
descriptor.customBlocks.forEach(genMap)
|
descriptor.customBlocks.forEach(genMap)
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { SFCDescriptor } from '../parse'
|
||||||
import {
|
import {
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
SimpleExpressionNode,
|
SimpleExpressionNode,
|
||||||
createRoot,
|
|
||||||
forAliasRE,
|
forAliasRE,
|
||||||
parserOptions,
|
parserOptions,
|
||||||
transform,
|
transform,
|
||||||
|
@ -35,7 +34,7 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let code = ''
|
let code = ''
|
||||||
transform(createRoot([ast]), {
|
transform(ast, {
|
||||||
nodeTransforms: [
|
nodeTransforms: [
|
||||||
node => {
|
node => {
|
||||||
if (node.type === NodeTypes.ELEMENT) {
|
if (node.type === NodeTypes.ELEMENT) {
|
||||||
|
|
Loading…
Reference in New Issue