上一篇主要讲到了在vue
中,template
通过parse
生成ast
(抽象语法树)的主要核心流程。这个ast
是对模板的完整描述,不能直接拿来生成代码,缺乏语义化,并且没有包含编译优化的相关属性,还需要进一步转换,所以用到了我们今天需要讲解的transform
。
主要流程
export function transform(root: RootNode, options: TransformOptions) {const context = createTransformContext(root, options)traverseNode(root, context)if (options.hoistStatic) {hoistStatic(root, context)}if (!options.ssr) {createRootCodegen(root, context)}// finalize meta informationroot.helpers = [...context.helpers.keys()]root.components = [...context.components]root.directives = [...context.directives]root.imports = context.importsroot.hoists = context.hoistsroot.temps = context.tempsroot.cached = context.cachedif (__COMPAT__) {root.filters = [...context.filters!]}
}
首先是创建transform
上下文,通过traverseNode
遍历ast
节点,通过createRootCodegen
创建根代码生成节点(当然还有一些静态提升的东东,这里暂时先不描述了)。
创建transform上下文
function createTransformsContext(root, options) {const context = {root,nodeTransforms: options.nodeTransforms || [],helpers: new Map(),helper(key) {context.helpers.set(key, 1)}···}return context
}
transform
上下文对象中维护了一些配置,这里我们就把核心流程中主要用的配置拿了出来。比如整个ast
节点,转换过程中需要调用的一些转换函数。
遍历AST节点
function traverseNode(node: any, context) {// 节点转换函数const nodeTransforms = context.nodeTransformsconst exitFns: any = []for (let i = 0; i < nodeTransforms.length; i++) {const transform = nodeTransforms[i]// 有些转换函数会设计一个退出函数,在处理完子节点后执行const onExit = transform(node, context)if (onExit) exitFns.push(onExit)}switch (node.type) {case NodeTypes.INTERPOLATION:// 需要导入toString辅助函数context.helper(TO_DISPLAT_STRING)breakcase NodeTypes.ROOT:case NodeTypes.ELEMENT:// 遍历子节点traverseChildren(node, context)breakdefault:break}// 执行转换函数返回的退出函数let i = exitFns.lengthwhile (i--) {exitFns[i]()}
}
traverseNode
递归的遍历ast
中的每个节点,然后执行一些转换函数,有些转换函数还会设计退出函数,然后用exitFns
接收,这些退出函数在子节点处理完毕之后执行,因为有些逻辑需要依赖子节点处理完毕的结果。
下面我们来看下转换函数,这里我们主要讲解3种转换函数:Element
、表达式和Text
。
Element转换函数
export const transformElement: NodeTransform = (node, context) => {
//返回退出函数 return function postTransformElement() {node = context.currentNode!if (!(node.type === NodeTypes.ELEMENT &&(node.tagType === ElementTypes.ELEMENT ||node.tagType === ElementTypes.COMPONENT))) {return}const { tag, props } = nodeconst isComponent = node.tagType ==