36-实现transform

74 阅读1分钟

前言

transformcompiler 中很重要的一个环境,当我们修改content或者dom时,都会触发 transform

用例

import { baseParse } from "../src/parse";
import { transform } from "../src/transform";
describe("Compiler: transform", () => {
  test("should change text content", () => {
    const ast = baseParse("<div>hi</div>");
    transform(ast);
    expect(ast.children[0].children[0].content).toEqual("hi mini-vue");
  });
});

实现

用例上需要把 hi 替换成 hi mini-vue,我们可以做深度查询,遍历ast树,找到对应的text节点,修改它的content即可。

/*
 * @Author: Lin ZeFan
 * @Date: 2022-04-16 22:33:45
 * @LastEditTime: 2022-04-16 22:41:37
 * @LastEditors: Lin ZeFan
 * @Description:
 * @FilePath: \mini-vue3\src\compiler-core\src\transform.ts
 *
 */

import { NodeType } from "./ast";

export function transform(root) {
  traverseNode(root);
}

function traverseNode(node) {
  let { children, type } = node;

  if (type === NodeType.TEXT) {
    node.content = "hi mini-vue";
  }
  if (children) {
    for (let i = 0; i < children.length; i++) {
      traverseNode(children[i]);
    }
  }
}

上面简单实现了,很多地方不够完善,例如判断的类型,只做了text的判断,并且赋值是固定的,不是动态的,逻辑内部的耦合性也高,transform 内部程序都按规定流程走。

但是我们设计程序是肯定要通用性最佳的,不可能要将特定的处理写在程序中。

所以我们就可以换一种思路,通过外部提供处理程序,内部再调用外部传入的处理程序。我们称之为插件(plugin)。

设计为plugin

修改用例

// 改写测试
test('should change text content', () => {
  const ast = baseParse('<div>hi</div>')
  // 外部提供处理
   const transformText = node => {
     if (node.type === NodeType.TEXT) {
       node.content += ' mini-vue'
     }
   }
  // 通过 options 传入内部,内部再调用
  transform(ast, {
    nodeTransforms: [transformText],
  })
  expect(ast.children[0].children[0].content).toEqual('hi mini-vue')
})

修改transfrom

/*
 * @Author: Lin ZeFan
 * @Date: 2022-04-16 22:33:45
 * @LastEditTime: 2022-04-16 23:08:13
 * @LastEditors: Lin ZeFan
 * @Description:
 * @FilePath: \mini-vue3\src\compiler-core\src\transform.ts
 *
 */

export function transform(root, options) {
  const context = createTransformContext(root, options);
  traverseNode(root, context);
}

function createTransformContext(root: any, options: any) {
  const { nodeTransforms = [] } = options;
  const context = {
    root,
    nodeTransforms: nodeTransforms || [],
  };
  return context;
}

function traverseNode(node, context) {
  const { nodeTransforms } = context;

  for (let index = 0; index < nodeTransforms.length; index++) {
    const transform = nodeTransforms[index];
    transform(node);
  }

  traverseChildren(node, context);
}

function traverseChildren(node: any, context: any) {
  const { children } = node;
  if (children) {
    for (let i = 0; i < children.length; i++) {
      traverseNode(children[i], context);
    }
  }
}