如何自定义babel插件

babel是如何工作的

babel主要处理步骤分为三个:解析、转换、生成

如何自定义babel插件_第1张图片

解析

解析步骤接受代码输出AST,该步骤分为两个阶段:词法分析、语法分析。

词法分析主要是对源代码进行分词,产生一个叫做token的数组,分割的单位是运算符、括号、数字、字符串、标点符号等可以处理的最小单元。

然后语法分析再将所有的tokens组合成一个整体,分析它们的语法和关系,最后输出AST(源代码的抽象语法树)。

分成两个阶段后,更容易的对解析步骤作优化,因为解析步骤大部分的时间都在词法分析过程中,同时也能提高可移植性。

转换

AST进行遍历,遇到需要处理的节点就操作,包括添加、移除和更新等。这个也是插件介入的主要工作内容。(babel也提供了自定义解析步骤的插件功能)

生成

最终再将AST转换回字符串形式的代码。

自定义插件开发

插件的格式

function customPlugin(babel) {
    return {
        visitor: {
            // 定义多个访问者
        }
    }
}

如上所示,一个插件就是一个普通的函数,函数接受一个babel对象(包含babel所有的api),最后返回一个包含visitor属性的对象,visitor属性中每个key都是一个ast节点的类型,值就是访问这个节点的函数。

每个访问者函数都会接受两个参数:pathstate。path对象表示两个节点之间连接的对象,例如:

{
  "parent": {
    "type": "FunctionDeclaration",
    "id": {...},
    ....
  },
  "node": {
    "type": "Identifier",
    "name": "square"
  }
}

state对象包含一些额外的状态信息,例如可以从state.opts中取出为插件配置的特定选项,甚至可以取出path对象,具体内容可以自己打印看看。

下面开发一个小插件,为代码中的console.log添加调用位置的信息。

module.exports = function (babel) {
    const t = babel.types
    
    return {
        name: 'custom-babel-plugin-demo',
        visitor: {
            CallExpression(path) {
                const obj = path.node.callee.object
                const prop = path.node.callee.property
                const arguments = path.node.arguments

                if (t.isIdentifier(obj) && t.isIdentifier(prop) && obj.name === 'console' && prop.name === 'log') {
                    const location = `---trace: line ${path.node.loc.start.line}, column ${path.node.loc.start.column}---`;
                    arguments.push(t.stringLiteral(location))
                }
            }
        }
    }
}

首先你需要知道你要访问节点的类型,如果不清楚,可以到这个网站查看。babel.types则提供了类似lodash的工具库功能(api)。

最后再测试下插件功能是否正常:

const { transform } = require('@babel/core')
const options = {
    plugins: [ ['./src/index.js', {
        option1: true,
        options2: false
    }] ]
}

const code = `
    const str1 = 'hello'
    console.log(str1)
    const str2 = 'babel'
    console.log(str2)
    const str3 = 'plugin'
    console.log(str3)
`

transform(code, options, function(err, result) {
    console.log(result.code)
})

得到输出:

const str1 = 'hello';
console.log(str1, "---trace: line 3, column 4---");
const str2 = 'babel';
console.log(str2, "---trace: line 5, column 4---");
const str3 = 'plugin';
console.log(str3, "---trace: line 7, column 4---");

如果你要编写良好的测试用例,可以借助babel-plugin-tester库。

你可能感兴趣的:(【工程化】)