如何编写一个 babel 插件
编写 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 的增删改了。
增加兄弟节点可以使用 insertBefore
和 insertAfter
方法,使用 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.findParent
、 path.find
、 path.getFunctionParent
、 path.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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论