不管是业务开发,还是做提效工具,基本都离不开编译,而市面上的文章大多都停留在 preset 使用上,并没有系统的深入讲解 babel 原理的教程,而官方的插件文档内容太少,也没有具体案例,所以我才想写这本小册。
标题定为“通关秘籍”,这也是我写小册的初衷,一定要把 babel 讲透。我觉得任何一个技术只有学习到一定的程度才是有意义的,让它化为你思想的一部分,而不是只是使用。只是使用没啥竞争力。
怎么通关呢?我这样设计了小册的内容:
先讲 babel 是什么,能做什么,然后讲 babel 编译流程,之后介绍下 AST 和 astexperer.net 这个工具,然后介绍各个包的 api。
前几节过了一遍这些内容之后,需要先来个实战案例感受下,所以后面是“插入函数调用参数”的一个案例,功能比较简单,但是却把 api、AST、编译流程等做了串联。
之后的章节是深入讲解每一部分了,包括 parser 的历史,梳理清楚各种 AST 的关联,acorn parser 是如何扩展的;visitor 的设计模式,path 和 path.scope 的各种 api 的讲解;generate 的原理和 sourcemap 的原理、code-frame 是怎么拼接的,控制台是如何实现代码高亮的;
这些深入的内容难度大一些,确实深入掌握 babel 必须要走的路。
之后介绍了 babel 插件和 preset 怎么写,单元测试怎么写,为后面实战案例做铺垫。
大多数人还是使用 babel 提供的 preset 等,于是花了两节介绍了插件的分类(transform、syntax 插件), babel 从babel6 的 preset-es20xx 到 babel7 的 preset-env 都做了啥改变,为什么要这样做,都解决了什么问题。为什么要有 transform-runtime 插件,还介绍了 babel8 的 polyfill provider 等。
babel 主要的功能都是基于插件提供了一层 preset,这是一种门面模式,简化开发者使用 babel 的成本。我们使用 babel 的话,学习下 preset-env 即可。
后面就是各种实战案例了,其实这些案例小册里实现比较简单,更多是为了理清思路,但这些都是真实的很多大厂在用的方案,包括自动函数插桩、自动国际化、自动生成文档等。这些思路是通用思路,学会了很容易可以用在项目里,成为简历的亮点和拿绩效的抓手。
之后我们实现了下 linter、type chekcer、压缩混淆,这些都是分析和转换代码的工具,目的是为了让大家理解 eslint、typescript、terser 的实现原理。同样,目的是为了打通思路,案例并不是完善的实现。
模块遍历器则是打包工具的基础,打包工具离不开模块的依赖分析和遍历,遍历之后构建依赖图,之后对每个依赖进行编译,最后合并到一块来输出目标文件。遍历 AST 是要确定什么属性,遍历模块则是要解析 require,然后处理路径,这两者也是有关联的。这一节的设计是为了帮助大家了解下打包工具用编译技术做了啥事情。
js 解释器则是为了让大家理解 js 引擎的原理,我们实现了一个 tree walker 解释器,是用 js 实现的,如果用 c++ 按照同样的思路实现,就可以是一个小型的 v8 或者 quickjs 这种引擎了。当然,现在大多 js 引擎都是编译成字节码再解释,而不是直接解释 AST,但只是多了一层转换,解释的本质不变。
babel macros 是这几年的一个概念,主要是可以在代码里面插入 macro 来配置转换哪里的代码,这个是基于 babel-macro-plugin 实现的。相比 babel plugin 只不过是从全局的 visitor 搜索到具体节点,变成了从具体节点向上找,和 babel 插件只是形式上不一样,但是修改 AST 的 api 都是一样的,会了 babel 插件的话很容易也就掌握 macro 了。
说要通关,不实现一个 babel 怎么算通关呢?
所以后面我们实现了 babel 的编译内核,包括 parser、traverse、generator,以及 types、template 还有 core、cli,这样就把编译流程和 api、cli 两种接口的实现思路都打通了。
babel 的各种 preset 只不过是基于插件的封装,这些东西是变化的,比如preset-env 通过 browsewrslist 来指定运行环境,然后基于插件和运行环境版本的对应关系的数据库来动态引入插件,最终做的事情还是插件做的,基于的内核都是一样的,只不过 preset 的封装使得 babel 更易用了。
babel 的preset 是一直变的,但是编译内核和插件基本没啥变化,理清变与不变,抓住本质来学。
helper 是插件的工具函数,runtime 包包括 helper、regenerator、corejs 这些是 babel 做 polyfill 需要的,这些都是第三方实现的,babel 是借助了它们来实现 polyfill 功能。babel8 还支持了 polyfill 的切换,也就是 polyfill provider 功能。
调试 babel 插件最好的方式永远是 debugger,但这个技能大部分人不会用,所以单独加了一节来介绍 vscode debugger 的原理和使用,帮助大家把这个调试利器用起来。
小册写了比较长的时间,因为比较浅的掌握是容易的,但是想深入就没那么容易了,基本都要看源码和大量的实验。但是就像开头说的那样,任何一门技术只有学到一定的深度,化为自己思想的一部分才是意义比较大的。浅尝辄止其实用处不大。
而且很多技术都是通的,比如 browserslist 来动态指定插件的思路,在 postcss 中也用到了,而 sourcemap 的原理各种编译打包工具都一样,当你能实现sourcemap 了,那掌握各种工具的 sourcemap 使用易如反掌。
前端的编译工具都是源码转源码,更直白点说都是字符串转字符串,只不过中间要理解代码需要 parse 成 AST,把对字符串的操作转为对 AST 的操作,而不是直接用正则修改字符串,这种转换其实各种工具都差不多,比如 eslint、terser、typescript、postcss、stylelint、swc 等等,小册中也实现了一下,大家应该能感受到。所以,学习完 babel 插件之后,再去学别的工具的插件也会简单很多,只是学习下不同的封装形式和 api 即可。
小册到这里就算结束了,希望像这本小册的标题一样,帮助大家真正的通关 babel !