Babel 插件:30分钟从入门到实战

网站建设4年前发布
93 0 0

Babel 是一个 source to source(源码到源码)的 JavaScript 编译器,简单来说,你为 Babel 提供一些 JavaScript 代码,Babel 可以更改这些代码,然后返回给你新生成的代码。Babel 主要用于将 ECMAScript 2015+ 代码转换为能够向后兼容的 JavaScript 版本。Babel 使用插件系统进行代码转换,因此任何人都可以为 babel 编写自己的转换插件,以支持实现广泛的功能。,Babel 的编译流程主要分为三个部分:解析(parse),转换(transform),生成(generate)。,解析 Parse,将源码转换成抽象语法树(AST, Abstract Syntax Tree)。,比如:,以上的程序可以被转换成类似这样的抽象语法树:,转换 Transform,转换阶段接受一个 AST 并遍历它,在遍历的过程中对树的节点进行增删改。这也是运行 Babel 插件的阶段。,生成 Generate,将经过一系列转换之后的 AST 转换成字符串形式的代码,同时还会创建 sourcemap。,对于每一个阶段,Babel 都提供了一些工具库:,以上提及的包都是 @babel/core 的 dependencies,所以只需要安装 @babel/core 就能访问到它们。,除了上面提到的工具库,以下工具库也比较常用:,本文不会深入讨论它们的详细用法,当你在编写插件的时候,可以根据功能需求找到它们,我们后文也会涉及到部分用法。,接下来让我们开始认识 Babel 插件吧。,babel 插件是一个简单的函数,它必须返回一个匹配以下接口的对象。如果 Babel 发现未知属性,它将抛出错误。,20230306011200c9a9e4c950ef19ee3ad799f5464a3fe66b7b7b170,以下是一个简单的插件示例:,Babel 插件接受 3 个参数:,返回的对象有 name、manipulateOptions、pre、visitor、post、inherits 等属性:,我们上面提到了一些陌生的概念:visitor、path、state,现在让我们一起来认识它们:,visitor 访问者,这个名字来源于设计模式中的访问者模式(https://en.wikipedia.org/wiki/Visitor_pattern)。简单的说它就是一个对象,指定了在遍历 AST 过程中,访问指定节点时应该被调用的方法。,假如我们有这样一段程序:,这段代码对应的 AST 如下:,当我们对这颗 AST 进行深度优先遍历时,每次访问 StringLiteral 都会调用 visitor.StringLiteral。,当 visitor.StringLiteral 是一个函数时,它将在向下遍历的过程中被调用(即进入阶段)。当 visitor.StringLiteral 是一个对象时({ enter(path, state) {}, exit(path, state) {} }),visitor.StringLiteral.enter 将在向下遍历的过程中被调用(进入阶段),visitor.StringLiteral.exit 将在向上遍历的过程中被调用(退出阶段)。,Path 路径,Path 用于表示两个节点之间连接的对象,这是一个可操作和访问的巨大可变对象。,Path 之间的关系如图所示:,除了能在 Path 对象上访问到当前 AST 节点、父级 AST 节点、父级 Path 对象,还能访问到添加、更新、移动和删除节点等其他方法,这些方法提高了我们对 AST 增删改的效率。,State 状态,在实际编写插件的过程中,某一类型节点的处理可能需要依赖其他类型节点的处理结果,但由于 visitor 属性之间互不关联,因此需要 state 帮助我们在不同的 visitor 之间传递状态。,一种处理方式是使用递归,并将状态往下层传递:,另外一种传递状态的办法是将状态直接设置到 this 上,Babel 会给 visitor 上的每个方法绑定 this。在 Babel 插件中,this 通常会被用于传递状态:从 pre 到 visitor 再到 post。,Babel 没有完整的文档讲解所有的 api,因此下面会列举一些可能还算常用的 api(并不是所有,主要是 path 和 types 上的方法或属性),我们并不需要全部背下来,在你需要用的时候,能找到对应的方法即可。,你可以通过 babel 的 typescript 类型定义找到以下列举的属性和方法,还可以通过 Babel Handbook 找到它们的具体使用方法。,查询,遍历,
,
,
,在 @babel/types 的类型定义中,可以找到所有 AST 节点类型。我们不需要记住所有节点类型,社区内有一个 AST 可视化工具能够帮助我们分析 AST:axtexplorer.net。,在这个网站的左侧,可以输入我们想要分析的代码,在右侧会自动生成对应的 AST。当我们在左侧代码区域点击某一个节点,比如函数名 foo,右侧 AST 会自动跳转到对应的 Identifier AST 节点,并高亮展示。,我们还可以修改要 parse 的语言、使用的 parser、parser 参数等。,现在让我们来实现一个简单的插件吧!以下是插件需要实现的功能:,因此,对于以下输入:,应该输出以下代码:,通过 https://astexplorer.net/,我们发现代码里的字符串在 AST 上对应的节点叫做 StringLiteral,如果想要拿到代码里所有的字符串并且统计每种字符串的数量,就需要遍历 StringLiteral 节点。,我们需要一个对象用于存储所有 StringLiteral,key 是 StringLiteral 节点的 value 属性值,value 是一个数组,用于存储拥有相同 path.node.value 的所有 path 对象,最后把这个对象存到 state 对象上,以便于在遍历结束时能统计相同字符串的重复次数,从而可以判断哪些节点需要被替换为一个标识符。,通过 https://astexplorer.net/,我们发现如果想要往顶层作用域中插入一个变量,其实就是往 Program 节点的 body 上插入 AST 节点。Program 节点也是 AST 的顶层节点,在遍历过程的退出阶段,Program 节点是最后一个被处理的,因此我们需要做的事情是:根据收集到的字符串字面量,分别创建一个位于顶层作用域的变量,并将它们统一插入到 Program 的 body 中,同时将代码中的字符串替换为对应的变量。,2023030601120078423b467e836457ec5801ab651da4c5e31853240,测试 Babel 插件有三种常用的方法:,我们一般使用第二种方法,配合 babel-plugin-tester 可以很好地帮助我们完成测试工作。配合 babel-plugin-tester,我们可以对比输入输出的字符串、文件、快照。,本文将以快照测试为例,以下是测试我们插件的示例代码:,当我们运行 jest 后(更多关于 jest 的介绍,可以查看 jest 官方文档https://jestjs.io/docs/getting-started),会生成一个 snapshots 目录:,202303060113257675b9e062d0afcd39d665150ef9290a1ae5e5786,有了快照以后,每次迭代插件都可以跑一下单测以快速检查功能是否正常。快照的更新也很简单,只需要执行 jest --updateSnapshot。,如果想要使用 Babel 插件,需要在配置文件里添加 plugins 选项,plugins 选项接受一个数组,值为字符串或者数组。以下是一些例子:,Babel 对插件名字的格式有一定的要求,比如最好包含 babel-plugin,如果不包含的话也会自动补充。以下是 Babel 插件名字的自动补全规则:,2023030601132532b11ce57e6ca266480170c9f00569176bd191985,到这里,Babel 插件的学习就告一段落了,如果大家想继续深入学习 Babel 插件,可以访问 Babel 的仓库(https://github.com/babel/babel/tree/main/packages)这是一个 monorepo,里面包含了很多真实的插件,通过阅读这些插件,相信你一定能对 Babel 插件有更深入的理解!,Babel plugin handbook:https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md,Babel 官方文档:https://babeljs.io/docs/en/,Babel 插件通关秘籍:https://juejin.cn/book/6946117847848321055

© 版权声明

相关文章