如何编写一个babel插件

2019-07-24 14:16:58 浏览数 (1)

编写babel插件时最常使用的是库 @babel/core@babel/types

babel插件需要返回一个function,function内返回visitor。

代码语言:javascript复制
module.exports = function() {
return {
    visitor: {
    // plugin code
    }
}
}

visitor里我们可以编写针对各类 AST type 的处理方式,从而达到修改AST的效果,关于 babel 转换得到的各类 AST 究竟有哪些类型,可以在 这里 看到。

visitor 这个对象的key就是ast的类型,值就是处理ast的函数。

例如,遇到全等号的时候我们将全等号的两边的值换掉,可以这样写。

代码语言:javascript复制
const babel = require('@babel/core');
const t = require('@babel/types');

const visitor = {
  BinaryExpression(path) {
    if (path.node.operator !== "===") return;
    path.node.left = t.identifier("huruji");
    path.node.right = t.identifier('grey');
  }
}

module.exports = function (babel) {
  return {
    visitor,
  }
}

babel插件通过options来配置使用,如:

代码语言:javascript复制
{
   plugins: [
      ["my-plugin", {
         "opt1": true,
         "opt2": false
      }]
   ]
}

这里的options参数会传给我们visitor的每个处理函数的第二个参数state的opts

代码语言:javascript复制
const visitor = {
  BinaryExpression(path, state) {
    if(state) {
       console.log(state.opts);
      // { opt1: true, opt2: false }
    }
  }
}

module.exports = function (babel) {
  return {
    visitor,
  }
}

通过这个参数我们就可以更好地配置我们的 babel 插件。

插件基本的编写已经明朗,接下来看看插件最核心的功能,就是修改 AST,也就是对AST进行增删改。

在增删改前我们首先需要保证我们是真正处理了我们想要处理的代码,在AST中也就是各个node节点,我们可以通过 @babel/types 来判断,同时通过node节点属性来辅助我们判断

代码语言:javascript复制
if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {
// plugin code
}
代码语言:javascript复制
BinaryExpression(path) {
  if (t.isIdentifier(path.node.left, { name: "n" })) {
    // ...
  }
}

这样的检查功能上等价于

代码语言:javascript复制
BinaryExpression(path) {
  if (
    path.node.left != null &&
    path.node.left.type === "Identifier" &&
    path.node.left.name === "n"
  ) {
    // ...
  }
}

接下来就是对于AST的增删改了。

增加兄弟节点可以使用 insertBeforeinsertAfter 方法,使用babel插件手册的例子:

代码语言:javascript复制
FunctionDeclaration(path) {
  path.insertBefore(t.expressionStatement(t.stringLiteral("Because I'm easy come, easy go.")));
  path.insertAfter(t.expressionStatement(t.stringLiteral("A little high, little low.")));
}

删除一个节点使用 remove 方法即可:

代码语言:javascript复制
path.move();

替换节点使用 replaceWith 方法,依旧使用别人的例子:

代码语言:javascript复制
BinaryExpression(path) {
  path.parentPath.replaceWith(
    t.expressionStatement(t.stringLiteral("Anyway the wind blows, doesn't really matter to me, to me."))
  );
}

有时候需要对父级节点做处理,可以通过 path.parentPath 来访问父级节点

代码语言:javascript复制
path.parentPath.remove();

同时还有其他方法 path.findParentpath.findpath.getFunctionParentpath.getStatementParent 等。

有时候我们需要判断或者获取兄弟节点同样也行,比如:

  • path.inList 判断是否有兄弟节点
  • path.getSibling(index) 获取指定的节点

实践以下,下面可以看一个大佬写的插件:

代码语言:javascript复制
var babel = require('babel-core');
var t = require('babel-types');

const visitor = {
  BinaryExpression(path) {
    const node = path.node;
    let result;
    // 判断表达式两边,是否都是数字
    if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {
      // 根据不同的操作符作运算
      switch (node.operator) {
        case " ":
          result = node.left.value   node.right.value;
          break
        case "-":
          result = node.left.value - node.right.value;
          break;
        case "*":
          result =  node.left.value * node.right.value;
          break;
        case "/":
          result =  node.left.value / node.right.value;
          break;
        case "**":
          let i = node.right.value;
          while (--i) {
            result = result || node.left.value;
            result =  result * node.left.value;
          }
          break;
        default:
      }
    }

    // 如果上面的运算有结果的话
    if (result !== undefined) {
      // 把表达式节点替换成number字面量
      path.replaceWith(t.numericLiteral(result));
    }
  }
};

module.exports = function (babel) {
  return {
    visitor
  };
}

0 人点赞