本文已参与「新人创作礼」活动,一起开启掘金创作之路。
上一节我们已经介绍了template解析成match对象的一个过程,不清楚可以点击这里,本节我们来分析match对象生成ast树的过程。
Vue 编译(compile)核心流程之parse(AST树的生成)
template模板字符串中的开始标签、结束标签、文本字符串等的大致解析过程在上一节我们已经介绍了,这一节我们来继续分析。
当处理开始标签的时候,其中会执行到handleStartTag函数,这个函数的最后一步会执行options.start函数,接下来我们分析下这个函数:
function handleStartTag (match) {
......
// 调用options中的start函数
if (options.start) {
options.start(tagName, attrs, unary, match.start, match.end)
}
}
start函数
start (tag, attrs, unary) {
// check namespace.
// inherit parent ns if there is one
const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)
// handle IE svg bug
/* istanbul ignore if */
if (isIE && ns === 'svg') {
attrs = guardIESVGBug(attrs)
}
// 生成ast树的核心函数,返回ast对象 { type: 1, tag, attrsList, attrsMap, parent, children}
let element: ASTElement = createASTElement(tag, attrs, currentParent)
if (ns) {
element.ns = ns
}
// 如果是script、stype、text/javascript等标签且非服务端渲染,则报错
if (isForbiddenTag(element) && !isServerRendering()) {
element.forbidden = true
process.env.NODE_ENV !== 'production' && warn(
'Templates should only be responsible for mapping the state to the ' +
'UI. Avoid placing tags with side-effects in your templates, such as ' +
`<${tag}>` + ', as they will not be parsed.'
)
}
// apply pre-transforms 执行平台相关的preTransforms函数
for (let i = 0; i < preTransforms.length; i++) {
element = preTransforms[i](element, options) || element
}
// 对ast对象做一个丰富
// v-pre相关,这个指定对于没有其他指令的标签用处较大,跳过大量没有指令的节点会加快编译。
if (!inVPre) {
// 拿到v-pre的值,给AST element添加pre属性,并且在attrs数组及attrsMap中删掉该指令
processPre(element)
if (element.pre) {
// 解析到了v-pre指令,inVPre赋值为true
inVPre = true
}
}
// 如果tag是pre标签
if (platformIsPreTag(element.tag)) {
// inPre赋为true
inPre = true
}
// inVPre为true的时候,执行processRawAttrs函数,就不需要编译分析其他的指令了
if (inVPre) {
processRawAttrs(element)
} else if (!element.processed) {
// 没有v-pre,解析attr中存储的指令
// structural directives
// 解析For指令
processFor(element)
// 解析If指令
processIf(element)
// 解析Once指令
processOnce(element)
// element-scope stuff
// 解析其他的指令
processElement(element, options)
}
function checkRootConstraints (el) {
if (process.env.NODE_ENV !== 'production') {
if (el.tag === 'slot' || el.tag === 'template') {
warnOnce(
`Cannot use <${el.tag}> as component root element because it may ` +
'contain multiple nodes.'
)
}
if (el.attrsMap.hasOwnProperty('v-for')) {
warnOnce(
'Cannot use v-for on stateful component root element because ' +
'it renders multiple elements.'
)
}
}
}
// tree management
// ast树管理
if (!root) {
root = element
// 检查根节点
checkRootConstraints(root)
} else if (!stack.length) {
// 不止一个根节点,报错
// allow root elements with v-if, v-else-if and v-else
if (root.if && (element.elseif || element.else)) {
checkRootConstraints(element)
addIfCondition(root, {
exp: element.elseif,
block: element
})
} else if (process.env.NODE_ENV !== 'production') {
warnOnce(
`Component template should contain exactly one root element. ` +
`If you are using v-if on multiple elements, ` +
`use v-else-if to chain them instead.`
)
}
}
// 对父子节点的关系进行管理,生成规范的AST树
if (currentParent && !element.forbidden) {
if (element.elseif || element.else) { // elseif的逻辑分析
processIfConditions(element, currentParent)
} else if (element.slotScope) { // scoped slot slot的相关逻辑分析
currentParent.plain = false
const name = element.slotTarget || '"default"'
;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
} else { // 管理父子节点关系
currentParent.children.push(element)
element.parent = currentParent
}
}
// 非一元标签,需要执行推栈入栈操作,管理ast树
if (!unary) {
currentParent = element
stack.push(element)
} else {
// 关闭标签
closeElement(element)
}
},
start函数主要的逻辑分成两个部分,第一部分是创建AST树,解析属性,对AST树进行扩展,第二部分是对AST树进行管理;
1.创建AST树并解析属性
创建AST树
export function createASTElement (
tag: string,
attrs: Array<Attr>,
parent: ASTElement | void
): ASTElement {
return {
type: 1,
tag,
attrsList: attrs,
attrsMap: makeAttrsMap(attrs),
parent,
children: []
}
}
function makeAttrsMap (attrs: Array<Object>): Object {
const map = {}
for (let i = 0, l = attrs.length; i < l; i++) {
if (
process.env.NODE_ENV !== 'production' &&
map[attrs[i].name] && !isIE && !isEdge
) {
warn('duplicate attribute: ' + attrs[i].name)
}
map[attrs[i].name] = attrs[i].value
}
return map
}
这个地方会返回一个原始的AST树对象,makeAttrsMap函数会遍历attr数组里面的对象属性,解析成为一个map对象键值对的形式,这样会方便后续对属性的查找取值操作。
解析指令
生成了基本的AST树后,会对标签的属性进行解析,这里我们分析下部分指令的解析:
- v-if指令
function processIf (el) {
// getAndRemoveAttr函数会取出AST element对象中的v-if指令作为exp,
// 并移除el对象的attr数组和attrMap对象中的v-if指令
// 如v-if="isTrue"会被解析成exp会赋值为isTrue
const exp = getAndRemoveAttr(el, 'v-if')
// 有表达式的情况
if (exp) {
// 在AST element对象上添加if属性
el.if = exp
添加ifConditions属性数组,将第二个参数push进该数组
addIfCondition(el, {
exp: exp,
block: el
})
} else {
// 如果是v-if不存在,设置v-else指令
if (getAndRemoveAttr(el, 'v-else') != null) {
el.else = true
}
// 如果是v-if不存在,设置v-else-if指令
const elseif = getAndRemoveAttr(el, 'v-else-if')
if (elseif) {
el.elseif = elseif
}
}
}
export function addIfCondition (el: ASTElement, condition: ASTIfCondition) {
// 给AST element添加ifConditions属性,该属性为数组
if (!el.ifConditions) {
el.ifConditions = []
}
el.ifConditions.push(condition)
}
- 其他指令
export function processElement (element: ASTElement, options: CompilerOptions) {
// 解析Key指令
processKey(element)
// determine whether this is a plain element after
// removing structural attributes
element.plain = !element.key && !element.attrsList.length
// 解析Ref指令
processRef(element)
// 解析Slot指令
processSlot(element)
// 解析Component指令
processComponent(element)
for (let i = 0; i < transforms.length; i++) {
element = transforms[i](element, options) || element
}
// 解析剩余的其他指令如event model等
processAttrs(element)
}
2.AST树管理
// tree management
// ast树管理
if (!root) {
root = element
// 检查根节点
checkRootConstraints(root)
} else if (!stack.length) {
// 不止一个根节点,报错
// allow root elements with v-if, v-else-if and v-else
if (root.if && (element.elseif || element.else)) {
checkRootConstraints(element)
addIfCondition(root, {
exp: element.elseif,
block: element
})
} else if (process.env.NODE_ENV !== 'production') {
warnOnce(
`Component template should contain exactly one root element. ` +
`If you are using v-if on multiple elements, ` +
`use v-else-if to chain them instead.`
)
}
}
if (currentParent && !element.forbidden) {
if (element.elseif || element.else) {
processIfConditions(element, currentParent)
} else if (element.slotScope) { // scoped slot
currentParent.plain = false
const name = element.slotTarget || '"default"'
;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
} else {
currentParent.children.push(element)
element.parent = currentParent
}
}
if (!unary) {
currentParent = element
stack.push(element)
} else {
// 关闭标签
closeElement(element)
}
AST树管理的主要作用就是去维护AST树对象的父子关系,并对根节点做一些检测,比如不能出现重复的根节点情况等,此外还会对一些指令进行解析。
总结
本节我们分析了处理开始标签中start函数的整个解析流程,大致可以分为三步:
- 1.生成初始的AST对象;
- 2.将我们扫描开始标签生成的match对象中attr数组作进一步的解析,针对不同的指令属性,解析生成不同的属性扩展到AST树对象上;
- 3.对生成的AST对象进行管理,维护AST树对象的父子关系;
下一节我们继续分析parse过程的其他流程,点击这里去往下一节。