# Babel 优化
提示
本节内容紧接着上节 Babel 转化。
如果你有仔细观察上节编译的产物,就可以发现很多当前浏览器能识别的 语法 和 API,Bable 都统统做了转换,导致编译出来的产物很大(dist/library.js
大约 15827 行代码)。还有一些垫片直接重写了浏览器的原型链等,这可能会导致用户代码运行后不是预期的结果。有些辅助函数还在各个文件重复出现,也会导致编译出来的产物很大。接下来的内容是介绍如何依据浏览器来使用最少的代码进行兼容,并且避免全局污染。
# 根据浏览器按需引用
查看产物
可以在产物 dist/library.js
搜索 .prototype.
,就可以发现有 Array.prototype.forEach
、Array.prototype.map
、Array.prototype.filter
、Array.prototype.some
、Array.prototype.every
、Array.prototype.find
、Array.prototype.findIndex
、Array.prototype.filterReject
等很多 API 垫片。
思考: 查看上节编译出来的产物,可以发现添加了很多新 API 垫片。如果想该库只支持 Chrome,不支持 IE。那还需要添加这么多 API 垫片吗?例如现在的 Chrome 并不需要上面的 Array.prototype.forEach
、Array.prototype.map
、Array.prototype.filter
、Array.prototype.some
、Array.prototype.every
、Array.prototype.find
、Array.prototype.findIndex
等。
兼容性
你可以在当前浏览器的控制台输入 Array.prototype.findIndex
等,如果未打印出函数,则说明浏览器不支持这个 API。或者前往 浏览器兼容性网站 (opens new window) 查询。
针对上述情况,Babel 的 @babel/preset-env
插件提供了 useBuiltIns (opens new window) 属性,只需要设置成 entry
(默认为:false
),Babel 就会根据配置中的目标浏览器进行选择性 API 垫片的添加。useBuiltIns: "entry" (opens new window)
注意
请确保入口引入了 core-js
和 regenerator-runtime/runtime
,因为 Babel 是将上述引入文件,将其拆分成所需的不同垫片。
对比: 简单使用,将 run-babel.js
中的 Babel 配置和编译文件进行修改,这样产物对比会更加清楚。
- 默认不设置(即
useBuiltIns: false
),编译配置和产物如下:
// run-babel.js
// ...
const filename = path.resolve(__dirname, "./src/browser.js");
const buildFileName = path.resolve(__dirname, "./src/browser-es5-false.js");
const data = fs.readFileSync(filename, "utf8");
// 确保入口文件 browser.js 添加了 core-js、regenerator-runtime
const transformData = babel.transformSync(data, {
presets: ["@babel/preset-env"],
});
// ...
2
3
4
5
6
7
8
9
10
// src/browser-es5-false.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.browser = browser;
require("core-js/stable");
require("regenerator-runtime/runtime");
// src/browser.js
var inBrowser = typeof window !== "undefined";
var UA = inBrowser && window.navigator.userAgent.toLowerCase();
var isIE = UA && /msie|trident/.test(UA);
var isEdge = UA && UA.includes("edg/");
var isChrome = UA && UA.includes("chrome") && !isEdge;
function browser() {
return [
"ie: ".concat(isIE),
"edge: ".concat(isEdge),
"chrome: ".concat(isChrome),
].join(", ");
}
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
- 设置
useBuiltIns: entry
,编译配置和产物如下:
// run-babel.js
// ...
const filename = path.resolve(__dirname, "./src/browser.js");
const buildFileName = path.resolve(__dirname, "./src/browser-es5-entry.js");
const data = fs.readFileSync(filename, "utf8");
// 确保入口文件 browser.js 添加了 core-js、regenerator-runtime
const transformData = babel.transformSync(data, {
presets: [["@babel/preset-env", { useBuiltIns: "entry", corejs: 3 }]],
});
// ...
2
3
4
5
6
7
8
9
10
注意
需要显示指定 corejs: 3
或者 corejs: 2
,否则 Babel 不清楚需要对哪个版本的 corejs 以哪种方式进行展开优化。
// src/browser-es5-entry.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.browser = browser;
require("core-js/modules/es.symbol.js");
require("core-js/modules/es.symbol.description.js");
require("core-js/modules/es.symbol.async-iterator.js");
require("core-js/modules/es.symbol.has-instance.js");
require("core-js/modules/es.symbol.is-concat-spreadable.js");
require("core-js/modules/es.symbol.iterator.js");
require("core-js/modules/es.symbol.match.js");
// ... 省略约 400 行代码,均为垫片
require("core-js/modules/web.url.to-json.js");
require("core-js/modules/web.url-search-params.js");
require("regenerator-runtime/runtime");
var inBrowser = typeof window !== "undefined";
var UA = inBrowser && window.navigator.userAgent.toLowerCase();
var isIE = UA && /msie|trident/.test(UA);
var isEdge = UA && UA.includes("edg/");
var isChrome = UA && UA.includes("chrome") && !isEdge;
function browser() {
return [
"ie: ".concat(isIE),
"edge: ".concat(isEdge),
"chrome: ".concat(isChrome),
].join(", ");
}
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
34
35
36
37
38
39
40
41
这样我们就清楚 useBuiltIns: entry
的效果了,由于我们没有指定目标浏览器,所以产物会将 ES2015-ES2020 转化为 ES5(语法同理),也就是引入 ES2015-ES2020 的所有垫片。no-targets 说明 (opens new window)。接下来我们将指定所需兼容的目标浏览器。targets 属性 (opens new window)
配置所需目标浏览器有以下 三种方式:
- 在
@babel/preset-env
的targets
属性上配置。 - 创建配置文件
.browserslistrc
- 在
package.json
的browserslist
属性上配置。
与之前小节同理,还是用插件中传入配置的方式,加深对 Babel 的理解。可以设置为 targets: "chrome >= 58"
—— 兼容 Chrome,且只兼容到 58 版本。
- 设置
targets:<需要兼容的浏览器>
// run-babel
// ...
const filename = path.resolve(__dirname, "./src/browser.js");
const buildFileName = path.resolve(
__dirname,
"./src/browser-es5-entry-chrome58.js"
);
const data = fs.readFileSync(filename, "utf8");
// 确保入口文件 browser.js 添加了 core-js、regenerator-runtime
const transformData = babel.transformSync(data, {
presets: [
[
"@babel/preset-env",
// 可以开启 debug,来查看兼容了哪些目标浏览器和添加的垫片
{ debug: true, useBuiltIns: "entry", corejs: 3, targets: "chrome >= 58" },
],
],
});
// ...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/browser-es5-entry-chrome58.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.browser = browser;
require("core-js/modules/es.symbol.description.js");
require("core-js/modules/es.symbol.async-iterator.js");
// ... 省略约 50 行代码,均为垫片
require("core-js/modules/web.dom-collections.iterator.js");
require("core-js/modules/web.immediate.js");
require("core-js/modules/web.queue-microtask.js");
require("core-js/modules/web.url.js");
require("core-js/modules/web.url.to-json.js");
require("core-js/modules/web.url-search-params.js");
const inBrowser = typeof window !== "undefined";
const UA = inBrowser && window.navigator.userAgent.toLowerCase();
const isIE = UA && /msie|trident/.test(UA);
const isEdge = UA && UA.includes("edg/");
const isChrome = UA && UA.includes("chrome") && !isEdge;
function browser() {
return [`ie: ${isIE}`, `edge: ${isEdge}`, `chrome: ${isChrome}`].join(", ");
}
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
从上方两个编译产物 src/browser-es5-entry.js
和 src/browser-es5-entry-chrome58.js
的代码对比,其结果减少了很多不必要的垫片。也可以看到 Babel 转化-语法转化 也对目标浏览器进行识别,const
和字符串模板都未转化,因为这些语法 Chrome 58 已经支持了。
根据上方配置,在 babel-loader 进行同样配置就可使用相同的功能:
// build.js
// ...
webpack(
{
// ...
// 添加以下
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
// 传入配置方式 或者 创建配置文件
options: {
presets: [
[
"@babel/preset-env",
{
debug: true,
useBuiltIns: "entry",
corejs: 3,
targets: "<替换成需要兼容的浏览器>",
},
],
],
},
},
},
],
},
}
// ...
);
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
34
注意
记得 清除 上面为了查看效果而添加的代码:browser.js
上的 core-js/stable
和 regenerator-runtime/runtime
。确保这两个文件在全部文件中只引用了一次。 否则按需加载会不起效果,请确保在入口文件中添加这两文件。
# 依据代码和浏览器按需引用
注意
请在本节开始前,请清除 上节 添加的部分代码(上方理由):
# src/browser.js
-import "core-js/stable";
-import "regenerator-runtime/runtime";
const inBrowser = typeof window !== "undefined";
const UA = inBrowser && window.navigator.userAgent.toLowerCase();
const isIE = UA && /msie|trident/.test(UA);
const isEdge = UA && UA.includes("edg/");
const isChrome = UA && UA.includes("chrome") && !isEdge;
export function browser() {
return [`ie: ${isIE}`, `edge: ${isEdge}`, `chrome: ${isChrome}`].join(", ");
}
2
3
4
5
6
7
8
9
10
11
12
13
观察上节编译出的 src/browser-es5-entry-chrome58.js
文件,不难发现有些用不上的 API 也会往里面添加。其实 src/browser.js
文件中 只用 到了以下 API:
String.prototype.toLowerCase
RegExp.prototype.test
String.prototype.includes
(ES6)Array.prototype.join
要加垫片,最多也就这四个。如果 Babel 能对项目使用到的新 API 进行智能识别并添加,这会大大降低包的体积。
针对上述情况,只需将 Babel 的 @babel/preset-env
插件的 useBuiltIns (opens new window) 属性设置成 usage
,Babel 就会根据项目的 API 使用情况来添加垫片。useBuiltIns: "usage" (opens new window)
注意
useBuiltIns: "usage"
模式下请确保全局未引入 core-js
和 regenerator-runtime/runtime
。因为 Babel 将自动识别并在对应的文件中引入相应的垫片,而不是由用户来引入全局性的垫片。
对比: 简单使用,将 run-babel.js
中的 Babel 配置和编译文件进行修改,这样产物对比会更加清楚。
- 设置
useBuiltIns: "usage"
,编译配置和产物如下:
// run-babel.js
// ...
const filename = path.resolve(__dirname, "./src/browser.js");
const buildFileName = path.resolve(__dirname, "./src/browser-es5-usage.js");
// ...
// 确保入口文件 browser.js !!没有!! 引入 core-js、regenerator-runtime
const transformData = babel.transformSync(data, {
presets: [
["@babel/preset-env", { debug: true, useBuiltIns: "usage", corejs: 3 }],
],
});
// ...
2
3
4
5
6
7
8
9
10
11
12
// src/browser-es5-usage.js
"use strict";
require("core-js/modules/es.object.define-property.js");
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.browser = browser;
require("core-js/modules/es.regexp.exec.js");
require("core-js/modules/es.array.includes.js");
require("core-js/modules/es.string.includes.js");
require("core-js/modules/es.array.join.js");
var inBrowser = typeof window !== "undefined";
var UA = inBrowser && window.navigator.userAgent.toLowerCase();
var isIE = UA && /msie|trident/.test(UA);
var isEdge = UA && UA.includes("edg/");
var isChrome = UA && UA.includes("chrome") && !isEdge;
function browser() {
return [
"ie: ".concat(isIE),
"edge: ".concat(isEdge),
"chrome: ".concat(isChrome),
].join(", ");
}
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
提示
由于 Babel 不清楚是 UA
是数组还是字符串,两个类型都有 includes
方法,所以都会添加。
不难发现现在的产物已经非常简洁,只有了 4 个垫片。
- 设置
targets:<需要兼容的浏览器>
接下来再添加目标浏览器 targets: "chrome >= 58"
,移除浏览器已经支持的 API。编译配置和产物如下:
// run-babel.js
// ...
const filename = path.resolve(__dirname, "./src/browser.js");
const buildFileName = path.resolve(
__dirname,
"./src/browser-es5-usage-chrome58.js"
);
// ...
// 确保入口文件 browser.js !!没有!! 引入 core-js、regenerator-runtime
const transformData = babel.transformSync(data, {
presets: [
[
"@babel/preset-env",
{ debug: true, useBuiltIns: "usage", corejs: 3, targets: "chrome >= 58" },
],
],
});
// ...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/browser-es5-usage-chrome58.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.browser = browser;
require("core-js/modules/es.regexp.exec.js");
const inBrowser = typeof window !== "undefined";
const UA = inBrowser && window.navigator.userAgent.toLowerCase();
const isIE = UA && /msie|trident/.test(UA);
const isEdge = UA && UA.includes("edg/");
const isChrome = UA && UA.includes("chrome") && !isEdge;
function browser() {
return [`ie: ${isIE}`, `edge: ${isEdge}`, `chrome: ${isChrome}`].join(", ");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
上面的 src/browser-es5-usage.js
、src/browser-es5-usage-chrome58.js
文件对比,可以发现现在的代码只需一个垫片就可在 Chrome 58 上运行的,其中使用的 String.prototype.includes
和 模板字符串功能均在 Chrome 58 支持。
# 打包配置
根据上方相同的配置,在 babel-loader 同样配置即可使用相同的功能:
注意
请确保所有文件没有 core-js
和 regenerator-runtime/runtime
。
// build.js
webpack({
// ...
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
// 传入配置方式 或者 创建配置文件
options: {
presets: [
[
"@babel/preset-env",
{
debug: true,
useBuiltIns: "usage",
corejs: 3,
targets: "<替换成需要兼容的浏览器>",
},
],
],
},
},
},
],
},
});
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
# 避免全局污染和重复辅助函数
将 build.js
的 babel 配置改为 ie >= 11
。然后运行 node build.js
,再到 IE 浏览器运行 index.html
文件,正常运行。但如果打开控制台,输入 Promise
或 String.prototype.includes
等,可以发现原本 不支持 的 API,现在却可以在控制台 正常运行。这是 babel 将新 API 的实现函数注入到了浏览器运行环境里,这是非常严重的代码入侵行为。这会让用户运行的代码出现非预期的结果,作为一个合格的库是不允许修改原型链等变量的。
其实还有个问题,先假设我们每个文件都有使用 class
、regenerator
或 async、await
,在原内容下使用这些语法。
// src/browser.js
// ...
// 类
class Browser {
constructor(value) {
this.value = value;
}
getVal() {
return `value: ${this.value}`;
}
}
// async、await
(async function() {
await new Promise((resolve) => setTimeout(resolve, 2000));
// 两秒后显示打印类
console.log(Browser);
})();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/util.js
// ...
// 类
class Util {
constructor(value) {
this.value = value;
}
getVal() {
return `value: ${this.value}`;
}
}
// async、await
(async function() {
await new Promise((resolve) => setTimeout(resolve, 2000));
// 两秒后显示打印类
console.log(Util);
})();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/element.js
// ...
// 类
class Element {
constructor(value) {
this.value = value;
}
getVal() {
return `value: ${this.value}`;
}
}
// async、await
(async function() {
await new Promise((resolve) => setTimeout(resolve, 2000));
// 两秒后显示打印类
console.log(Element);
})();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这时候在 mode: "none"
模式下运行 node build.js
,打开 dist/library.js
,查询生成出的 _classCallCheck
、_asyncToGenerator
声明辅助函数。它们的数量均为 3 个。
如果你认为 mode: "production"
模式下,Webpack 会进行合并优化等。可以尝试在该模式下运行,然后在产物中查询 Cannot call a class as a function
字符串,因为变量会被压缩,而 _classCallCheck
函数内部会包含这个字符串,也就可以当作 _classCallCheck
的计数。_asyncToGenerator
也同理。结果就是 Cannot call a class as a function
字符串能查询出 3 个。
这就说明多一个包含 class
、regenerator
或 async、await
内容的文件,就会多个相应的辅助函数。假设是 100 个文件,那么就是 100 个相同的辅助函数。这会无意义的增加包的体积。
# 改造
Babel 的 @babel/plugin-transform-runtime
插件支持为库的代码提供一个沙盒环境,也可以将重复的 regenerator
、class
辅助函数提取出来。
# 安装开发依赖
npm install @babel/plugin-transform-runtime --save-dev
# 安装生产依赖,请选择对应 corejs 的版本安装
# corejs false
npm install @babel/runtime
# corejs 2
npm install @babel/runtime-corejs2
# corejs 3
npm install @babel/runtime-corejs3
2
3
4
5
6
7
8
9
注意
- 这需要安装两个包,因为 Babel 将辅助函数统一放到了
@babel/runtime
编译出来的代码需要用到,而@babel/plugin-transform-runtime
的作用是编译的时候将各个地方原本用到的辅助函数(单独生成或污染全局)切换成@babel/runtime
里的辅助函数。 @babel/preset-env
的useBuiltIns
不能 与@babel/plugin-transform-runtime
混用,因为这两个功能是 互斥 的。一个是全局加上垫片,一个是单独添加,避免全局污染。Babel 作者的回答 (opens new window)
我们可以将 run-babel.js
中的 Babel 配置和编译文件进行修改,这样产物对比会更加清楚。
使用 @babel/plugin-transform-runtime
,编译配置和产物如下:
// run-babel.js
// ...
const filename = path.resolve(__dirname, "./src/browser.js");
const buildFileName = path.resolve(
__dirname,
"./src/browser-es5-ie11-runtime.js"
);
const data = fs.readFileSync(filename, "utf8");
// 配置会读取根目录下的 babel.config.json 或 .babelrc.json (也有其他的扩展名文件)
// const transformData = babel.transformSync(data);
// 指定配置
const transformData = babel.transformSync(data, {
presets: [
[
"@babel/preset-env",
// 可以开启 debug,来查看兼容了哪些目标浏览器和添加的垫片
{ debug: true, targets: "ie >= 11" },
],
],
// @babel/preset-env 的 useBuiltIns 不能与 @babel/plugin-transform-runtime 混用
plugins: [["@babel/plugin-transform-runtime", { corejs: 3 }]],
});
// ...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/browser-es5-ie11-runtime.js
"use strict";
var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property");
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
_Object$defineProperty(exports, "__esModule", {
value: true,
});
exports.browser = browser;
var _regenerator = _interopRequireDefault(
require("@babel/runtime-corejs3/regenerator")
);
var _asyncToGenerator2 = _interopRequireDefault(
require("@babel/runtime-corejs3/helpers/asyncToGenerator")
);
var _classCallCheck2 = _interopRequireDefault(
require("@babel/runtime-corejs3/helpers/classCallCheck")
);
var _createClass2 = _interopRequireDefault(
require("@babel/runtime-corejs3/helpers/createClass")
);
var _includes = _interopRequireDefault(
require("@babel/runtime-corejs3/core-js-stable/instance/includes")
);
var _promise = _interopRequireDefault(
require("@babel/runtime-corejs3/core-js-stable/promise")
);
var _setTimeout2 = _interopRequireDefault(
require("@babel/runtime-corejs3/core-js-stable/set-timeout")
);
var inBrowser = typeof window !== "undefined";
var UA = inBrowser && window.navigator.userAgent.toLowerCase();
var isIE = UA && /msie|trident/.test(UA);
var isEdge = UA && (0, _includes.default)(UA).call(UA, "edg/");
var isChrome = UA && (0, _includes.default)(UA).call(UA, "chrome") && !isEdge;
function browser() {
return [
"ie: ".concat(isIE),
"edge: ".concat(isEdge),
"chrome: ".concat(isChrome),
].join(", ");
} // 类
var Browser = /*#__PURE__*/ (function() {
function Browser(value) {
(0, _classCallCheck2.default)(this, Browser);
this.value = value;
}
(0, _createClass2.default)(Browser, [
{
key: "getVal",
value: function getVal() {
return "value: ".concat(this.value);
},
},
]);
return Browser;
})(); // async、await
(0, _asyncToGenerator2.default)(
/*#__PURE__*/ _regenerator.default.mark(function _callee() {
return _regenerator.default.wrap(function _callee$(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
_context.next = 2;
return new _promise.default(function(resolve) {
return (0, _setTimeout2.default)(resolve, 2000);
});
case 2:
// 两秒后显示打印类
console.log(Browser);
case 3:
case "end":
return _context.stop();
}
}
}, _callee);
})
)();
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
可以发现:
- 转化的代码是通过 变量 和 自执行函数 去执行的,而不是直接
require
使用副作用来修改环境变量、原型链。 - 辅助函数 使用的是
@babel/runtime-corejs
,而不是每个文件都实现。 includes
,其结果与 官方示例 (opens new window) 类似。await async
(异步函数),其结果与 官方示例 (opens new window) 类似。class
,其结果也类似 官方示例 (opens new window)。
# 打包配置
根据上方相同的配置,在 babel-loader 同样配置即可使用相同的功能:
// build.js
webpack(
{
// ...
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
// 传入配置方式 或者 创建配置文件
options: {
presets: [
["@babel/preset-env", { debug: true, targets: "ie >= 11" }],
],
plugins: [["@babel/plugin-transform-runtime", { corejs: 3 }]],
},
},
},
],
},
}
// ...
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
运行 node build.js
,打开 index.html
,IE 上正常运行。在 IE 控制台输入 String.prototype.includes
或 Promise
API,其结果为 undefined
或 未定义
,这就说明库的代码并未入侵到运行环境中。
同时查看 mode: "none"
输出的 dist/index.js
产物的代码行数,为 5002 行。对比之前未优化的 Babel 配置所输出的 15827 行代码,减少 了将近 三分之二。
提示
代码行数不一定和作者的结果一致,Webpack 和 Babel 的版本、代码的写法与注释都会影响代码行数。
整理
为了方便后续章节展开,将 src/browser.js
以各种模式输出的
src/browser.js
(复制)src/browser-es5-false.js
(迁移)src/browser-es5-entry.js
(迁移)src/browser-es5-entry-chrome58.js
(迁移)src/browser-es5-usage.js
(迁移)src/browser-es5-usage-chrome58.js
(迁移)src/browser-es5-ie11-runtime.js
(迁移)
移动至 babel-example
文件夹下。