如何编写一个 babel 插件

发布于 2022-06-21 15:00:50 字数 4754 浏览 1008 评论 0

1523878872421

编写 babel 插件时最常使用的是库 @babel/core@babel/types,babel 插件需要返回一个 function,function 内返回 visitor。

module.exports = function() {
return {
    visitor: {
    // plugin code
    }
}
}

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

visitor 这个对象的 key 就是 ast 的类型,值就是处理 ast 的函数。例如,遇到全等号的时候我们将全等号的两边的值换掉,可以这样写。

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 来配置使用,如:

{
   plugins: [
      ["my-plugin", {
         "opt1": true,
         "opt2": false
      }]
   ]
}

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

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 节点属性来辅助我们判断

if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {
// plugin code
}
BinaryExpression(path) {
  if (t.isIdentifier(path.node.left, { name: "n" })) {
    // ...
  }
}

这样的检查功能上等价于

BinaryExpression(path) {
  if (
    path.node.left != null &&
    path.node.left.type === "Identifier" &&
    path.node.left.name === "n"
  ) {
    // ...
  }
}

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

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

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 方法即可:

path.move();

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

BinaryExpression(path) {
  path.parentPath.replaceWith(
    t.expressionStatement(t.stringLiteral("Anyway the wind blows, doesn't really matter to me, to me."))
  );
}

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

path.parentPath.remove();

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

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

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

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

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

我自己动手实践一下,实现了一个去掉调试代码的 babel 插件, 地址在这里 babel-plugin-no-debugging

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

自找没趣

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

遂心如意

文章 0 评论 0

5513090242

文章 0 评论 0

巷雨优美回忆

文章 0 评论 0

junpengz2000

文章 0 评论 0

13郎

文章 0 评论 0

qq_xU4RDg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文