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-envexclude 了transform-typeof-symbol,因为会导致代码变慢,具体见这个issuebabel-plugin-macros: 支持了宏,具体是干嘛的有兴趣的可以自行了解一下babel-plugin-macros@babel/plugin-transform-runtimecore-js: false: 不引入core-js相关内容,因为上面@babel/preset-env提到了需要入口文件import core-jsversion: 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