Webpack-babel

Babel

avatar

babel/corePolyfillbabel/plugin-transform-runtime

Babel 能够做的事情:

  • 语法转换
  • 通过 Polyfill 方式在目标环境中添加缺失的特性(@babel/polyfill 模块)
  • 源码转换(codemods)

核心 @babel/core

Babel 的核心功能包含在 @babel/core 模块中。不安装 @babel/core,无法使用 babel 进行编译。

Polyfill

@babel/polyfill 模块包括 core-js 和一个自定义的 regenerator runtime 模块,可以模拟完整的 ES2015+ 环境(不包含第4阶段前的提议)。 使我们能使用类似 PromiseWeakMap 构造函数、Array.formObject.assign 的静态方法、Array.prototype.includes 之类的实例方法以及生成器函数(前提是使用了 @babel/plugin-transform-regenerator 插件)。为了添加这些功能,polyfill 将添加到全局范围和类似 String 这样的内置原型中。但会对全局环境造成污染。

首先,安装 @babel/polyfill 依赖:

1
npm install --save @babel/polyfill

我们需要将完整的 polyfill 在代码之前加载,修改我们的 src/index.js:

1
2
3
4
5
6
7
import '@babel/polyfill';

const isHas = [1,2,3].includes(2);

const p = new Promise((resolve, reject) => {
resolve(100);
});

webpack 中进行配置

例如:

1
2
3
4
entry: [
require.resolve('./polyfills'),
path.resolve('./index')
]

polyfills.js 文件内容如下:

1
2
//当然,还可能有一些其它的 polyfill,例如 stage 4之前的一些 polyfill
import '@babel/polyfill';

如果完整的引入 @babel/polyfill 会导致我们最终构建出的包的体积增大

@babel/polyfill的包大小为89K (当前 @babel/polyfill 版本为 7.7.0)。

@babel/preset-env 提供了一个 useBuiltIns 参数,设置值为 usage 时,就只会包含代码需要的 polyfill 。有一点需要注意:配置此参数的值为 usage ,必须要同时设置 corejs (如果不设置,会给出警告,默认使用的是“corejs”: 2) ,注意: 这里仍然需要安装 @babel/polyfill(当前 @babel/polyfill 版本默认会安装 “corejs”: 2):

首先说一下使用 core-js@3 的原因,core-js@2 分支中已经不会再添加新特性,新特性都会添加到 core-js@3。例如你使用了 Array.prototype.flat(),如果你使用的是 core-js@2,那么其不包含此新特性。为了可以使用更多的新特性,建议大家使用 core-js@3

安装依赖依赖:

1
npm install --save core-js@3

现在,修改 Babel 的配置文件如下:

1
2
3
4
5
6
7
8
9
//.babelrcconst presets = [    
[        
"@babel/env",        
{               
"useBuiltIns": "usage",
"corejs": 3
        }    
]
]

Babel 会检查所有代码,以便查找在目标环境中缺失的功能,然后仅仅把需要的 polyfill 包含进来。

例如,src/index.js 代码不变:

1
2
3
4
5
const isHas = [1,2,3].includes(2);

const p = new Promise((resolve, reject) => {
resolve(100);
});

我们看看编译出来的文件 (lib/index):

1
2
3
4
5
6
7
8
9
10
11
12
13
"use strict";

require("core-js/modules/es.array.includes");

require("core-js/modules/es.object.to-string");

require("core-js/modules/es.promise");

var isHas = [1, 2, 3].includes(2);

var p = new Promise(function (resolve, reject) {
resolve(100);
});

同样的代码,我们用 webpack 构建一下(production 模式),能看到最终的代码大小仅为: 20KB。而如果我们引入整个 @babel/polyfill 的话,构建出的包大小为:89KB

前面曾提到,在 useBuiltIns 参数值为 usage 时,仍然需要安装 @babel/polyfill,虽然我们上面的代码转换中看起来并没有使用到,但是,如果我们源码中使用到了 async/await,那么编译出来的代码需要 require(“regenerator-runtime/runtime”),在 @babel/polyfill 的依赖中,当然啦,你也可以只安装 regenerator-runtime/runtime 取代安装 @babel/polyfill

下面我要说的内容,也许你已经知道,也许你还不知道,这都不重要,但是此刻起,你要知道了: Babel 会使用很小的辅助函数来实现类似 _createClass 等公共方法。默认情况下,它将被添加(inject)到需要它的每个文件中。

假如,我们的 src/index.js 是这样的:

1
2
3
4
5
6
7
8
9
10
class Point {
constructor(x, y) {
        this.x = x;
        this.y = y;
    };
getX() {
return this.x;
    }
}
let cp = new ColorPoint(25, 8);

编译出来的 lib/index.js,如下所示:`

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
"use strict";

require("core-js/modules/es.object.define-property");

function _classCallCheck(instance, Constructor) { 
if (!(instance instanceof Constructor)) { 
throw new TypeError("Cannot call a class as a function"); }
}

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

var Point =
/*#__PURE__*/
function () {
function Point(x, y) {
_classCallCheck(this, Point);
    this.x = x;
    this.y = y;
}
_createClass(Point, [
{
key: "getX",
value: function getX() {
return this.x;
}
}
]
);
return Point;
}();
var cp = new ColorPoint(25, 8);

看起来,似乎并没有什么问题,但是你想一下,如果你有10个文件中都使用了这个 class,是不是意味着 _classCallCheck_defineProperties_createClass 这些方法被 inject 了10次。这显然会导致包体积增大,最关键的是,我们并不需要它 inject 多次。

这个时候,就是 @babel/plugin-transform-runtime 插件大显身手的时候了,使用 @babel/plugin-transform-runtime 插件,所有帮助程序都将引用模块 @babel/runtime,这样就可以避免编译后的代码中出现重复的帮助程序,有效减少包体积。

@babel/plugin-transform-runtime

@babel/plugin-transform-runtime 是一个可以重复使用 Babel 注入的帮助程序,以节省代码大小的插件。

另外,@babel/plugin-transform-runtime 需要和 @babel/runtime 配合使用。

首先安装依赖,@babel/plugin-transform-runtime 通常仅在开发时使用,但是运行时最终代码需要依赖 @babel/runtime,所以 @babel/runtime 必须要作为生产依赖被安装,如下 :

1
2
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime

除了前文所说的,@babel/plugin-transform-runtime 可以减少编译后代码的体积外,我们使用它还有一个好处,它可以为代码创建一个沙盒环境,如果使用 @babel/polyfill 及其提供的内置程序(例如 Promise ,Set 和 Map ),则它们将污染全局范围。虽然这对于应用程序或命令行工具可能是可以的,但是如果你的代码是要发布供他人使用的库,或者无法完全控制代码运行的环境,则将成为一个问题。
@babel/plugin-transform-runtime 会将这些内置别名作为 core-js 的别名,因此您可以无缝使用它们,而无需 polyfill
修改 .babelrc 的配置,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//.babelrc
{    
"presets": 
[
    [
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": [        
[            
"@babel/plugin-transform-runtime"        
]    
]}

重新编译 npm run compiler , 现在,编译出来的内容为(lib/index.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var Point =
/*#__PURE__*/
function () {
function Point(x, y) {
(0, _classCallCheck2.default)(this, Point);
            this.x = x;
            this.y = y;
        }
(0, _createClass2.default)(Point, [{
key: "getX",
            value: function getX() {
return this.x;
}
}]);
return Point;
}();
var cp = new ColorPoint(25, 8);

可以看出,帮助函数现在不是直接被 inject 到代码中,而是从 @babel/runtime 中引入。前文说了使用 @babel/plugin-transform-runtime 可以避免全局污染,我们来看看是如何避免污染的。

修改 src/index.js 如下:

1
2
3
4
5
let isHas = [1,2,3].includes(2);

new Promise((resolve, reject) => {
resolve(100);
});

编译出来的代码如下(lib/index.js):

1
2
3
4
5
6
7
8
"use strict";
require("core-js/modules/es.array.includes");
require("core-js/modules/es.object.to-string");
require("core-js/modules/es.promise");
var isHas = [1, 2, 3].includes(2);
new Promise(function (resolve, reject) {    
resolve(100);
});

Array.prototype 上新增了 includes 方法,并且新增了全局的 Promise 方法,污染了全局环境,这跟不使用 @babel/plugin-transform-runtime 没有区别嘛。

如果我们希望 @babel/plugin-transform-runtime 不仅仅处理帮助函数,同时也能加载 polyfill 的话,我们需要给 @babel/plugin-transform-runtime 增加配置信息。

首先新增依赖 @babel/runtime-corejs3:

1
npm install @babel/runtime-corejs3 --save

修改配置文件如下(移除了 @babel/preset-envuseBuiltIns 的配置,不然不就重复了嘛嘛嘛,不信的话,你用 async/await 编译下试试咯):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{    
"presets": [        
[            
"@babel/preset-env"        
]    
],    
"plugins": [        
[            
"@babel/plugin-transform-runtime",
{                
"corejs": 3            
}        
]    
]}

然后重新编译,看一下,编译出来的结果(lib/index.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));

var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));

var _context;

var isHas = (0, _includes.default)(_context = [1, 2, 3]).call(_context, 2);

new _promise.default(function (resolve, reject) {  
resolve(100);
});

可以看出,没有直接去修改 Array.prototype,或者是新增 Promise 方法,避免了全局污染。如果上面 @babel/plugin-transform-runtime 配置的 core-js 是 “2”,其中不包含实例的 polyfill 需要单独引入。

划重点:如果我们配置的 corejs 是 3 版本,那么不管是实例方法还是全局方法,都不会再污染全局环境。

看到这里,不知道大家有没有这样一个疑问?给 @babel/plugin-transform-runtime 配置 corejs 是如此的完美,既可以将帮助函数变成引用的形式,又可以动态引入 polyfill,并且不会污染全局环境。何必要给 @babel/preset-env 提供 useBuiltIns 功能呢,看起来似乎不需要呀。

带着这样的疑问,我新建了几个文件(内容简单且基本一致,使用了些新特性),然后使用 webpack 构建,以下是我对比的数据:

序号 .babelrc 配置 webpack mode production
0 不使用 @babel/plugin-transform-runtime 36KB
1 使用 @babel/plugin-transform-runtime ,并配置参数 corejs: 3。不会污染全局环境 37KB
2 使用 @babel/plugin-transform-runtime ,不配置 corejs 22KB

我猜测是 @babel/runtime-corejs3/XXX 的包本身比 *core-js/modules/XXX *要大一些

结语

@babel/plugin-transform-runtime

@babel/polyfill

合理配置Babel配置,使用对应polyfill达到优化的目的

参考 帖子:刘小夕-不容错过的 Babel7 知识