从零手写Vue3 中编译原理(二)

一.Vue3 transform实现

定义转化标识

export const CREATE_VNODE = Symbol('createVnode');
export const TO_DISPALY_STRING = Symbol('toDisplayString');
export const OPEN_BLOCK = Symbol('openBlock');
export const CREATE_BLOCK = Symbol('createBlock')
export const FRAGMENT = Symbol('Fragment');
export const CREATE_TEXT = Symbol('createTextVNode');

export const helperNameMap: any = {
    [FRAGMENT]: `Fragment`,
    [OPEN_BLOCK]: `openBlock`,
    [CREATE_BLOCK]: `createBlock`,
    [CREATE_VNODE]: `createVNode`,
    [TO_DISPALY_STRING]: "toDisplayString",
    [CREATE_TEXT]: "createTextVNode"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

对AST语法树进行转化,主要是对AST语法树进行优化操作

export function baseCompile(template) {
    // 1.生成ast语法树
    const ast = baseParse(template);
    // 得到对应的转化方法 元素转化、文本转化...  还有指令转化都应该在这里实现
    const nodeTransforms = getBaseTransformPreset(); 
    transform(ast, { nodeTransform })
}
1
2
3
4
5
6
7
function transformElement(node,context) {
    // 转化标签 需要处理他的子节点,所以需要等待子节点遍历完成在处理
    if(!(node.type === 1)){ // 不是元素就不必执行了
        return;
    }
    console.log('转化元素',node)
}
function transformText(node,context) {
    // 处理文本 需要处理他的同级 表达式/文本,所以需要处理完同级后在处理
    if (node.type === NodeTypes.ROOT || node.type === NodeTypes.ElEMENT) {
        console.log('内部可能包含文本', node);
    }
}
function getBaseTransformPreset() {
    return [
        transformElement,
        transformText
    ]// ...指令转化
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

开始进行转化,会先创建转化上下文,之后遍历ast树

function createTransformContext(root, { nodeTransforms }) {
    const context = {
        root, // 转化的完整ast
        currentNode: root, // 当前转化的节点
        nodeTransforms, // 转化方法
        helpers: new Set(), // 收集导入的包
        helper(name) {
            context.helpers.add(name);
            return name;
        }
    }
    return context;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
function transform(root, options) {
    const context = createTransformContext(root, options);
    traverseNode(root, context)
}
1
2
3
4

深度遍历节点,调用transform函数

function traverseNode(node, context) { // 遍历树
    context.currentNode = node;
    const { nodeTransforms } = context;
    for (let i = 0; i < nodeTransforms.length; i++) {
        nodeTransforms[i](node, context);
    }
    switch (node.type) {
        case NodeTypes.ROOT:
        case NodeTypes.ElEMENT:
            traverseChildren(node, context); // 递归遍历子节点
            break;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
function traverseChildren(parent, context) {
    for (let i = 0; i < parent.children.length; i++) {
        const child = parent.children[i];
        traverseNode(child, context); // 遍历节点
    }
}
1
2
3
4
5
6

1.退出函数

返回一个函数等递归完成后在执行

function transformElement(node,context) {
    // 转化标签 需要处理他的子节点,所以需要等待子节点遍历完成在处理
    if(!(node.type === 1)){ // 不是元素就不必执行了
        return;
    }
    return ()=>{
        console.log('转化元素',node)
    }
}
function transformText(node,context) {
    // 处理文本 需要处理他的同级 表达式/文本,所以需要处理完同级后在处理
    if (node.type === NodeTypes.ROOT || node.type === NodeTypes.ElEMENT) {
        return ()=>{
            console.log('内部可能包含文本', node);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function traverseNode(node, context) { // 遍历树
    context.currentNode = node;
    const { nodeTransforms } = context;
    const exitFns = [];
    for (let i = 0; i < nodeTransforms.length; i++) {
        const onExit = nodeTransforms[i](node, context);
        if (onExit) exitFns.push(onExit)
    }
    switch (node.type) {
        case NodeTypes.ROOT:
        case NodeTypes.ElEMENT:
            traverseChildren(node, context);
            break;
    }
    let i = exitFns.length;
    context.currentNode = node; // 保证退出方法的context是正确的
    while (i--) {
        exitFns[i]()
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

2.文本转化

export const enum PatchFlags {
    TEXT = 1,
    CLASS = 1 << 1,
    STYLE = 1 << 2,
    PROPS = 1 << 3,
    FULL_PROPS = 1 << 4,
    HYDRATE_EVENTS = 1 << 5,
    STABLE_FRAGMENT = 1 << 6,
    KEYED_FRAGMENT = 1 << 7,
    UNKEYED_FRAGMENT = 1 << 8,
    NEED_PATCH = 1 << 9,
    DYNAMIC_SLOTS = 1 << 10,
    DEV_ROOT_FRAGMENT = 1 << 11,
    HOISTED = -1,
    BAIL = -2
}

function isText(node) {
    return node.type == NodeTypes.INTERPOLATION || node.type == NodeTypes.TEXT;
}
export function createCallExpression(callee,args) {
    return {
        type: NodeTypes.JS_CALL_EXPRESSION,
        callee,
        arguments: args
    }
}
function transformText(node, context) { // 转化文本 核心就是相邻的合并
    if (node.type === NodeTypes.ROOT || node.type === NodeTypes.ElEMENT) {
        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,
                        content: child,
                        loc: child.loc,
                        codegenNode: createCallExpression(
                            context.helper(CREATE_TEXT),
                            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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
switch (node.type) {
    case NodeTypes.ROOT:
    case NodeTypes.ElEMENT:
        traverseChildren(node, context);
    case NodeTypes.INTERPOLATION: // 给表达式新增导入方法
        context.helper(TO_DISPALY_STRING);
        break;
}
1
2
3
4
5
6
7
8

3.元素转化

function createVNodeCall(context, tag, props, children, patchFlag) {
    context.helper(CREATE_VNODE);
    return {
        type: NodeTypes.VNODE_CALL,
        tag,
        props,
        children,
        patchFlag
    }
}

function transformElement(node, context) {
    if (!(node.type === 1)) {
        return;
    }
    return () => { // 对元素的处理
        const { tag, children } = node;
        const vnodeTag = `"${tag}"`; // 标签名
        let vnodeProps;
        let vnodeChildren;
        let vnodePatchFlag;
        let patchFlag = 0;

        if (node.children.length > 0) {
            if (node.children.length === 1) {
                const child = node.children[0];
                const type = child.type;
                const hasDynamicTextChild = type === NodeTypes.INTERPOLATION || type == NodeTypes.COMPOUND_EXPRESSION;
                if (hasDynamicTextChild) {
                    patchFlag |= PatchFlags.TEXT;
                }
                vnodeChildren = child; // 一个儿子去掉数组
            } else {
                vnodeChildren = children; 
            }
        }
        if (patchFlag !== 0) {
            vnodePatchFlag = String(patchFlag);
        }
        node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren, vnodePatchFlag);
    }
}
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

4.根元素的转化

function createRootCodegen(root, context) {
    const { helper } = context
    const { children } = root
    if (children.length == 1) {
        const child = children[0];
        // 就一个元素
        const codegenNode = child.codegenNode;
        codegenNode.isBlock = true
        helper(OPEN_BLOCK)
        helper(CREATE_BLOCK);
        root.codegenNode = codegenNode; // 单个节点就转化成一个block
    } else if (children.length > 1) {  // 增加fragment
        root.codegenNode = createVNodeCall(
            context,
            helper(FRAGMENT),
            undefined,
            root.children,
            PatchFlags.STABLE_FRAGMENT
        )
    }
}
export function transform(root, options) {
    const context = createTransformContext(root, options);
    traverseNode(root, context);
    createRootCodegen(root, context)
    root.helpers = [...context.helpers]
}
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