wip: v-pre handling

This commit is contained in:
Evan You 2023-11-16 01:31:52 +08:00
parent 1a1f680536
commit c342433cb5
3 changed files with 122 additions and 49 deletions

View File

@ -191,6 +191,7 @@ export interface DirectiveNode extends Node {
exp: ExpressionNode | undefined exp: ExpressionNode | undefined
arg: ExpressionNode | undefined arg: ExpressionNode | undefined
modifiers: string[] modifiers: string[]
raw?: string
/** /**
* optional property to cache the expression parse result for v-for * optional property to cache the expression parse result for v-for
*/ */

View File

@ -282,6 +282,7 @@ export default class Tokenizer {
this.cbs.oninterpolation(this.sectionStart, this.index) this.cbs.oninterpolation(this.sectionStart, this.index)
this.state = State.Text this.state = State.Text
this.sectionStart = this.index this.sectionStart = this.index
this.stateText(this.buffer.charCodeAt(this.index))
} }
} }

View File

@ -8,6 +8,7 @@ import {
Namespaces, Namespaces,
NodeTypes, NodeTypes,
RootNode, RootNode,
SimpleExpressionNode,
SourceLocation, SourceLocation,
TemplateChildNode, TemplateChildNode,
createRoot createRoot
@ -67,7 +68,8 @@ let currentAttrStartIndex = -1
let currentAttrEndIndex = -1 let currentAttrEndIndex = -1
let currentAttrs: Set<string> = new Set() let currentAttrs: Set<string> = new Set()
let inPre = 0 let inPre = 0
// let inVPre = 0 let inVPre = false
let currentElementIsVPreBoundary = false
const stack: ElementNode[] = [] const stack: ElementNode[] = []
const tokenizer = new Tokenizer({ const tokenizer = new Tokenizer({
@ -80,6 +82,9 @@ const tokenizer = new Tokenizer({
}, },
oninterpolation(start, end) { oninterpolation(start, end) {
if (inVPre) {
return onText(getSlice(start, end), start, end)
}
let innerStart = start + tokenizer.delimiterOpen.length let innerStart = start + tokenizer.delimiterOpen.length
let innerEnd = end - tokenizer.delimiterClose.length let innerEnd = end - tokenizer.delimiterClose.length
while (isWhitespace(currentInput.charCodeAt(innerStart))) { while (isWhitespace(currentInput.charCodeAt(innerStart))) {
@ -103,7 +108,24 @@ const tokenizer = new Tokenizer({
}, },
onopentagname(start, end) { onopentagname(start, end) {
emitOpenTag(getSlice(start, end), start) const name = getSlice(start, end)
currentElement = {
type: NodeTypes.ELEMENT,
tag: name,
ns: currentOptions.getNamespace(name, getParent()),
// TODO refine tag type
tagType: ElementTypes.ELEMENT,
props: [],
children: [],
loc: {
start: tokenizer.getPos(start - 1),
// @ts-expect-error to be attached on tag close
end: undefined,
source: ''
},
codegenNode: undefined
}
currentAttrs.clear()
}, },
onopentagend(end) { onopentagend(end) {
@ -138,40 +160,72 @@ const tokenizer = new Tokenizer({
ondirname(start, end) { ondirname(start, end) {
const raw = getSlice(start, end) const raw = getSlice(start, end)
const name = if (inVPre) {
raw === '.' || raw === ':' currentProp = {
? 'bind' type: NodeTypes.ATTRIBUTE,
: raw === '@' name: raw,
? 'on' value: undefined,
: raw === '#' loc: getLoc(start)
? 'slot' }
: raw.slice(2) } else {
currentProp = { const name =
type: NodeTypes.DIRECTIVE, raw === '.' || raw === ':'
name, ? 'bind'
exp: undefined, : raw === '@'
arg: undefined, ? 'on'
modifiers: [], : raw === '#'
loc: getLoc(start) ? 'slot'
: raw.slice(2)
currentProp = {
type: NodeTypes.DIRECTIVE,
name,
raw,
exp: undefined,
arg: undefined,
modifiers: [],
loc: getLoc(start)
}
if (name === 'pre') {
inVPre = true
currentElementIsVPreBoundary = true
// force current element type
currentElement!.tagType = ElementTypes.ELEMENT
// convert dirs before this one to attributes
const props = currentElement!.props
for (let i = 0; i < props.length; i++) {
if (props[i].type === NodeTypes.DIRECTIVE) {
props[i] = dirToAttr(props[i] as DirectiveNode)
}
}
}
} }
}, },
ondirarg(start, end) { ondirarg(start, end) {
const arg = getSlice(start, end) const arg = getSlice(start, end)
const isStatic = arg[0] !== `[` if (inVPre) {
;(currentProp as DirectiveNode).arg = { ;(currentProp as AttributeNode).name += arg
type: NodeTypes.SIMPLE_EXPRESSION, } else {
content: arg, const isStatic = arg[0] !== `[`
isStatic, ;(currentProp as DirectiveNode).arg = {
constType: isStatic type: NodeTypes.SIMPLE_EXPRESSION,
? ConstantTypes.CAN_STRINGIFY content: arg,
: ConstantTypes.NOT_CONSTANT, isStatic,
loc: getLoc(start, end) constType: isStatic
? ConstantTypes.CAN_STRINGIFY
: ConstantTypes.NOT_CONSTANT,
loc: getLoc(start, end)
}
} }
}, },
ondirmodifier(start, end) { ondirmodifier(start, end) {
;(currentProp as DirectiveNode).modifiers.push(getSlice(start, end)) const mod = getSlice(start, end)
if (inVPre) {
;(currentProp as AttributeNode).name += '.' + mod
} else {
;(currentProp as DirectiveNode).modifiers.push(mod)
}
}, },
onattribdata(start, end) { onattribdata(start, end) {
@ -188,6 +242,9 @@ const tokenizer = new Tokenizer({
// check duplicate attrs // check duplicate attrs
const start = currentProp!.loc.start.offset const start = currentProp!.loc.start.offset
const name = getSlice(start, end) const name = getSlice(start, end)
if (currentProp!.type === NodeTypes.DIRECTIVE) {
currentProp!.raw = name
}
if (currentAttrs.has(name)) { if (currentAttrs.has(name)) {
currentProp = null currentProp = null
// TODO emit error DUPLICATE_ATTRIBUTE // TODO emit error DUPLICATE_ATTRIBUTE
@ -225,7 +282,12 @@ const tokenizer = new Tokenizer({
} }
} }
currentProp.loc.end = tokenizer.getPos(end) currentProp.loc.end = tokenizer.getPos(end)
currentElement.props.push(currentProp!) if (
currentProp.type !== NodeTypes.DIRECTIVE ||
currentProp.name !== 'pre'
) {
currentElement.props.push(currentProp)
}
} }
currentAttrValue = '' currentAttrValue = ''
currentAttrStartIndex = currentAttrEndIndex = -1 currentAttrStartIndex = currentAttrEndIndex = -1
@ -251,26 +313,6 @@ function getSlice(start: number, end: number) {
return currentInput.slice(start, end) return currentInput.slice(start, end)
} }
function emitOpenTag(name: string, start: number) {
currentElement = {
type: NodeTypes.ELEMENT,
tag: name,
ns: currentOptions.getNamespace(name, getParent()),
// TODO refine tag type
tagType: ElementTypes.ELEMENT,
props: [],
children: [],
loc: {
start: tokenizer.getPos(start - 1),
// @ts-expect-error to be attached on tag close
end: undefined,
source: ''
},
codegenNode: undefined
}
currentAttrs.clear()
}
function endOpenTag(end: number) { function endOpenTag(end: number) {
addNode(currentElement!) addNode(currentElement!)
const name = currentElement!.tag const name = currentElement!.tag
@ -299,7 +341,7 @@ function onText(content: string, start: number, end: number) {
if (lastNode?.type === NodeTypes.TEXT) { if (lastNode?.type === NodeTypes.TEXT) {
// merge // merge
lastNode.content += content lastNode.content += content
// TODO update loc lastNode.loc.end = tokenizer.getPos(end)
} else { } else {
parent.children.push({ parent.children.push({
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
@ -325,6 +367,10 @@ function onCloseTag(el: ElementNode, end: number) {
if (currentOptions.isPreTag(el.tag)) { if (currentOptions.isPreTag(el.tag)) {
inPre-- inPre--
} }
if (currentElementIsVPreBoundary) {
inVPre = false
currentElementIsVPreBoundary = false
}
} }
const windowsNewlineRE = /\r\n/g const windowsNewlineRE = /\r\n/g
@ -429,6 +475,31 @@ function getLoc(start: number, end?: number): SourceLocation {
} }
} }
function dirToAttr(dir: DirectiveNode): AttributeNode {
const attr: AttributeNode = {
type: NodeTypes.ATTRIBUTE,
name: dir.raw!,
value: undefined,
loc: dir.loc
}
if (dir.exp) {
// account for quotes
const loc = dir.exp.loc
if (loc.end.offset < dir.loc.end.offset) {
loc.start.offset--
loc.start.column--
loc.end.offset++
loc.end.column++
}
attr.value = {
type: NodeTypes.TEXT,
content: (dir.exp as SimpleExpressionNode).content,
loc
}
}
return attr
}
function reset() { function reset() {
tokenizer.reset() tokenizer.reset()
currentElement = null currentElement = null