上一节我们已经介绍了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过程的其他流程,点击这里去往下一节。
原文:https://juejin.cn/post/7095294495746424840