• Babel 插件通关秘籍
  • Git 原理详解及实用指南
  • Nest 通关秘籍
  • React 通关秘籍
  • TypeScript 全面进阶指南
  • TypeScript 类型体操通关秘籍
  • 现代CSS
  • Babel 插件通关秘籍
  • Git 原理详解及实用指南
  • Nest 通关秘籍
  • React 通关秘籍
  • TypeScript 全面进阶指南
  • TypeScript 类型体操通关秘籍
  • 现代CSS
  • Babel 插件通关秘籍

    • 1.Babel 的介绍
    • 2.Babel 的编译流程
    • 3.Babel 的 AST
    • 4.Babel 的 API
    • 5.实战案例:插入函数调用参数
    • 6.JS Parser 的历史
    • 7.traverse 的 path、scope、visitor
    • 8.Generator 和 SourceMap 的奥秘
    • 9.Code- Frame 和代码高亮原理
    • 10.Babel 插件和 preset
    • 11.Babel 插件的单元测试
    • 12.Babel 的内置功能(上)
    • 13.Babel 的内置功能(下)
    • 14.Babel 配置的原理
    • 15.工具介绍:VSCode Debugger 的使用
    • 16.实战案例:自动埋点
    • 17.实战案例: 自动国际化
    • 18.实战案例:自动生成 API 文档
    • 19.实战案例: Linter
    • 20.实战案例: 类型检查
    • 21.实战案例: 压缩混淆
    • 22.实战案例: JS 解释器
    • 23.实战案例: 模块遍历
    • 24.Babel Macros
    • 25.如何调试 Babel 源码?
    • 26.手写 Babel:思路篇
    • 27.手写 Babel: parser 篇
    • 28.手写 Babel: traverse 篇
    • 29.手写 Babel: traverse -- path篇
    • 30.手写 Babel: traverse -- scope篇
    • 31.手写 Babel: generator篇
    • 32.手写 Babel: core篇
    • 33.手写 Babel: cli篇
    • 34.手写 Babel: 总结
    • 35.小册总结
    • 36.加餐:会了 babel 插件,就会写 prettier 插件

parser 的功能是把源码转成 AST,支持各种语法的 parse。

babel 的 parser 并不是从零自己实现的,而是基于 acron 做了扩展。在 《js parser 的历史》那一节大部分讲过 js parser 都是 estree 标准的,acorn 也是 estree 标准的实现,支持插件,

babel 就是基于 acorn,然后实现了 jsx、typescript、flow 等语法插件的扩展,并且修改了一些 AST,比如 Literal 扩展为了 StringLitreal、NumericLiteral 等。

所以,我们也不会从零实现 parser,也会采用基于 acron 扩下扩展的方式。

思路分析

acorn 插件的实现方式是继承之前的 Parser 返回新的 Parser,重写一些方法来做 AST 修改和扩充。

比如:

module.exports = function (Parser) {
    return class extends Parser {
        parseLiteral(...args) {
            const node = super.parseLiteral(...args);
            switch (typeof node.value) {
                case "number":
                    node.type = "NumericLiteral";
                    break;
                case "string":
                    node.type = "StringLiteral";
                    break;
            }
            return node;
        }
    };
};

这是我们之前实现过的,把 Literal 扩展为 StringLiteral、NumericLiteral 等的一个插件。

之前还实现过扩展一个 guang 的关键字的插件。

我们希望提供这种 api:

const ast = parser.parse(sourceCode, {
    plugins: ["literal", "guangKeyword"],
});

也就是根据传入的 plugins 来确定使用什么插件,然后返回扩展以后的 parser。实现方式就是保存一个插件的 map,按照传入的插件名使用就行。

代码实现

我们把插件放到不同的模块中,然后通过 map 来维护:

const syntaxPlugins = {
    literal: require("./plugins/literal"),
    guangKeyword: require("./plugins/guangKeyword"),
};

之后实现 parse 的时候,先把 options 做合并,之后根据 plugin 来依此启用不同的插件。

const defaultOptions = {
    plugins: [],
};

function parse(code, options) {
    const resolvedOptions = Object.assign({}, defaultOptions, options);

    const newParser = resolvedOptions.plugins.reduce((Parser, pluginName) => {
        let plugin = syntaxPlugins[pluginName];
        return plugin ? Parser.extend(plugin) : Parser;
    }, acorn.Parser);

    return newParser.parse(code, {
        locations: true,
    });
}

这里要指定 locations 为 true,也就是保留 AST 在源码中的位置信息,这个在生成 sourcemap 的时候会用的。

这样就实现了 parse 和语法插件功能。

总结

parser 负责把源码转成 AST,js parser 大多是符合 estree 的标准的,acorn 也是对它的实现。

acorn 支持插件,可以扩展语法,babel parser 就是 fork 了 acorn 做了扩展,我们也通过类似的方式,实现了两个语法插件,然后通过 options 启用。

当然,我们没有实现类似 jsx、typescript 这种复杂语法插件。我们的目的只是理清 babel 实现思路,而不是做一个完善的 babel。

(代码在这里,建议 git clone 下来通过 node 跑一下)

上次更新: 6/21/25, 9:42 AM
贡献者: YNight
Prev
26.手写 Babel:思路篇
Next
28.手写 Babel: traverse 篇