本文首发于:github.com/bigo-fronte… 欢迎关注、转载。
工欲善其事,必先利其器
作为一个前端开发者,想要使用ECMAScript 2015+
新语法,又要兼容旧版的浏览器,babel相关的工具及配置是一个无法绕过去的坎。
前段时间笔者想优化公司内部的一个npm库的size,苦于胸总中无沟壑,只能老老实实看babel的官方文档,写了4个“自认为”
是使用babel的最佳配置,分别对应webapp和library的配置,希望对大家日程开发实践和性能优化有一定借鉴意义,如有不足之处,也欢迎大家斧正。
下面我将围绕babel相关的主要npm库,为大家娓娓道来(本文基于最新的babel7)。
@babel/core
babel核心库,使用babel必须要安装的。
在这里我们解释一下babel到底是什么,这里引用官方的定义:
Babel 是一个 JavaScript 编译器
Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。下面列出的是 Babel 能为你做的事情
- 语法转换
- 通过 Polyfill 方式在目标环境中添加缺失的特性(通过第三方 polyfill 模块,例如 core-js,实现)
- 源码转换 (codemods)
// Babel 输入: ES2015 箭头函数
[1, 2, 3].map((n) => n + 1);
// Babel 输出: ES5 语法实现的同等功能
[1, 2, 3].map(function(n) {
return n + 1;
});
复制代码
@babel/cli
:
babel命令行工具,单独使用babel时安装,当babel配合webpack
,rollup
等打包工具一起使用的时候,通常会有相应的loader
或着plugin
,此时可能并不需要@babel/cli
。
@babel/preset-env
在解释这个库的作用之前,我们看一下最原始的babel配置是怎样的:
babel.config.js
module.exports = {
plugins: [
"@babel/plugin-transform-block-scoping",
"@babel/plugin-transform-arrow-functions",
[
"babel-plugin-polyfill-corejs3",
{
"method": "usage-global"
}
]
]
}
复制代码
input
// index.js
const a = 'hello world'
const set = new Set();
const foo = () => {
console.log('function')
}
复制代码
output
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.set.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/web.dom-collections.iterator.js");
var a = 'hello world';
var set = new Set();
var foo = function foo() {
console.log('function');
};
复制代码
在我们的代码中,用到了哪些特性,就需要把对应的babel插件添加进来,后续如果我们还要添加其他的esnext
特性,就要这样一个一个的加入各种各样的插件,这对开发这来说非常的不友好。
如果有一个工具可以把常用的plugin都一股脑加进来,开发者并不需要关心自己的代码用了什么新特性,也不关心要安装哪些babel插件,添加plugin的这些工作全都由这个工具去完成,那就轻松很多了。
这时候preset
就登场了,我看看官方介绍:
Babel 的预设(preset)可以被看作是一组 Babel 插件和或 options 配置的可共享模块。
preset
有很多,官方的preset中,有根据stage的不同,和ECMAScript的版本的不同推出的各种preset
,而今天我们的主角是@babel/preset-env
,其他的基本都被废弃掉了。
通过官方文档的描述,preset-env主要做的是转换JavaScript最新的语法,而作为可选项 preset-env 也可以转换 JavaScript 最新的 API (指的是比如数组最新的方法includes,Promise等等)
总之,就是把所有的常用插件都汇聚到了一起,省去了自己配置插件的功夫。
这个插件有很多选项可以配置,我们挑几个重要的讲
useBuiltIns
"usage"
| "entry"
| false
, defaults to false
.
此选项配置 @babel/preset-env 如何处理 polyfill。
entry
我们需要在代码的入口文件顶部加入两行代码:
import "core-js";
import "regenerator-runtime/runtime";
复制代码
会在此时 babel 会根据当前targets
描述,把需要的所有的 polyfills 全部引入到你的入口文件(注意是全部,不管你是否有用到高级的 API)
usage
无需额外代码,babel 会根据用户代码的使用情况,并根据 targets 自行注入相关 polyfills。
false
这种方式下,不会引入 polyfills,你需要人为在入口文件处import '@babel/polyfill' 全量引入。或者手工引入对应模块的polyfill。
corejs
string |{ version: string, proposals: boolean },defaults "2.0"
此选项仅在与 useBuiltIns: usage 或 useBuiltIns: entry 一起使用时才有效,并确保 @babel/preset-env 注入core-js
版本支持的polyfill。
选项需要安装对应的corejs
版本
npm install core-js@3 --save
# or
npm install core-js@2 --save
复制代码
可能的配置如下
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: '3.16', proposals: true }, // 实际的corejs版本
}
]
]
};
复制代码
targets
运行代码的目标浏览器。
亦可以使用browserslist代替该选项。
loose
boolean, defaults to false.
优化编译的产物,如果设置为true,则会生成性能更高的转译代码,但可能不太符合ES规范。具体查看assumptions
modules
"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false, defaults to "auto".
启用 ES 模块语法到另一种模块类型的转换。
如果是配合打包工具rollup
或是webpack
,则false
即可。
@babel/plugin-transform-runtime
一个插件,可以重用 Babel 注入的辅助代码以节省代码大小。
helpers
boolean, defaults to true.
切换是否将内联的 Babel 助手(classCallCheck、extends 等)替换为对 moduleName 的调用。
corejs
false, 2, 3 or { version: 2 | 3, proposals: boolean }, defaults to false.
由@babel/preset-env
添加的polyfill都是污染全局的,对于webapp来说是可以接受,而作为library的开发者,并不希望污染全局。
默认情况下,@babel/plugin-transform-runtime 不polyfill提案阶段的api。如果使用的是 corejs: 3,可以通过使用 proposal: true 选项来启用它。
需要的依赖项如下
corejs option | Install command |
---|---|
false | npm install --save @babel/runtime |
2 | npm install --save @babel/runtime-corejs2 |
3 | npm install --save @babel/runtime-corejs3 |
webapp最佳实践1
这套针对webapp的配置,最大程度的增加对目标浏览器(运行环境)的支持,即便是项目依赖里使用的某个依赖库里使用了某些高级api,代码亦可正常运行。因为他会根据targets去引入目标targets浏览器所需的polyfill,而不管你代码中是否使用了该特性。当然,这种方式的缺点就是打包后的包体积会比较大,有很大可能会包含一些并未用到的polyfill。
npm install -D @babel/core @babel/preset-env @babel/plugin-transform-runtime
npm install -S core-js@3
复制代码
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'entry',
targets: 'Android 4.0, IOS 7', // .browserslistrc
corejs: { version: '3.16', proposals: true }, // 实际的corejs版本
loose: true,
modules: false
}
]
],
plugins: [['@babel/plugin-transform-runtime', { helpers: true }]]
};
复制代码
// index.js
import "core-js/stable"
import "regenerator-runtime/runtime"
// other code
复制代码
webapp最佳实践2
这个webapp的配置,则仅针对代码中使用到的api添加polyfill,最大程度的减小打包体积。然而,由于babel不会再对依赖库中的产物进行编译,因此babel便无法检测到依赖库里的代码,一旦某个依赖库需要依赖某些polyfill,则可能最终类库会无法运行。
npm install -D @babel/core @babel/preset-env @babel/plugin-transform-runtime
npm install -S core-js@3
复制代码
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
targets: 'Android 4.0, IOS 7', // .browserslistrc
corejs: { version: '3.16', proposals: true }, // 实际的corejs版本
loose: true,
modules: false
}
]
],
plugins: [['@babel/plugin-transform-runtime', { helpers: true }]]
};
复制代码
library最佳实践1
这个是针对"不关心类库体积大小"的场景下的一个类库开发最佳实践。使用如下的babel配置时,polyfill不会污染全局,同时又能让类库自己能正常运行,不至于代码运行在低版本浏览器里直接就报错。
然而,由于类库自身添加了局部的polyfill,会使你打包后的体积膨胀。如果类库提供给一个C端应用使用的话,那么应用自身的全局polyfill和类库自身的局部polyfill必然会存在冗余,这样无形又增大了应用的体积,反而降低应用的性能。
因此,这个实践,只适用于"不关注性能"的场景。
npm install -D @babel/core @babel/preset-env @babel/plugin-transform-runtime
npm install -S @babel/runtime-corejs3
复制代码
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
loose: true,
modules: false
}
]
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
helpers: true,
corejs: {
version: 3,
proposals: true
}
}
]
]
};
复制代码
library最佳实践2
以下配置,比较适合 "公司内自有C端业务" 或 "开源类库产品"。通常这些产品对性能有着极致的要求。因此在代码体积方面 "寸土寸金"。
基于此,我们可以直接将类库的corejs配置设置为false,也就是类库自身不添加任何polyfill。这就要求我们在开发类库项目时,要做到如下2点任选其一:
1、直接放弃使用ES5+的新特性,使用原生API语法来编写代码(在社区中可以看到很多类库作者是这样做的)
2、可以使用ES6语法,但是要通过文档告诉宿主环境,也就是告诉我们的调用者,让他注意要主动手工引入对应的polyfill。
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
loose: true,
modules: false
}
]
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
helpers: true,
corejs: false
}
]
]
};
复制代码
总结
从上面babel实践和 vue/babel-preset-app
的一些实践总结来看的话,可以将library和webapp最佳实践做如下的总结:
-
对于类库开发来说,比如我们要给公司或者github上开发一个开源类库。可能我们大部分"要照顾性能的场景下"最好就把polyfill设置为false, 只把helper设置为true。 然后编码时只用es5语法写类库,或者使用es6但要通过文档告诉调用者。
-
如果主web项目的依赖库是以ES5的形式释出的,同时依赖库若使用了ES6+特性。此时,要看该依赖库的作者是否"在文档中声明了其依赖的polyfill"。
- 若作者声明了依赖polyfill列表。那么我们可以在主项目中使用
useBuiltIns:'usage'
,且需要预先引入类库所需的polyfills。一般项目的引入方法可以使用import
语法在入口文件引入,具体模块需参考corejs文档;而若是使用了vue/babel-preset-app的项目,则可以直接通过其polyfill选项配置来指定。 - 若类库作者并没有声明所依赖的polyfill。则我们为了保险起见,则可以将主项目的babel配置为
useBuiltIns:'entry'
。从而尽最大可能保证我们主项目引入的全局polyfill能覆盖类库所需。
- 若主项目依赖库是用ES6+语法来写的,且使用了目标浏览器不支持的API特性。那么我们可以在主项目中使用
useBuiltIns:'usage'
的配置。然后在主项目代码中的babel和webpack配置里将对应的依赖库设置为include编译包含,这样的话babel编译则会将该依赖库按照主项目的配置进行编译并遵循useBuiltIns:'usage'
配置进行polyfill。(若你主项目是使用vue/babel-preset-app,则请参考其文档进行对应项的配置)
参考自: www.npmjs.com/package/@vu…
相关库
以下为在babel配置实践过程中用到的相关库及版本
- "@babel/cli": "^7.14.8"
- "@babel/core": "^7.14.8"
- "@babel/plugin-transform-runtime": "^7.15.0"
- "@babel/preset-env": "^7.15.0"
- "@babel/runtime-corejs3": "^7.15.3"
- "core-js": "^3.16.1"
link
欢迎大家留言讨论,祝工作顺利、生活愉快!
我是bigo前端,下期见。