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(
|
||||
template: string,
|
||||
src: string | RootNode,
|
||||
options: CompilerOptions = {}
|
||||
): CodegenResult {
|
||||
return baseCompile(
|
||||
template,
|
||||
src,
|
||||
extend({}, parserOptions, options, {
|
||||
nodeTransforms: [
|
||||
// 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`] = `
|
||||
[
|
||||
[SyntaxError: Error parsing JavaScript expression: Unexpected token (1:3)],
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { RawSourceMap, SourceMapConsumer } from 'source-map-js'
|
||||
import {
|
||||
compileTemplate,
|
||||
SFCTemplateCompileOptions
|
||||
|
@ -107,18 +108,54 @@ test('source map', () => {
|
|||
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', () => {
|
||||
|
@ -199,3 +236,36 @@ test('dynamic v-on + static v-on should merged', () => {
|
|||
|
||||
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'
|
||||
|
||||
export interface TemplateCompiler {
|
||||
compile(template: string, options: CompilerOptions): CodegenResult
|
||||
compile(source: string | RootNode, options: CompilerOptions): CodegenResult
|
||||
parse(template: string, options: ParserOptions): RootNode
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,7 @@ export interface SFCTemplateCompileResults {
|
|||
|
||||
export interface SFCTemplateCompileOptions {
|
||||
source: string
|
||||
ast?: RootNode
|
||||
filename: string
|
||||
id: string
|
||||
scoped?: boolean
|
||||
|
@ -164,6 +165,7 @@ function doCompileTemplate({
|
|||
slotted,
|
||||
inMap,
|
||||
source,
|
||||
ast: inAST,
|
||||
ssr = false,
|
||||
ssrCssVars,
|
||||
isProd = false,
|
||||
|
@ -199,7 +201,7 @@ function doCompileTemplate({
|
|||
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,
|
||||
|
@ -235,7 +237,7 @@ function doCompileTemplate({
|
|||
let msg = w.message
|
||||
if (w.loc) {
|
||||
msg += `\n${generateCodeFrame(
|
||||
source,
|
||||
inAST?.source || source,
|
||||
w.loc.start.offset,
|
||||
w.loc.end.offset
|
||||
)}`
|
||||
|
|
|
@ -3,7 +3,9 @@ import {
|
|||
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'
|
||||
|
@ -36,7 +38,7 @@ export interface SFCBlock {
|
|||
|
||||
export interface SFCTemplateBlock extends SFCBlock {
|
||||
type: 'template'
|
||||
ast: ElementNode
|
||||
ast: RootNode
|
||||
}
|
||||
|
||||
export interface SFCScriptBlock extends SFCBlock {
|
||||
|
@ -154,7 +156,7 @@ export function parse(
|
|||
source,
|
||||
false
|
||||
) as SFCTemplateBlock)
|
||||
templateBlock.ast = node
|
||||
templateBlock.ast = createRoot(node.children, source)
|
||||
|
||||
// warn against 2.x <template 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)
|
||||
descriptor.styles.forEach(genMap)
|
||||
descriptor.customBlocks.forEach(genMap)
|
||||
|
|
|
@ -3,7 +3,6 @@ import { SFCDescriptor } from '../parse'
|
|||
import {
|
||||
NodeTypes,
|
||||
SimpleExpressionNode,
|
||||
createRoot,
|
||||
forAliasRE,
|
||||
parserOptions,
|
||||
transform,
|
||||
|
@ -35,7 +34,7 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
|
|||
}
|
||||
|
||||
let code = ''
|
||||
transform(createRoot([ast]), {
|
||||
transform(ast, {
|
||||
nodeTransforms: [
|
||||
node => {
|
||||
if (node.type === NodeTypes.ELEMENT) {
|
||||
|
|
Loading…
Reference in New Issue