wip: support reusing template ast from sfc descriptor

This commit is contained in:
Evan You 2023-11-20 22:05:27 +08:00
parent fc4f801070
commit 5fc695be26
6 changed files with 89 additions and 33 deletions

View File

@ -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>

View File

@ -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)],

View File

@ -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
}

View File

@ -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
)}`

View File

@ -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)

View File

@ -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) {