Skip to content
On this page

遍历AST语法树

我们需要遍历ast语法树,访问树中节点进行语法树的转化

function transformElement(node){
    console.log('元素处理',node)
}
function transformText(node){
    console.log('文本处理',node)
}
function transformExpression(node){
    console.log('表达式')
}
function traverseNode(node,context){
    context.currentNode = node;
    const transforms = context.nodeTransforms;
    for(let i = 0; i < transforms.length;i++){
        transforms[i](node,context); // 调用转化方法进行转化
        if(!context.currentNode) return
    }
    switch(node.type){
        case NodeTypes.ELEMENT:
        case NodeTypes.ROOT:
            for(let i = 0; i < node.children.length;i++){
                context.parent = node;
                traverseNode(node.children[i],context);
            }
            
    }
}

function createTransformContext(root){
    const context = {
        currentNode:root, // 当前转化节点 
        parent:null,   // 当前转化节点的父节点
        nodeTransforms:[ // 转化方法
            transformElement,
            transformText,
            transformExpression
        ],
        helpers: new Map(), // 创建帮助映射表,记录调用方法次数
        helper(name){
            const count = context.helpers.get(name) || 0;
            context.helpers.set(name,count+1)
            return name
        }
    }
    return context
}

function transform(root){
    // 创建转化的上下文, 记录转化方法及当前转化节点
    let context = createTransformContext(root)
    // 递归遍历
    traverseNode(root,context)
}
export function compile(template){
    const ast = baseParse(template);
    transform(ast);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

退出函数

表达式不需要退出函数,直接处理即可。元素需要在遍历完所有子节点在进行处理

function transformExpression(node){
    if(node.type == NodeTypes.INTERPOLATION){
        console.log('表达式')
    }
}
function transformElement(node){
    if(node.type === NodeTypes.ELEMENT ){
        return function postTransformElement(){ // 元素处理的退出函数
            // 如果这个元素
            console.log('元素',node)
        }
    }
}
function transformText(node){
    if(node.type === NodeTypes.ELEMENT || node.type === NodeTypes.ROOT){
        return ()=>{
            console.log('元素/root',node)
        }   
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function traverseNode(node,context){
  	// ...
    for(let i = 0; i < transforms.length;i++){
        let onExit = transforms[i](node,context); // 调用转化方法进行转化
        if(onExit){
            exitsFns.push(onExit)
        }
        if(!context.currentNode) return
    }
    // ...
    // 最终context.currentNode 是最里面的
    context.currentNode = node; // 修正currentNode;
    let i = exitsFns.length
    while (i--) {
        exitsFns[i]()
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

转化表达式

runtimeHelpers.ts

export const TO_DISPLAY_STRING = Symbol(`toDisplayString`);
export const helperNameMap = {
    [TO_DISPLAY_STRING]: "toDisplayString",
}
1
2
3
4
switch(node.type){
    case NodeTypes.INTERPOLATION:
        context.helper(TO_DISPLAY_STRING); // 用于JSON.stringify
        break
    // ...
}
1
2
3
4
5
6

最后生成的代码我们取值时需要从上下文中进行取值。

export function transformExpression(node){
    if(node.type == NodeTypes.INTERPOLATION){
        node.content.content = `_ctx.${node.content.content}`; // 修改content信息
    }
}
1
2
3
4
5

转化文本元素

文本元素的转化我们需要将多个文本连接起来,如果是动态文本添加patchFlags标识,最终生成文本调用逻辑

function isText(node) {
    return node.type == NodeTypes.INTERPOLATION || node.type == NodeTypes.TEXT;
}
export function transformText(node,context){
    if(node.type === NodeTypes.ELEMENT || node.type === NodeTypes.ROOT){
        return ()=>{
              // 如果这个元素
              let hasText = false;
              const children = node.children;
               let currentContainer = undefined // 合并儿子
               for (let i = 0; i < children.length; i++) {
                   let child = children[i];
                   if (isText(child)) {
                       hasText = true;
                       for (let j = i + 1; j < children.length; j++) {
                           const next = children[j];
                           if(isText(next)){
                               if(!currentContainer){
                                   currentContainer = children[i] = { // 合并表达式
                                       type:NodeTypes.COMPOUND_EXPRESSION,
                                       loc:child.loc,
                                       children:[child]
                                   }
                               }
                               currentContainer.children.push(` + `,next);
                               children.splice(j,1);
                               j--;
                           }else{
                               currentContainer = undefined;
                               break;
                           }
                       }
                   }
               }
              if (!hasText || children.length == 1) { // 一个元素不用管,可以执行innerHTML
                  return
              }
              for (let i = 0; i < children.length; i++) {
                  const child = children[i]
                  if (isText(child) || child.type === NodeTypes.COMPOUND_EXPRESSION) {
                      const callArgs = []
                      callArgs.push(child)
                      if (child.type !== NodeTypes.TEXT) { // 如果不是文本
                          callArgs.push(PatchFlags.TEXT + '')
                      }
                      // 全部格式话成文本调用
                      children[i] = {
                          type: NodeTypes.TEXT_CALL, // 最终需要变成createTextVnode() 增加patchFlag
                          content: child,
                          loc: child.loc, 
                          codegenNode: createCallExpression(context,callArgs) // 创建表达式调用
                      }
                  }
              }
        }   
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

runtimeHelpers.ts

export const CREATE_TEXT = Symbol(`createTextVNode`)
export const helperNameMap = {
    [CREATE_TEXT]:`createTextVNode`
}
1
2
3
4

ast.ts

export function createCallExpression(context,args){
    let callee = context.helper(CREATE_TEXT); // 生成代码时需要createTextVNode方法
    return {
        callee,
        type: NodeTypes.JS_CALL_EXPRESSION,
        arguments: args
    }
}
1
2
3
4
5
6
7
8

转化元素

export function createObjectExpression(properties){
    return {
        type: NodeTypes.JS_OBJECT_EXPRESSION,
        properties
    }
}
export function transformElement(node,context){
    if(node.type === NodeTypes.ELEMENT ){
        return function postTransformElement(){ // 元素处理的退出函数
            let vnodeTag = `'${node.tag}'`;
            let properties = [];
            let props = node.props
           for(let i = 0 ; i< props.length; i++){ // 这里属性其实应该在codegen里在处理
                properties.push({
                    key:props[i].name,
                    value:props[i].value.content
                })
            }
            const propsExpression = props.length > 0 ? createObjectExpression(properties):null
            let vnodeChildren = null;
            if (node.children.length === 1) {
                // 只有一个孩子节点 ,那么当生成 render 函数的时候就不用 [] 包裹
                const child = node.children[0];
                vnodeChildren = child;
            }else{ 
                if(node.children.length > 0){ // 处理儿子节点
                    vnodeChildren = node.children
                }
           }
           // 代码生成
           node.codegenNode = createVNodeCall(context,vnodeTag,propsExpression,vnodeChildren);
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

runtimeHelpers.ts

export const CREATE_ELEMENT_VNODE = Symbol("createElementVNode");
export const helperNameMap = {
    [CREATE_ELEMENT_VNODE]: "createElementVNode", // 创建元素节点标识
}
1
2
3
4
export function createVNodeCall(context,tag,props,children){
    context.helper(CREATE_ELEMENT_VNODE);
    return {
        type:NodeTypes.VNODE_CALL,
        tag,
        props,
        children
    }
}
1
2
3
4
5
6
7
8
9

转化根节点

转化根节点需要添加block节点

export const FRAGMENT = Symbol("FRAGMENT");
export const CREATE_ELEMENT_BLOCK = Symbol(`createElementBlock`)
export const OPEN_BLOCK = Symbol(`openBlock`)
export const helperNameMap = {
    [FRAGMENT]: "Fragment",
    [OPEN_BLOCK]: `openBlock`,  // block处理
    [CREATE_ELEMENT_BLOCK]: `createElementBlock`
}
1
2
3
4
5
6
7
8
function createTransformContext(root){
    const context = {
        removeHelper(name){
            const count = context.helpers.get(name);
            if(count){
                const currentCount = count - 1;
                if(!currentCount){
                    context.helpers.delete(name);
                }else{
                    context.helpers.set(name,currentCount)
                }
            }
        },
        // ...
    }
    return context
}
function createRootCodegen(root,context){
    let {children } = root
    if(children.length == 1){
        const child = children[0];
        if (child.type === NodeTypes.ELEMENT && child.codegenNode) {
            const codegenNode = child.codegenNode;
            root.codegenNode = codegenNode; 
            context.removeHelper(CREATE_ELEMENT_VNODE); // 不要创建元素
            context.helper(OPEN_BLOCK) 
            context.helper(CREATE_ELEMENT_BLOCK); // 创建元素block就好了
            root.codegenNode.isBlock = true; // 只有一个元素节点,那么他就是block节点
          } else {
            root.codegenNode = child; // 直接用里面的节点换掉
          }
    }else{
        root.codegenNode = createVNodeCall(context,context.helper(FRAGMENT),undefined,root.children)
        context.helper(OPEN_BLOCK)
        context.helper(CREATE_ELEMENT_BLOCK)
        root.codegenNode.isBlock = true; // 增加block fragment
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
export function transform(root){
    // 创建转化的上下文, 记录转化方法及当前转化节点
    let context = createTransformContext(root)
    // 递归遍历
    traverseNode(root,context)
    createRootCodegen(root,context); // 生成根节点的codegen
    root.helpers = [...context.helpers.keys()]
}
1
2
3
4
5
6
7
8

整个transform的流程,就是给ast节点添加codegenNode属性,用于方便生成对应的代码,并且收集生成代码所需的方法。

Released under the MIT License.