Babel进阶:如何自定义插件?

发布于:2025-05-12 ⋅ 阅读:(6) ⋅ 点赞:(0)

Babel 是一个非常流行的 JavaScript 编译器,下面我们将从零到一编写一个 babel 箭头函数语法转换插件,掌握 babel 插件设计思路与编写规范,需求很简单就是将箭头函数转换为普通函数。

const test = ()=>{
    console.log("Hello World!") 
}

如上所示,通过 babel 编译后,生成如下代码:

const test = function(){
    console.log("Hello World!") 
}

1. 自定义插件

1.1. 使用types辅助修改

首先定义箭头函数插件:plugin-arrow-functions.js

const { types: t } = require("@babel/core");

module.exports = function () {
    return {
        visitor: {
            ArrowFunctionExpression(path) {
                // 我当前访问到的箭头函数节点
                const { node } = path;
                const body = t.isBlockStatement(node.body)
                    ? node.body
                    : t.blockStatement([t.returnStatement(node.body)]);
                const functionExpression = t.functionExpression(
                    null,
                    node.params,
                    body,
                    node.async
                );
                console.log(
                    "🚀 ~ ArrowFunctionExpression ~ functionExpression:",
                    functionExpression
                );

                // // 假设箭头函数表达式直接返回值,需要特殊处理
                // if (!t.isBlockStatement(node.body)) {
                //     functionExpression.body = t.blockStatement([
                //       t.returnStatement(node.body),
                //     ]);
                // }

                path.replaceWith(functionExpression);
            }
        }
    }
}

通过 types 辅助对象,可以判断当前访问 ast 节点的类型以及对其类型进行加工。

然后在 babel.config.js中定义该插件。

module.exports = {
	plugins: ["./plugins/arrow-function.js"],
};

最后修改 package.json,添加构建脚本命令。

"scripts": {
    "build": "babel src -d dist"
}

最后执行pnpm build 执行 babel 编译,在 dist 中输出目标资源,经验证符合要求。

1.2. 使用@babel/helper-plugin-utils

这个方案只是在 visitor 时有所差异,我们可以这样处理:

const { declare } = require("@babel/helper-plugin-utils");
module.exports = declare((api, options) => {
    const noNewArrows = api.assumption("noNewArrows") ?? options.spec;
    return {
        name: "transform-arrow-functions",
        visitor: {
            ArrowFunctionExpression(path) {
                if (!path.isArrowFunctionExpression()) return;
                path.arrowFunctionToExpression({
                    allowInsertArrow: false,
                    noNewArrows,
                    specCompliant: !noNewArrows,
                })

            }
        }
    }
})

2. Babel编译过程

Babel 最核心的流程我们可以概括为上图所示,parser => traverse => generator

我们以一个问题来切入:

const a = 1;
let b = 2;
var c = "aiguangyuan";

请将以上代码,转为如下所示 ES5 代码:

var a = 1;
var b = 2;
var c = "aiguangyuan";

2.1. parser

借助@babel/parser将源代码转为 ast 结构,即:

const parser = require('@babel/parser');
const ast = parser.parse(code);

2.2. traverse

借助 @babel/traverse将 ast 进行处理生成新 ast,即:

const traverse = require('@babel/traverse');

// 访问者对象
const visitor = {
    // 遍历声明表达式
    // 可以直接通过节点的类型操作AST节点。
    VariableDeclaration(path) {
        // 替换
        if (path.node.kind === 'var') {
            path.node.kind = 'let';
        }
    },
};
traverse.default(ast, visitor);

或者:

const traverse = require('@babel/traverse');

traverse.default(ast, {
    enter(path) {
        if (path.isVariableDeclaration({ kind: 'var' })) {
            path.node.kind = 'let';
        }
    },
});

2.3. generator

借助@babel/generator根据目标 ast 生成目标代码,示例:

const generator = require('@babel/generator');

generator.default(ast, {}, code).code;

2.4. 完整过程

const parser = require('@babel/parser');
const traverse = require('@babel/traverse');
const generator = require('@babel/generator');

const trans = (code) => {
    const ast = parser.parse(code);
    // 访问者对象
    const visitor = {
        // 遍历声明表达式
        // 可以直接通过节点的类型操作AST节点。
        VariableDeclaration(path) {
            // 替换
            if (path.node.kind == 'var') {
                path.node.kind = 'let';
            }
        },
    };

    traverse.default(ast, visitor);
    // 生成代码
    const newCode = generator.default(ast, {}, code).code;
    return newCode;
};

const code = `const a = 1
let b = 2
var c = 3`;
console.log(trans(code));

网站公告

今日签到

点亮在社区的每一天
去签到