Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何编写一个babel插件 #55

Open
huruji opened this issue Nov 18, 2018 · 0 comments
Open

如何编写一个babel插件 #55

huruji opened this issue Nov 18, 2018 · 0 comments

Comments

@huruji
Copy link
Owner

huruji commented Nov 18, 2018

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

refs:

https://github.com/jamiebuilds/babel-handbook

面试官: 你了解过Babel吗?写过Babel插件吗? 答: 没有。卒

理解 Babel 插件

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant