Guide To Babel in 2021
Introduction
Babel 在 2021 年一共进行了 2 个minor
版本的更新,增加了一些Stage 4 proposals
的支持,以及一些Top-level
的配置项(targets, assumptions)。伴随着这些更新,结合babel-loader和babel-preset-react-app我们来探究一下在 2021 年该如何使用 Babel。
@babel/preset-env
@babel/preset-env
是官方推荐的preset
,只需要配置相关的targets
就可以转换当前代码到目标环境的代码,遵循browserslist的相关配置,主要配置项如下:
targets
配置目标环境,如果不指定,则会转换所有 ES2015-ES2020 的代码到 ES5.而不是使用 browserslist 的defaults
配置(> 0.5%, last 2 versions, Firefox ESR, not dead)。
useBuiltIns
配置@babel/preset-env
如何处理polyfills
,可选项为"usage"
|"entry"
|false
"entry"
这个配置会自动将import "core-js/stable";
和import "regenerator-runtime/runtime"
转换为目标环境的按需引入,举个例子:
1 | import 'core-js/stable'; |
在不同环境下可能转换为:
1 | import 'core-js/modules/es.string.pad-start'; |
但是有个缺点是用不到的 polyfill 也可能会引入进来,因为entry
配置只针对目标环境,而不是具体代码
"usage"
这个配置则会自动引入代码中需要的 polyfill,且不需要显示声明import core-js
,推荐使用该配置
false
不自动添加 polyfill,也不自动转换import core-js
为按需引入
corejs
当 useBuiltIns 配置项为entry
或usage
时生效,默认值为"2.0"
,建议配置为minor version
的具体版本号
其它配置诸如include
,exclude
详见Options
@babel/runtime
@babel/runtime
与其配套的@babel/plugin-transform-runtime
主要有三个配置项,分别对应不同场景:
regenerator
使用 generator/async 函数时自动引用@babel/runtime/regenerator
。默认值为true
,默认开启
1 | function* foo() {} |
输出
1 | ; |
corejs
按需引入相关@babel/runtime-corejs
的 helpers,避免生成污染全局空间和内置对象原型的代码,常用于开发类库/工具。可选值为false | 2 | 3
或 { version: 2 | 3 , proposals: boolean}
格式,配置为false
则不引入相关helpers
1 | var sym = Symbol(); |
输出
1 | import _getIterator from '@babel/runtime-corejs3/core-js/get-iterator'; |
helpers
自动移除 inline 格式的Babel helpers
并替换为引入模式,好处是移除了冗余重复的代码。默认值为true
,默认开启
1 | class Person {} |
通常情况下的转换结果为:
1 | ; |
开启之后转换结果为:
1 | ; |
babel-loader
babel-loader 除了支持 babel 相关的所有options,还增加了相关的缓存支持,相关的缓存配置主要如下:
cacheDirectory
默认值为false
,如果设置为true
或者其他地址,那么 webpack 后续的 build 会尝试从缓存中读取之前内容,避免了 babel 的重新编译环节
cacheIdentifier
默认值为@babel/core
的版本,babel-loader
的版本,以及.babelrc 的内容合在一起的 stringify 值,即:
1 | cacheIdentifier = JSON.stringify({ |
如果该值改变,则会强制刷新缓存
cacheCompression
默认值为true
,设置之后 babel 的缓存结果会被 gzip 压缩
开启缓存如何加快 rebuild?
关于对缓存的支持以加快 rebuild,简单来说有如下几个步骤:
- 检测是否配置了
cacheDirectory
,如果配置,则调用cache()
进一步处理,如果没有则直接transform()
- 配置了
cacheDirectory
之后,先根据每个文件内容(source)和配置(options)以及标识符(identifier)三部分内容JSON.stringify
之后进行哈希,得到文件名称filename
,即调用了filename(source, cacheIdentifier, options)
,再path.join
对应的directory
获得缓存文件的绝对路径file
- 获得文件的绝对路径之后,尝试读取文件内容,如果读取到说明之前相对应的
source
已经缓存过,直接返回对应的结果 - 没有的则将
transform()
之后的结果写到对应的文件file
下,并将结果返回
感兴趣的可以阅读相关的cache源码
create-react-app
有了以上的基础知识铺垫,我们来看一下create-react-app
和 babel 相关的内容是如何配置和处理的
react-scripts
先看一下 react-scripts 中关于 webpack 的babel-loader是如何配置的,配置很多,去掉注释之后如下所示:
1 | { |
总结如下:
- 引入了
babel-preset-react-app
,这个 preset 也是 create-react-app 维护的,细节我们后面讲 - 自定义了
babel-loader
的cacheIdentifier
,确保其值唯一,具体如上所示 - 设置
cacheDirectory
值为true
,开启相关缓存 - 但是禁用了缓存相关的 gzip 压缩,原因见这个PR
- 正式环境开启了
compact
模式
babel-preset-react-app
babel-preset-react-app
对 react 以及 flow 和 typescript 都有所支持,剔除掉这些,具体的配置可以精简如下:
1 | { |
总结如下:
@babel/preset-env
采用了useBuiltIns
的entry
模式,而不是usage
模式,这个comment解释了为什么,总的来说usage
模式貌似有点问题?@babel/preset-env
exclude 了transform-typeof-symbol
,因为会导致代码变慢,具体见这个issuebabel-plugin-macros
: 支持了宏,具体是干嘛的有兴趣的可以自行了解一下babel-plugin-macros@babel/plugin-transform-runtime
core-js: false
: 不引入core-js
相关内容,因为上面@babel/preset-env
提到了需要入口文件import core-js
version: require('@babel/runtime/package.json').version
:固定了 version,这也是 babel 官方推荐做法regenerator: true
:支持@babel/runtime/regenerator
的自动引入
- 对其它常用的和 webpack 4 不支持的 proposal 做了兼容,引入了这些 plugin
Conclusion
create-react-app
相关的react-scripts
和babel-preset-react-app
给出了 2021 年如何使用 babel 的标准范式,在日常的开发学习中有很多值得参考和借鉴的地方。当然在实际运用中需要掌握基础,才能遇到不同的情况灵活处理。最后来一波总结:
- 使用
babel-loader
时建议配置cacheDirectory
,开启缓存,增加重新构建速度 - 项目中需要编译代码到指定环境时,善用
@babel/preset-env
的targets
和useBuiltIns
配置项,可以减少很多不必要的代码编译和 polyfill - 开发工具/类库时,建议使用
@babel/runtime
配合@babel/plugin-transform-runtime