Babel 历史和原理
title: Babel 历史和原理 id: 2439d67e9f2c71d13d0eb7f012fe2ba6 tags: [] date: 2000/01/01 00:00:00 updated: 2023/03/04 19:29:12 isPublic: true --#|[分隔]|#--
Babel 历史和原理
Babel文档:https://babeljs.io/docs/en/。
只能说,Babel 的历史太厚重了。
之前的配置中,进行编译使用的是 babel-preset-es2015、babel-preset-stage-2、transform-object-rest-spread 、 @babel/polyfill 等包,现在已经不是这些了,有了新的配置方案,见后面。
以下历史参考自 https://juejin.cn/post/6976501655302832159。
core-js
core-js 是 JavaScript 的模块化标准库,包括了 ECMAScript 新 api 的兼容实现。它和 babel 高度集成,是 babel 解决新特性在浏览器中兼容问题的核心依赖。
目前 core-js 的版本是 3.x,与 core-js@2 相比不仅在本身的架构上有重大调整,还对 babel 中的一些插件有重大影响。
这里为了后面方便理解,先说两个名字,一个叫做污染包,一个叫做清洁包,后面马上就会用到。
总之,阅读了下面 的 core-js@2 和 core-js@3 的说明后,会知道 core-js@2 和 core-js@3 各有一个污染包和一个清洁包。
具体污染包和清洁包的实现具体实例,见后面的举例
现阶段能够编译 api 的插件一共有三个,见下:
@babel/polyfill 只能使用 core-js@2 的污染包(@babel/polyfill 包已被 Babel 废弃,不推荐使用)。
@babel/preset-env,根据配置,能使用 core-js@2 或 core-js@3 的污染包。
@babel/plugin-transform-runtime 使用 @babel/runtime-corejs2 或 @babel/runtime-corejs3:
@babel/runtime-corejs2 使用 core-js@2 的清洁包。
@babel/runtime-corejs3 使用 core-js@3 的清洁包。
core-js@2
core-js@2 被 @babel/polyfill、@babel/preset-env 和 @babel/runtime-corejs2 (最终会由 @babel/plugin-transform-runtime 使用) 引入,来进行新 api 的兼容处理。
其中有两个核心的模块,其实就是一个是污染包,一个清洁包:
modules:污染全局的 polyfill 模块,供 @babel/polyfill 和 @babel/preset-env 引入。
这个包我叫它 污染包,它主要是采用在全局和实例上添加 api 的方式解决兼容性问题,比如要兼容 Array 的 includes 方法,就直接给 Array 的原型链添加这个方法,会修改原型链。
library:不污染全局的 runtime 模块,供 @babel/runtime-corejs2 引入。
这个包我叫它 清洁包,它主要是采用模拟替换api的方式解决兼容性问题,比如发现代码中使用了 [1, 2].includes(2) 方法,就引入一个方法叫做 _includes,然后把代码部分替换为 _includes.call([1, 2], 2) 的方式,不污染原型链。
core-js@3
core-js@3 放弃了对 @babel/polyfill 的支持,被 @babel/preset-env 和 @babel/runtime-corejs3(最终会由 @babel/plugin-transform-runtime 使用) 引入来进行新api的兼容处理。
由于 core-js@2 包的体积太大(约2M),并且有很多重复的文件被引用,所以 core-js@3 对包进行拆分,其中两个核心,仍然一个是污染包,一个清洁包:
core-js:污染全局的 polyfill 包,供 @babel/preset-env 引入,等价于 core-js@2/modules,这个包我叫它污染包;
core-js-pure:不污染全局的 runtime 包,供 @babel/runtime-corejs3 引入,等价于core-js@2/library,这个包我叫它清洁包。
core-js@2分支已经冻结,不会再添加新特性,新特性都会添加到 core-js@3。
为了可以使用更多的新特性,建议大家使用 core-js@3,但我们其实不会直接使用 core-js@3,我们需要使用的是那些引入了 core-js@3 的插件和配置。
关于 core-js 的内容大家先了解这么多,先有个印象,大家只需要记住一点:corejs 才是 api 兼容实现的提供者!
污染包和清洁包的实际效果举例
使用污染包的编译前后,编译后的使用 require 引入的 js,就是给 Array 添加了 from 这个静态方法:
使用清洁包的编译前后,可见对应编译前的 Array.form 方法,编译后是引入了一个 _from 来替换使用。
@babel/polyfill
此包已被官方弃用,虽然仍然可以使用,但不建议使用,后面有完全取代的方案。
@babel/polyfill 是一个运行时包,主要是通过核心依赖 core-js@2 来完成对应浏览器不支持的新的全局和实例 api 的添加。
而在升级到 core-js@3 后,如果还要保留 @babel/polyfill 的使用,就要在 @babel/polyfill 中添加 core-js@2 和 core-js@3 切换的选项,这样 @babel/polyfill 中将包含 core-js@2 和 core-js@3 两个包。
出于这个原因,官方决定弃用 @babel/polyfill。
@babel/preset-env
编译语法时,有的环境下可能需要转换几十种不同语法的代码,则需要配置几十个 plugin ,这显然会非常繁琐。
所以,为了解决这种问题,Babel 提供了预设插件机制 preset,preset 中可以 预设置一组插件来便捷的使用这些插件所提供的功能。目前,Babel 官方推荐使用 @babel/preset-env 预设插件。
从 babel@7 开始,已经移除了 @babel/preset-stage-x,所以当前的新版本中直接使用 @babel/preset-env 就行。
@babel/preset-env 主要的作用是用来转换那些已经被正式纳入TC39 中的语法。所以它无法对那些还在提案中的语法进行处理,对于处在 stage 中的语法,需要安装对应的 plugin 进行处理。
意思就是刚刚提出的新语法还无法编译,需要安装语法对应的 plugin 来处理,不过那种太新的语法,不太关注这些的程序员几乎也都不知道。
除了语法转换,@babel/preset-env 另一个重要的功能是对 api 的处理,也就是在代码中引入 polyfill。
但是,@babel/preset-env 默认是不开启处理 api 的功能,只有设置了 useBuiltIns 选项(不为 false )才会开启。
@babel/preset-env 主要还是依赖 core-js 来处理api的兼容性,在升级到 7.4.0 以上的版本以后,既支持 core-js@2,也支持 core-js@3,所以增加了 corejs 的配置来控制所需的版本。
**但注意,根据上面 core-js 的描述,如果使用 @babel/preset-env 来编译API,那只能使用 core-js@2 或 core-js@3 的污染包! **
如果设置了useBuiltIns选项(不为false)就得设置 corejs 版本,否则 babel 将会发出警告。
下面是 useBuiltIns 的三个值的区别说明。
useBuiltIns 设置为 usage
注意,设置了此项,需要同时设置 corejs 选项。
代码中不需要开发人员主动引入 polyfill 垫片,babel 会自动将代码里已使用到、且 browserslist 环境不支持的 polyfill 导入。
useBuiltIns 设置为 entry
注意,设置了此项,需要同时设置 corejs 选项。
需要在代码运行之前导入,会将 browserslist 环境不支持的所有 polyfill 都导入。
useBuiltIns 设置为 false
只做了语法转换,不会导入任何 polyfill 进来,并且 corejs 配置将无效。
runtime 和 @babel/plugin-transform-runtime
在使用 @babel/preset-env 提供的全局 api 添加的功能时,由于使用的是污染包,难免会造成文件的体积增加以及api的全局污染。
为了解决这类问题,Babel 提供了另一种 api 转译的方式,引入了 runtime 的概念。
runtime
runtime 的核心思想是以引入方法替换的方式来解决兼容性问题。
runtime 包其实有三个:
@babel/runtime:只能处理语法替换,且只有在 @babel/preset-env 的帮助下,runtime 包的语法模拟替换功能才会发挥作用,这个包相当于 @babel/preset-env 的语法替换功能的延伸。
@babel/runtime-corejs2:相比较 @babel/runtime,增加了 core-js@2 的清洁包,来支持全局构造函数(例如Promise)和静态方法(例如Array.from)兼容。
@babel/runtime-corejs3:相比较 @babel/runtime-corejs2,增加了 core-js@3 的清洁包,支持了实例方法的兼容,同时还支持对ECMAScript 提案的 api 进行模拟,例如 [].flat()。
三个包都依赖 helpers、regenerator-runtime 模块来实现语法的替换,helpers中 提供了一些语法模拟的函数,regenerator-runtime 中实现了 async/await 语法的转换。
所以实际使用时,直接使用 @babel/runtime-corejs3 就行,比如要使用数组的 includes 方法,可以像下面这么写:
但这样一个个手动导入很麻烦,这个时候我们就需要借助自动导入插件来帮助我们完成这项工作。
@babel/plugin-transform-runtime
@babel/plugin-transform-runtime 就是为了方便 @babel/runtime 的使用。通过ast的分析,自动识别并替换代码中的新api,解决手动 require 的烦恼。
@babel/plugin-transform-runtime 是开发依赖,编译时负责处理 @babel/runtime,两者是搭配使用的。
下面是使用 @babel/plugin-transform-runtime 来编译 api 的配置,其中 corejs 选项来配置使用的是 @babel/runtime-corejs2 还是 @babel/runtime-corejs3。
总结
目前,Babel 处理兼容性问题有两种方案:
@babel/preset-env + corejs@3:实现语法转换 + 在全局和实例上添加api,支持全量加载和按需加载,我们简称 polyfill 方案;
缺点:就是会造成全局污染。
优点:可以根据浏览器对新特性的支持度,来选择性的进行兼容性处理。
@babel/preset-env + @babel/runtime-corejs3 + @babel/plugin-transform-runtime:实现语法转换 + 模拟替换 api,只支持按需加载,我们简称 runtime 方案。
优点:不会存在全局污染。
不能根据浏览器对新特性的支持度来选择性的进行兼容性处理,也就是说只要在代码中识别到的 api,并且该api也在 corejs3 的 core-js-pure 包中存在,就会自动替换,这样一来就会造成一些不必要的转换,从而增加代码体积。
两种方案都依赖核心包 corejs@3,只不过依赖的模块(一个污染包一个清洁包)不同,导致实现方式不同。
所以,polyfill 方案比较适合单独运行的业务项目,如果你是想开发一些供别人使用的第三方工具库,则建议你使用 runtime 方案来处理兼容性方案,以免影响使用者的运行环境。
两种配置的模板在 Babel 配置和使用 的文档中。
Last updated
Was this helpful?