feat(compiler): implement support for v-pre

This commit is contained in:
Evan You 2019-10-09 16:00:08 -04:00
parent 08df965e3c
commit 5dfb271551
2 changed files with 151 additions and 33 deletions

View File

@ -1278,6 +1278,88 @@ describe('compiler: parse', () => {
})
})
test('v-pre', () => {
const ast = parse(
`<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
`<div :id="foo"><Comp/>{{ bar }}</div>`
)
const divWithPre = ast.children[0] as ElementNode
expect(divWithPre.props).toMatchObject([
{
type: NodeTypes.ATTRIBUTE,
name: `:id`,
value: {
type: NodeTypes.TEXT,
content: `foo`
},
loc: {
source: `:id="foo"`,
start: {
line: 1,
column: 12
},
end: {
line: 1,
column: 21
}
}
}
])
expect(divWithPre.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tagType: ElementTypes.ELEMENT,
tag: `Comp`
})
expect(divWithPre.children[1]).toMatchObject({
type: NodeTypes.TEXT,
content: `{{ bar }}`
})
// should not affect siblings after it
const divWithoutPre = ast.children[1] as ElementNode
expect(divWithoutPre.props).toMatchObject([
{
type: NodeTypes.DIRECTIVE,
name: `bind`,
arg: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: true,
content: `id`
},
exp: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: false,
content: `foo`
},
loc: {
source: `:id="foo"`,
start: {
line: 2,
column: 6
},
end: {
line: 2,
column: 15
}
}
}
])
expect(divWithoutPre.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tagType: ElementTypes.COMPONENT,
tag: `Comp`
})
expect(divWithoutPre.children[1]).toMatchObject({
type: NodeTypes.INTERPOLATION,
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `bar`,
isStatic: false
}
})
})
test('end tags are case-insensitive.', () => {
const ast = parse('<div>hello</DIV>after')
const element = ast.children[0] as ElementNode

View File

@ -26,6 +26,7 @@ import {
TemplateChildNode,
InterpolationNode
} from './ast'
import { extend } from '@vue/shared'
export interface ParserOptions {
isVoidTag?: (tag: string) => boolean // e.g. img, br, hr
@ -74,6 +75,7 @@ interface ParserContext {
line: number
column: number
maxCRNameLength: number
inPre: boolean
}
export function parse(content: string, options: ParserOptions = {}): RootNode {
@ -109,7 +111,8 @@ function createParserContext(
maxCRNameLength: Object.keys(
options.namedCharacterReferences ||
defaultParserOptions.namedCharacterReferences
).reduce((max, name) => Math.max(max, name.length), 0)
).reduce((max, name) => Math.max(max, name.length), 0),
inPre: false
}
}
@ -127,7 +130,7 @@ function parseChildren(
const s = context.source
let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined
if (startsWith(s, context.options.delimiters[0])) {
if (!context.inPre && startsWith(s, context.options.delimiters[0])) {
// '{{'
node = parseInterpolation(context, mode)
} else if (mode === TextModes.DATA && s[0] === '<') {
@ -325,8 +328,10 @@ function parseElement(
__DEV__ && assert(/^<[a-z]/i.test(context.source))
// Start tag.
const wasInPre = context.inPre
const parent = last(ancestors)
const element = parseTag(context, TagType.Start, parent)
const isPreBoundary = context.inPre && !wasInPre
if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
return element
@ -354,6 +359,10 @@ function parseElement(
}
element.loc = getSelection(context, element.loc.start)
if (isPreBoundary) {
context.inPre = false
}
return element
}
@ -380,18 +389,68 @@ function parseTag(
const start = getCursor(context)
const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!
const tag = match[1]
const props = []
const ns = context.options.getNamespace(tag, parent)
let tagType = ElementTypes.ELEMENT
if (tag === 'slot') tagType = ElementTypes.SLOT
else if (tag === 'template') tagType = ElementTypes.TEMPLATE
else if (/[A-Z-]/.test(tag)) tagType = ElementTypes.COMPONENT
advanceBy(context, match[0].length)
advanceSpaces(context)
// save current state in case we need to re-parse attributes with v-pre
const cursor = getCursor(context)
const currentSource = context.source
// Attributes.
let props = parseAttributes(context, type)
// check v-pre
if (
!context.inPre &&
props.some(p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre')
) {
context.inPre = true
// reset context
extend(context, cursor)
context.source = currentSource
// re-parse attrs and filter out v-pre itself
props = parseAttributes(context, type).filter(p => p.name !== 'v-pre')
}
// Tag close.
let isSelfClosing = false
if (context.source.length === 0) {
emitError(context, ErrorCodes.EOF_IN_TAG)
} else {
isSelfClosing = startsWith(context.source, '/>')
if (type === TagType.End && isSelfClosing) {
emitError(context, ErrorCodes.END_TAG_WITH_TRAILING_SOLIDUS)
}
advanceBy(context, isSelfClosing ? 2 : 1)
}
let tagType = ElementTypes.ELEMENT
if (!context.inPre) {
if (tag === 'slot') tagType = ElementTypes.SLOT
else if (tag === 'template') tagType = ElementTypes.TEMPLATE
else if (/[A-Z-]/.test(tag)) tagType = ElementTypes.COMPONENT
}
return {
type: NodeTypes.ELEMENT,
ns,
tag,
tagType,
props,
isSelfClosing,
children: [],
loc: getSelection(context, start),
codegenNode: undefined // to be created during transform phase
}
}
function parseAttributes(
context: ParserContext,
type: TagType
): (AttributeNode | DirectiveNode)[] {
const props = []
const attributeNames = new Set<string>()
while (
context.source.length > 0 &&
@ -418,30 +477,7 @@ function parseTag(
}
advanceSpaces(context)
}
// Tag close.
let isSelfClosing = false
if (context.source.length === 0) {
emitError(context, ErrorCodes.EOF_IN_TAG)
} else {
isSelfClosing = startsWith(context.source, '/>')
if (type === TagType.End && isSelfClosing) {
emitError(context, ErrorCodes.END_TAG_WITH_TRAILING_SOLIDUS)
}
advanceBy(context, isSelfClosing ? 2 : 1)
}
return {
type: NodeTypes.ELEMENT,
ns,
tag,
tagType,
props,
isSelfClosing,
children: [],
loc: getSelection(context, start),
codegenNode: undefined // to be created during transform phase
}
return props
}
function parseAttribute(
@ -497,7 +533,7 @@ function parseAttribute(
}
const loc = getSelection(context, start)
if (/^(v-|:|@|#)/.test(name)) {
if (!context.inPre && /^(v-|:|@|#)/.test(name)) {
const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^@|^#)([^\.]+))?(.+)?$/i.exec(
name
)!