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); // 调用转化方法进行转化
  }
  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);
}

退出函数

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

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);
    };
  }
}
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]();
  }
}

转化表达式

runtimeHelpers.ts

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

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

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

转化文本元素

文本元素的转化我们需要将多个文本连接起来,如果是动态文本添加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), // 创建表达式调用
          };
        }
      }
    };
  }
}

runtimeHelpers.ts

export const CREATE_TEXT = Symbol(`createTextVNode`);
export const helperNameMap = {
  [CREATE_TEXT]: `createTextVNode`,
};

ast.ts

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

转化元素

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
      );
    };
  }
}

runtimeHelpers.ts

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

转化根节点

转化根节点需要添加 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`,
};
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
  }
}
export function transform(root) {
  // 创建转化的上下文, 记录转化方法及当前转化节点
  let context = createTransformContext(root);
  // 递归遍历
  traverseNode(root, context);
  createRootCodegen(root, context); // 生成根节点的codegen
  root.helpers = [...context.helpers.keys()];
}

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

Released under the MIT License.