// 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');
};
作为一个前端开发者,想要使用
ECMAScript 2015+
新语法,又要兼容旧版的浏览器,babel相关的工具及配置是一个无法绕过去的坎。前段时间想优化公司内部的一个npm库的size,苦于胸总中无沟壑,只能老老实实看babel的官方文档,写了4个
“自认为”
是使用babel的最佳配置,分别对应webapp和library的配置,希望对大家日程开发实践和性能优化有一定借鉴意义。下面我将围绕babel相关的主要npm库,为大家娓娓道来(本文基于最新的babel7)。
@babel/core
babel核心库,使用babel必须要安装的。
在这里我们解释一下babel到底是什么,这里引用官方的定义:
Babel 是一个 JavaScript 编译器
// Babel 输出: ES5 语法实现的同等功能 [1, 2, 3].map(function(n) { return n + 1; });
input
output
在我们的代码中,用到了哪些特性,就需要把对应的babel插件添加进来,后续如果我们还要添加其他的
esnext
特性,就要这样一个一个的加入各种各样的插件,这对开发这来说非常的不友好。如果有一个工具可以把常用的plugin都一股脑加进来,开发者并不需要关心自己的代码用了什么新特性,也不关心要安装哪些babel插件,添加plugin的这些工作全都由这个工具去完成,那就轻松很多了。
这时候
preset
就登场了,我看看官方介绍:preset
有很多,官方的preset中,有根据stage的不同,和ECMAScript的版本的不同推出的各种preset
,而今天我们的主角是@babel/preset-env
,其他的基本都被废弃掉了。通过官方文档的描述,preset-env主要做的是转换JavaScript最新的语法,而作为可选项 preset-env 也可以转换 JavaScript 最新的 API (指的是比如数组最新的方法includes,Promise等等)
总之,就是把所有的常用插件都汇聚到了一起,省去了自己配置插件的功夫。
这个插件有很多选项可以配置,我们挑几个重要的讲
useBuiltIns
"usage"
|"entry"
|false
, defaults tofalse
.此选项配置 @babel/preset-env 如何处理 polyfill。
entry
我们需要在代码的入口文件顶部加入两行代码:
会在此时 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
版本可能的配置如下
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 选项来启用它。
需要的依赖项如下
npm install --save @babel/runtime
npm install --save @babel/runtime-corejs2
npm install --save @babel/runtime-corejs3
webapp最佳实践1
这套针对webapp的配置,最大程度的增加对目标浏览器(运行环境)的支持,即便是项目依赖里使用的某个依赖库里使用了某些高级api,代码亦可正常运行。因为他会根据targets去引入目标targets浏览器所需的polyfill,而不管你代码中是否使用了该特性。当然,这种方式的缺点就是打包后的包体积会比较大,有很大可能会包含一些并未用到的polyfill。
webapp最佳实践2
这个webapp的配置,则仅针对代码中使用到的api添加polyfill,最大程度的减小打包体积。然而,由于babel不会再对依赖库中的产物进行编译,因此babel便无法检测到依赖库里的代码,一旦某个依赖库需要依赖某些polyfill,则可能最终类库会无法运行。
library最佳实践1
这个是针对"不关心类库体积大小"的场景下的一个类库开发最佳实践。使用如下的babel配置时,polyfill不会污染全局,同时又能让类库自己能正常运行,不至于代码运行在低版本浏览器里直接就报错。
然而,由于类库自身添加了局部的polyfill,会使你打包后的体积膨胀。如果类库提供给一个C端应用使用的话,那么应用自身的全局polyfill和类库自身的局部polyfill必然会存在冗余,这样无形又增大了应用的体积,反而降低应用的性能。
因此,这个实践,只适用于"不关注性能"的场景。
library最佳实践2
以下配置,比较适合 "公司内自有C端业务" 或 "开源类库产品"。通常这些产品对性能有着极致的要求。因此在代码体积方面 "寸土寸金"。
基于此,我们可以直接将类库的corejs配置设置为false,也就是类库自身不添加任何polyfill。这就要求我们在开发类库项目时,要做到如下2点任选其一:
1、直接放弃使用ES5+的新特性,使用原生API语法来编写代码(在社区中可以看到很多类库作者是这样做的) 2、可以使用ES6语法,但是要通过文档告诉宿主环境,也就是告诉我们的调用者,让他注意要主动手工引入对应的polyfill。
总结
从上面babel实践和
vue/babel-preset-app
的一些实践总结来看的话,可以将library和webapp最佳实践做如下的总结:对于类库开发来说,比如我们要给公司或者github上开发一个开源类库。可能我们大部分"要照顾性能的场景下"最好就把polyfill设置为false, 只把helper设置为true。 然后编码时只用es5语法写类库,或者使用es6但要通过文档告诉调用者。
如果主web项目的依赖库是以ES5的形式释出的,同时依赖库若使用了ES6+特性。此时,要看该依赖库的作者是否"在文档中声明了其依赖的polyfill"。
useBuiltIns:'usage'
,且需要预先引入类库所需的polyfills。一般项目的引入方法可以使用import
语法在入口文件引入,具体模块需参考corejs文档;而若是使用了vue/babel-preset-app的项目,则可以直接通过其polyfill选项配置来指定。useBuiltIns:'entry'
。从而尽最大可能保证我们主项目引入的全局polyfill能覆盖类库所需。若主项目依赖库是用ES6+语法来写的,且使用了目标浏览器不支持的API特性。那么我们可以在主项目中使用
useBuiltIns:'usage'
的配置。然后在主项目代码中的babel和webpack配置里将对应的依赖库设置为include编译包含,这样的话babel编译则会将该依赖库按照主项目的配置进行编译并遵循useBuiltIns:'usage'
配置进行polyfill。(若你主项目是使用vue/babel-preset-app,则请参考其文档进行对应项的配置)参考自: https://www.npmjs.com/package/@vue/babel-preset-app
相关库
以下为在babel配置实践过程中用到的相关库及版本
link