平时在开发的过程中,我们可能并不太需要十分了解babel的内容,仅仅知道它能够将新特性的代码转换成能够在旧版本浏览器中运行的代码。但是这一次想要趁着自己搭建脚手架的机会去进一步的了解babel的知识,所以写了这篇文章。以下内容是babel 7.4之后的版本,也就是@babel/polyfill
被废弃需要独立安装core-js
和 regenerator-runtime
模块的版本。
babel命令行工具 @babel/cli
@babel/cli
是babel的命令行工具,主要提供babel
命令。另外还需要安装@babel/core
才能使用babel去编译。
npm install --save-dev @babel/core @babel/cli
将命令配置在 package.json 文件的 scripts 字段中:
// package.json
"scripts": {
"compiler": "babel src --out-dir lib --watch"
}
这样就能够通过npm run compiler
来执行编译,但是babel本身什么都不做,需要添加插件来帮助babel完成工作。
plugin
babel所有功能都建立在各种的plugin上,使用方式是安装相应的plugin
再去配置文件中去使用。例如箭头函数转换插件,
安装@babel/plugin-transform-arrow-functions
,然后在.babelrc
配置文件中去指定对应的插件
//.babelrc
{
plugins: ["@babel/plugin-transform-arrow-functions"],
};
然后执行npm run compiler
,可以看到箭头函数已经被编译完成
但是如果我们每个功能都去一个个添加对应的plugin
会很麻烦,多以我们就需要preset
预设去直接添加一组插件。
preset
preset
就是一组插件的集合,最常用的preset
就是@babel/preset-env
。
@babel/preset-env
它的作用是根据目标环境去进行语法转换和导入对应的polyfill
。
需要注意的是,@babel/preset-env
会根据你配置的目标环境,生成插件列表来编译。默认情况下,如果你没有在 Babel 配置文件中(如 .babelrc)设置 targets 或 ignoreBrowserslistConfig,@babel/preset-env
会使用 package.json
的browserslist
配置源。
我们可以模拟生产环境和开发环境的浏览器版本
const product = ["ie >= 9"];
const development = ["last 2 Chrome versions"];
通过设置不同浏览器环境使用@babel/preset-env
去编译相同代码,可以看到最终的结果也会不同。
module.exports = {
presets: [
[
"@babel/preset-env",
{
// targets: product,
targets: development,
},
],
],
};
babel 只负责对语法进行编译,比如当我们写箭头函数,babel 会帮你把它编译成普通函数。但是对一些新的扩展方法,新的类来说babel就不能转换了。这时就需要去引入polyfill
,polyfill
的中文意思是垫片,所谓垫片就是垫平不同浏览器或者不同环境下的差异,让新的内置函数、实例方法等在低版本浏览器中也可以使用。
polyfill
babel v7.4版之后,需要直接安装core-js
和 regenerator-runtime
去替代之前的@babel/polyfill
。croe-js
提供了 ES5、ES6 规范中新定义的各种对象、方法的polyfill,regenerator-runtime
用来实现 ES6/ES7 中 generators、yield、async 及 await 等相关的 polyfill。
首先,我们需要安装他们到生产环境中,因为需要在生产环境中运行其中的polyfill
。
npm install --save core-js regenerator-runtime
在@babel/preset-env
的配置项中把useBuiltIns
设置成usage
,这样会根据目标浏览器环境去引入所需要的polyfill
。需要注意点是,设置useBuiltIns
还需要同时设置corejs
。
//.babelrc
const product = ["ie >= 9"];
const development = ["last 2 Chrome versions"];
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: development,
useBuiltIns: "usage",
corejs: 3,
},
],
],
};
//index.js
const isHas = [1, 2, 3].includes(2);
const getData = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(100);
}, 1000);
});
const main = async () => {
const res = await getData();
console.log(res);
};
main();
可以看到,编译后的文件中只引入了所用到的polyfill。
useBuiltIns
还可以设置成其他值,比如entry
,这需要在项目入口文件手动引入polyfills,例如@babel/polyfill
或者core-js
//.babelrc
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: product,
useBuiltIns: "entry",
corejs: 3,
},
],
],
};
//index.js
// 入口文件引入core-js
require("core-js");
但是这种方式会引入全量的polyfill。
useBuiltIns
默认值为false
,代表每个文件里不自动添加polyfill,或不将import "@babel/polyfill"
转换为单独的polyfill。
@babel/plugin-transform-runtime
@babel/plugin-transform-runtime可以重复使用 Babel 注入的帮助程序
在使用@babel/preset-env
配合useBuiltIns: usage
时,文件中会引入一些辅助方法例如_classCallCheck
,当多处文件都使用到class时同样也会在每个文件中去引入这些辅助方法,这样会增大打包体积并且完全没有必要多次去引入同样的辅助方法。
//index.js
class A {}
//.babelrc.js
const product = ["ie >= 9"];
const development = ["last 2 Chrome versions"];
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: product,
useBuiltIns: "usage",
corejs: 3,
},
],
],
};
编译结果:
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var A = function A() {
_classCallCheck(this, A);
};
为了解决这个问题就需要使用@babel/plugin-transform-runtime
,使用该插件,所有辅助方法都将引用模块 @babel/runtime
,这样就可以避免编译后的代码中出现重复的辅助方法,有效减少包体积。
@babel/plugin-transform-runtime
需要配合@babel/runtime
来使用,@babel/plugin-transform-runtime
在开发时使用,最终代码需要依赖@babel/runtime
。
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
//index.js
class A {}
//.babelrc.js
const product = ["ie >= 9"];
const development = ["last 2 Chrome versions"];
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: product,
useBuiltIns: "usage",
corejs: 3,
},
],
],
//使用@babel/plugin-transform-runtime
plugins: [["@babel/plugin-transform-runtime"]],
};
编译结果:
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var A = function A() {
(0, _classCallCheck2.default)(this, A);
};
可以看到这些辅助方法都是从@babel/runtime
中引入。
@babel/plugin-transform-runtime可以创建一个沙盒环境来避免对全局环境的污染
之前在使用@babel/preset-env
编译promise和includes时会引入core-js
中的全局变量或者在对应的原型链中添加相应的方法,这样都造成了全局环境的污染。虽然这对于应用程序或命令行工具是可以的,但是如果你的代码是要发布供他人使用的库,或者无法完全控制代码运行的环境,则将成为一个问题。
首先,单独使用@babel/plugin-transform-runtime
只能够处理辅助方法,如果想要去引入polyfill
就需要配合@babel/runtime-corejs3
使用。
同样还是在生产环境安装@babel/runtime-corejs3
。
npm install @babel/runtime-corejs3 --save
这里需要在.babelrc
中去除@babel/preset-env
配置中关于polyfill
的部分以免与@babel/runtime-corejs3
重复。
//index.js
const isHas = [1, 2, 3].includes(2);
const getData = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(100);
}, 1000);
});
getData();
//.babelrc.js
module.exports = {
presets: [["@babel/preset-env"]],
plugins: [
[
"@babel/plugin-transform-runtime",
{
corejs: 3,
},
],
],
};
编译结果:
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _setTimeout2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/set-timeout"));
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);
var getData = function getData() {
return new _promise["default"](function (resolve, reject) {
(0, _setTimeout2["default"])(function () {
resolve(100);
}, 1000);
});
};
getData();
可以看到,使用@babel/plugin-transform-runtime
会用一个临时变量去保存polyfill中的一些值,并不是直接去修改原型链或者新增Promise方法。
在一般开发中使用@babel/preset-env
配合useBuiltIns: usage
,在开发第三方库时使用@babel/plugin-transform-runtime
在上面介绍@babel/plugin-transform-runtime
的一些使用时可以看到,它不仅能够处理引入多次helper辅助方法的问题,而且在只引入所需polyfill时还不会污染全局环境,那还有必要使用@babel/preset-env
的useBuiltIns
吗?
其实@babel/plugin-transform-runtime
配合@babel/runtime-corejs3
引入polyfill有一个很大的不足就是不能够通过设置目标环境去引入所需要的polyfil。,我们在普通开发时只需要在package.json
中的browserslist
去设置开发环境和生产环境的浏览器版本,然后通过使用@babel/preset-env
的useBuiltIns
就能够根据不同的运行环境去引入适当的polyfill。
但是在开发第三方库时,不能确定代码的运行环境,所以就需要利用@babel/plugin-transform-runtime
来保证引入的polyfill不去污染全局环境。
最后总结
一般开发: 通过useBuiltIns: usage
去保证引入恰当的polyfill,通过@babel/plugin-transform-runtime
保证辅助函数都是引用@babel/runtime
`。
const product = ["ie >= 9"];
const development = ["last 2 Chrome versions"];
module.exports = {
presets: [
[
"@babel/preset-env",
{
//代替browserslist设置浏览器版本
targets: product,
useBuiltIns: "usage",
corejs: 3,
},
],
],
plugins: [["@babel/plugin-transform-runtime"]],
};