您现在的位置是:亿华云 > IT科技
你可能并没有理解的 Babel 配置的原理
亿华云2025-10-09 13:41:11【IT科技】6人已围观
简介babel 是一个 JS、TS 的编译器,它能把新语法写的代码转换成目标环境支持的语法的代码,并且对目标环境不支持的 api 自动 polyfill。babel 基本每个项目都用
babel 是并没一个 JS、TS 的有理原理编译器,它能把新语法写的并没代码转换成目标环境支持的语法的代码,并且对目标环境不支持的有理原理 api 自动 polyfill。
babel 基本每个项目都用,并没大家可能对 @babel/preset-env 和 @babel/plugin-transform-runtime 都很熟悉了,有理原理但是并没你真的理解它们么?
相信很多同学只是知道它能干什么,但不知道它是有理原理怎么实现的,这篇文章我们就来深入下它们的并没实现原理吧。
首先,有理原理我们先来试一下 preset-env 和 plugin-transform-runtime 的并没功能:
功能测试@babel/preset-env 的作用是根据 targets 的配置引入对应插件来实现编译和 polyfill。
比如这段代码:
class Dong {
}在低版本浏览器不支持,有理原理会做语法转换。并没
我们把 targets 指定成比较低版本的有理原理浏览器,比如 chrome 30,并没并且打开 debug 选项,它的云服务器提供商作用是会打印用到的 plugin。
{
presets: [
[@babel/preset-env, {
targets: chrome 30,
debug: true,
useBuiltIns: usage,
corejs: 3
}]
]
}执行 babel 就会发现它用到了这些插件:
这就是 @babel/preset-env 的意义,自动根据 targets 来引入需要的插件,不然要是手动写这么一堆插件不得麻烦死。
开启 polyfill 功能要指定它的引入方式,也就是 useBuiltIns。设置为 usage 是在每个模块引入用到的,设置为 entry 是统一在入口处引入 targets 需要的。
polyfill 的实现就是 core-js,需要再指定下 corejs 版本,一般是指定 3,这个会 polyfill 实例方法,而 corejs2 不会。
上面一段代码会转换成这样:
注入了 3 个 helper,也就是 _createClass 这种以下划线开头的辅助方法。
因为 helper 方法里用到了 Object.defineProperty 的云服务器 api,这里也会从 core-js 里引入。
我们再测试一下这样一段代码:
async function func() {
}会被转换成这样:
除了注入 core-js、helper 代码外,还注入了 regenerator 代码,这个是 async await 的实现。
综上,babel runtime 包含的代码就 core-js、helper、regenerator 这三种。
@babel/preset-env 的处理方式是 helper 代码直接注入、regenerator、core-js 代码全局引入。
这样就会导致多个模块重复注入同样的代码,会污染全局环境。
解决这个问题就要使用 @babel/plugin-transform-runtime 插件了。
我们在配置文件里引入这个插件:
{
presets: [
[@babel/preset-env, {
targets: chrome 30,
debug: true,
useBuiltIns: usage,
corejs: 3
}]
],
plugins: [
[@babel/plugin-transform-runtime, {
corejs: 3
}]
]
}注意,这个插件也是处理 polyfill ,也就同样需要指定 corejs 的版本。
然后测试下引入之后有什么变化:
先测试 class 那个案例:
之前是这样的:
现在变成了这样:
变成了从 @babel/runtime-corejs3 引入的形式,这样就不会多个模块重复注入同样的实现代码了,站群服务器而且 core-js 的 api 也不是全局引入了,变成了模块化引入。
这样就解决了 corejs 的重复注入和全局引入 polyfill 的两个问题。
再测试 async function 那个案例:
之前是这样的:
同样有全局引入和重复注入的问题。
引入 transform-runtime 插件之后是这样的:
也是同样的方式解决了那两个问题。
再来测试一个 api 的,用这样一段代码:
new WeakMap();当只配置 preset-env 时:
{
presets: [
[@babel/preset-env, {
targets: chrome 30,
debug: true,
useBuiltIns: usage,
corejs: 3
}]
]
}结果是这样的:
再加上 @babel/plugin-transform-runtime 后:
{
presets: [
[@babel/preset-env, {
targets: chrome 30,
debug: true,
useBuiltIns: usage,
corejs: 3
}]
],
plugins: [
[@babel/plugin-transform-runtime,
{
corejs: 3
}
]
]
}结果是这样的:
这样我们就清楚了 @babel/plugin-transform-runtime 的功能,把注入的代码和 core-js 全局引入的代码转换成从 @babel/runtime-corejs3 中引入的形式。
@babel/runtime-corejs3 就包含了 helpers、core-js、regenerator 这 3 部分。
功能我们都清楚了,那它们是怎么实现的呢?
实现原理preset-env 的原理之前讲过,就是根据 targets 的配置查询内部的 @babe/compat-data 的数据库,过滤出目标环境不支持的语法和 api,引入对应的转换插件。
targets 使用 browserslist 来解析成具体的浏览器和版本:
然后根据 @babel/compact-data 的数据来过滤出这些浏览器支持的语法和 api:
然后去掉这些已经支持的语法和 api 对应的插件,剩下的就是需要用的转换插件:
这就是 preset-env 的根据 targtes 来按需转换语法和 polyfill 的原理。
那 @babel/plugin-transform-runtime 呢?它是怎么实现的?
这个插件的原理是因为 babel 插件和 preset 生效的顺序是这样的(下面是官网文档的截图):
先插件后 preset,插件从左往右,preset 从右往左。
这就导致了 @babel/plugin-transform-runtime 是在 @babel/preset-env 之前调用的,提前做了 api 的转换,那到了 @babel/preset-env 就没什么可转了,也就实现了 polyfill 的抽取。
它的源码是这样的:
会根据配置来引入 corejs、regenerator 的转换插件,实现 polyfill 注入的功能。
并且还设置了一个 helperGenerator 的函数到全局上下文 file,这样后面 @babel/preset-env 就可以用它来生成 helper 代码。那自然也就是抽离的了。
这就是 @babel/plugin-transform-runtime 的原理:
因为插件在 preset 之前调用,所以可以提前把 polyfill 转换了,而且注入了 helpGenerator 来修改 @babel/preset-env 生成 helper 代码的行为。
原理我们理清了,但是大家有没有发现其中的问题:
现有方案的问题我们通过 @babel/plugin-transform-runtime 提前把 polyfill 转换了,但是这个插件里没有 targets 的设置呀,不是按需转换的,那就会多做一些没必要的转换。
这个其实是已知问题,可以在 babel 的项目里找到这个 issue:
当然官方也提出了解决的方案,只不过这个得等 babel 新版本更新再用了,等 babel8 吧。
总结babel7 以后,我们只需要使用 @babel/preset-env,指定目标环境的 targets,babel 就会根据内部的兼容性数据库查询出该环境不支持的语法和 api,进行对应插件的引入,从而实现按需的语法转换和 polyfill 引入。
但是 @babel/preset-env 转换用到的一些辅助代码(helper)是直接注入到模块里的,没有做抽离,多个模块可能会重复注入。并且用到的 polyfill 代码也是全局引入的,可能污染全局环境。为了解决这两个问题我们会使用 @babel/plugin-transform-runtime 插件来把注入的代码抽离,把全局的引入改为从 @babel/runtime-corejs3 引入的方式。
runtime 包包含 core-js、regenerator、helper 三部分。
@babel/plugin-transform-runtime 能生效的原理是因为插件先于 preset 被调用,提前把那些 api 做了转换,并且设置了 preset-env 生成 helper 的方式。
但是这个转换和 preset-env 是独立的,它没有 targets 的配置,这就导致了不能按需 polyfill,会进行一些不必要的转换。这个是已知的 issue,等 babel 版本更新吧。
看到这里,你对 babel 的配置和这些配置的原理是否有更深的理解了呢。
很赞哦!(2)
上一篇: 四、一定要仔细阅读细节
相关文章
- 在数以亿计的网站中,我们应该抓住每一个可能带来宣传的机会,域名可以带有企业的名字,一般可以使用汉语拼音或者英语单词或者是相关缩写的形式,只要用户记住了你企业的名字,就能很容易的打出你的网站域名,同样的,记住了网站域名也能很快的记住你公司的名字。
- mysqldump Got error: 1045
- 判断杂米域名值不值钱的几个因素
- 双拼com域名资源几近枯竭,新手还能投资哪类域名?
- 众所周知,com域名拥有最大的流通市场和流通历史。最好选择com域名,特别是在购买域名时处理域名。其次可以是cn域名、net域名、org域名等主流域名,现在比较流行的王域名和顶级域名,都是值得注册和投资的。
- python之matplotlib模块绘制常见的激活函数及各激活函数的优缺点
- 为什么选择.cn域名?原因都在这里!
- 域名怎么选?数字米、字母米谁更胜一筹?
- 3、商标域名一经注册,就可以作为域名裁决过程中的主要信息之一。这可以大大增加公司被抢注的相关域名胜诉的机会。
- 2020年 cc域名的注册价格