如何写 babel 插件

认识 babel

大家知道 babel 是一款语法转换器,有了它我们才可以把es6的代码转换成低版本的 js 从而兼容低版本的浏览器。但借助babel能做到的事情远不止这些,比如我认为react最有难度的jsx解析,就是通过babel处理的。

babel api

babel 官网我们可以看到提供了:

  • @babel/parser 可以把代码解析成 ast
  • @babel/generator 把 ast 转换成代码
  • @babel/traverse 遍历/修改 ast
  • @babel/types 该包含手动构建 AST 和检查 AST 节点类型的方法
  • @babel/core 几乎整合了所有 api

这里直接使用@babel/core:


import babel from "@babel/core"

const code = `
    const a = 1
    const x = 2
`;

// step 1 解析得到 ast
const ast = babel.parse(code,{
    plugins:[], // jsx typescript 等等解析代码需要的插件  
    sourceType:'module' // 解析模式 可以是 script 脚本 module 模块 或者 unambiguous 让 babel 自己去猜是什么
})

// step 2 修改  
// path.node 即遍历到的所有 ast 节点  
// 这个 path 的原型上提供了一系列的检测方法 可以通过查看源码 或者 debugger 上点开 __proto__ 查阅  
babel.traverse(ast, {
    enter(path) {
      if (path.isIdentifier({ name: "a" })) { // 是否是一个 Identifier 而且 name 是 a
        path.node.name = "b";
      }
      if(path.isVariableDeclaration({kind:'const',start:5})){ // 是否是一个 VariableDeclaration 而且 kind 是 const 节点开始与字符串第5个字符  
        path.node.kind = 'let'
      }
    },
});

// step3  ast 转换为 code  
babel.transformFromAst(ast,code,{},function(err,res){
    const { code, map, ast } = res
    console.log(code) // `let b = 1;const x = 2;` // 修改成功
})

其中 babel.traverse 也可以改写为:


babel.traverse(ast, {
    // enter(path) {
    //   if (path.isIdentifier({ name: "a" })) {
    //     path.node.name = "b";
    //   }
      
    //   if(path.isVariableDeclaration({kind:'const',start:5})){
    //     path.node.kind = 'let'
    //   }
    // },
    Identifier(path){
        if(path.node.name==='a'){
            path.node.name = "b";
        }
    },
    VariableDeclaration(path){
        if(path.node.kind==='const' && path.node.start === 5){
            path.node.kind = 'let'
        }
    }
});

关于 path 以及下面插件用到的 vistor babel 手册有非常详细的介绍

babel 插件示例


// package.json
{
    "scripts": {
        "build": "babel src -d dist",
        "debug": "node --inspect node_modules/@babel/cli/bin/babel src -d dist",
    },
    "devDependencies": {
        "@babel/cli": "^7.17.6",
        "@babel/core": "^7.17.9",
    }
}

// .babelrc
"plugins": ["./plugin/babel-plugin-test"]

// plugin/babel-plugin-test.js
module.exports = (babel) => {
    return {
        visitor: {
            Identifier(path, state) {
                if(path.node.name==='a'){
                    path.node.name = "b";
                }
            },
            VariableDeclaration(path,state){
                if(path.node.kind==='const'){
                    path.node.kind = 'let'
                }
            }
        }
    }
};

// src/index.js
const a = 1

// then yarn build