Compiler
vue 内置了一个编译器,主要的作用是将template 中的字符串转换为AST,将各标签中的元素以及属性等用特定的数据结构进行了描述,其内部通过pase对字符串进行解析。在vue中compiler并不是固定执行,而是通过createCompilerCreator 生成 createCompiler 再由 其生成
{compile,compileToFunctions:createCompileToFunctionFn(compile)}
而主要对template 进行解析的 parse就在compile 中执行。顺带提一句这个编译器之所以要通过这么麻烦的创建流程主要是因为它需要通过多个compileOption对编译器进行配置,修改编译器内部的规则。
Parse
对字符串进行解析,而其内部主要分为html的解析与text的解析
parseHTML
HTML解析,其接受template以及一些配置参数其中最主要的就是 start 和 end ,在parse HTML 内部对template 进行了标签拆分,拆分的时候又会根据不同类型的标签进行处理, 其主要分为几个阶段:起始标签开合、起始标签闭合、结束标签闭合、文本解析。
起始标签解析:
将检测到的所有的标签都以栈的形式存储,当检测到一个标签开始时,会对它内部的属性进行解析,将解析出来的属性关联到标签中最后当起始标签闭合的时候会将解析的结果作为参数调用外部传入的start
结束标签闭合:
当一个标签解析完成后,会检测标签存储的栈,如果栈顶中的标签与当前标签不同则会忽略不匹配的标签(修改栈顶的位置)并提示,直到找到匹配的标签,然后执行 外部传入的end
文本解析:
不在标签中的字符都被视作文本,会将其作为参数由外部的 chars 进行处理
start
注意:在编译器生成期间进行了多个options 的处理。
module:
其中有class(区分class 与 :class) , style (类class), model(对input 根据类型生成 ifCondition【checkbox,radio,other】 ) 的处理
- transforms = pluckModuleFunction(options.modules, 'transformNode') ;
- preTransforms = pluckModuleFunction(options.modules, 'preTransformNode');
- postTransforms = pluckModuleFunction(options.modules, 'postTransformNode');
- 为每个标签生成 ASTElement 再由一系列的方法对 AST 进行补充
- 执行 element = preTransforms[i](element,options) [ 默认生成 input 的 ifCondition ]
- processPre(element) 确定 el.pre [ 是否对文本做解析 ]
processRawAttrs(element) 将属性作为无绑定属性做解析
processFor(element) 针对 v-for 进行解析 其内部 对 v-for 表达式的值进行语义拆解,并{for [迭代对象],alias [item],iterator1 [ index ],iterator2 [ key ] } 更新到 element 中
processIf(element) 生成 ifConditions<[{ exp: exp,block: el }]> 新增 {if, elseif, else}
processOnce(element) 针对 v-once 生成只渲染一次的节点 新增 { once }
checkRootConstraints(root) 如果是根节点,则判断是否合法
如果非一元标签 , 则确定 currentParent ,将当前元素压入栈中 , 如果是一元标签则 closeElement(element)
end
- 在栈中剔除空白字符的节点
- 弹栈,并确定 currentParent
closeElement(element)
closeElement(element)
processElement(element, options) 处理:key、ref、内外部插槽、绑定属性、组件的判断
- checkRootConstraints(element)
addIfCondition(root, { exp: <string>element.elseif, block: element })
processIfConditions(element, currentParent) 如果上个兄弟存在 ifConditions 则合并
currentParent.scopedSlots[ element.slotTarget ] = element 如果当前标签是 具名插槽则将其归档到父组件的插槽域中
将当前元素加入 parent.children
postTransforms[i](element, options)
processElement(element, options)
processKey(element)
processRef(element)
processSlot(element) 解析 <slot> <xx slot-scope=""> 新增 { slotScope}
processScopedSlots(el) 对组件进行插槽操作,1.对所有子节点进行遍历,并以slotTarget 进行分类;2.针对不同的slotTarget 进行遍历 判断 nodeHas$Slot(child)【判断子节点下的所有分支是否存在引用 $slot 的情况】&& 创建新的 AST scopedSlots [slotTarget] = createASTElement('template', [], el) && slotContainer.children = group (将该子节点作为 child 加入新的AST) , slotContainer.slotScope = '$slot' (将新增的插槽节点 slotScope 的引用改为 $slot) && 遍历原有的 children 将 刚加入至 scopeSlot 的节点剔除; 新增 { scopedSlots }
新增 { slot }
chars
- decodeHTMLCached(text) 对文本进行解码 (使用 he.decode)
去除未闭合标签中的多余空白字符
压缩空白字符
parseText(text, delimiters) 解析文本,会将 {{ var }} 的字符作为绑定属性 , 同时还会 filterParse ,最后生成当前文本节点