Babel
Babel 是一个 JavaScript 编译器,它可以将ES6+语法编译为浏览器支持的ES5语法。要学好babel
必须先理解相关的概念,但是你刚起步就去扣这些细节的话,很可能因为babel
一些复杂而模糊的概念打击你的信息。所以我们先从最简单的开始,然后深入。
最简单的例子
接下来我们尝试按照官方文档做一个最简单的例子
安装babel-cli
npm install --save-dev babel-cli
添加babel编译命令
"build": "babel src -d lib"
添加.babelrc
配置文件
{
"presets": [],
"plugins": []
}
添加JS文件
// /src/main.js
const fetch = args => args;
console.log(Promise);
执行 npm run build
,我们发现编译后的文件没有发生任何的改变,就是简单的原样输出
// /lib/main.js
const fetch = args => args;
console.log(Promise);
为什么呢?这是因为在.babelrc
文件没有做任何的配置。我们一共使用了三个新特性,分别是常量申明const
,箭头函数=>
,新全局变量Promise
。接下来我门尝试将他们编译为ES5代码。
如上图所示,借助plugin
可以实现我们的目标。接下来我们分别实现它:
const
原文链接 文档中说明:
babel-plugin-check-es2015-constants
这个插件仅仅是验证const
变量的规则,比如不能重复申明,不可变等特性。
把它编译为ES5代码需要借助babel-plugin-transform-es2015-block-scoping
我们设置需要编译的代码为:
const fetch = args => args;
fetch = 55; // 故意写错
console.log(Promise);
安装上面所需的两个plugin
并改写.babelrc
配置:
{
"presets": [],
"plugins": [
"check-es2015-constants",
"transform-es2015-block-scoping"
]
}
编译过程报错验证check-es2015-constants
生效
我们修改需要编译的代码为:
const fetch = args => args;
console.log(Promise);
再次编译成功
var fetch = args => args;
console.log(Promise);
到这里我们已经成功的使用插件对const
的语法和转译(Syntax
和 transform
)ES5化。是不是很开心呢? 接下来我们处理箭头函数=>
。
arrow functions
同样安装对应的plugin
:
npm install --save-dev babel-plugin-transform-es2015-arrow-functions
这里延用上面需要compile的源代码:
var fetch = args => args;
console.log(Promise);
改写.babelrc
配置文件:
{
"presets": [],
"plugins": [
"check-es2015-constants",
"transform-es2015-block-scoping",
"transform-es2015-arrow-functions"
]
}
编译结果:
var fetch = function (args) {
return args;
};
console.log(Promise);
编译成功,unbelievable, we did it!!!
,是不是真的很简单?先别得意???,还有一个Promise
没有解决呢。有惊喜哟!!!
Promise
我有一个问题:我特意将上面的两个例子放在一起,是因为他们都属于对新语法Syntax
进行编译,而Promise
是作为一个全局api
的存在,在大部分浏览器中是不存在这个全局api
的,如果让你来解决这个问题,你会怎么做?
你的答案:是的,没错!就是在不支持Promise
的环境中实现Promise
,在babel
中被称为polyfill
,注意它和shim
的区别哦~
babel-polyfill
文档最简单的使用:
npm install --save-dev babel-polyfill
Use it by requiring it at the top of the entry point to your application or in your bundler config. —在入口文件的最顶层作用域直接引入
到这里我们已经完成既定的3个目标,但是你有没有想过,随着我们ES6新特性的增加,plugin
的长度也逐渐增加,可以遇见会有多长?,而且自己去找这些对应的plugin
也是比较麻烦的。有没有傻瓜式的集成方法呢?您接着往下看。
Preset
为了方便,Babel团队将一些Plugins
集合在一起,并称之为preset
。所以一个preset
是一系列plugin
的总和。按照年份划分:
ES2015
/ ES-2016
/ES2017
等等,还延伸了stage
的概念,详情请查阅官网。
babel-preset-env
A Babel preset that compiles ES2015+ down to ES5 by automatically determining the Babel plugins and polyfills you need based on your targeted browser or runtime environments.
不需要指定任何的
plugin
和
polyfill
,Babel preset 可以将ES6+的新语法向下编译为ES5代码,按照你指定的运行环境。具体的配置项请看官网。Without any configuration options, babel-preset-env behaves exactly the same as babel-preset-latest (or babel-preset-es2015, babel-preset-es2016, and babel-preset-es2017 together).
不需要做任何配置选项,它和babel-preset-latest
表现一致,或者说和babel-preset-es2015
,babel-preset-es2016
,babel-preset-es2017
4个preset总和一致。
感觉是不是很牛逼!下面我们来尝试一个例子。为防止有任何的代码污染,我卸载所有npm package
最初的样子:
{
"name": "babelrc",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "babel src -d lib"
},
"author": "",
"license": "ISC",
"devDependencies": {}
}
我安装时发现@babel/preset-env
还处于bate
版本,使用时还有一些问题,所以决定还是使用老版本。
(1)安装babel-preset-env
上图为babel-preset-env
的一些依赖,主要为一些helper
和plugin
。
为了验证出babel-preset-env
的是否满足要求,我新增了几行包括ES6的代码:
const fetch = args => args;
console.log(Promise);
// 新增一些代码
class G {
}
let [a, b, c] = [1, 2, 3];
(async () => {
await console.log(1)
})();
(2)配置.babelrc
{
"presets": ["env"],
"plugins": []
}
(3)编译结果
"use strict";
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var fetch = function fetch(args) {
return args;
};
console.log(Promise);
// 新增一些代码
var G = function G() {
_classCallCheck(this, G);
};
var a = 1,
b = 2,
c = 3;
_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return console.log(1);
case 2:
case "end":
return _context.stop();
}
}
}, _callee, undefined);
}))();
我就问还有谁???所有的新语法特性都被成功处理了。
Runtime
babel为源代码非实例方法(比如Object.assign
)和 babel-runtime/helps
下的工具函数自动引用了polyfill。这样可以避免全局空间的污染,非常适合用于JS库和工具包的实现。但是实例方法(比如someString.includes("target")
)还是需要使用babel-polyfill
。
如果你是新手,你可能没有注意到,babel编译时会在每个文件生成一些需要帮助函数,如果文件比较多,那么这些重复的代码会增加编译后的代码体积。下面是一个例子:
源代码
class G {
}
.babelrc
配置
{
"presets": ["env"],
"plugins": []
}
编译代码
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var G = function G() {
_classCallCheck(this, G);
};
// _classCallCheck就是一个内部生成的帮助函数
为了优化编译体积,babel
团队推出了 babel-plugin-transform-runtime
+ babel-runtime
来解决这个问题。先看一下直观体验:
源代码
class G {
}
.babelrc
配置
{
"presets": ["env"],
"plugins": ["transform-runtime"]
}
编译后的代码:
"use strict";
var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck");
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var G = function G() {
(0, _classCallCheck3.default)(this, G);
};
通过对比可以看出,第二种方案直接从babel-runtime
引入babel-runtime/helpers/classCallCheck
,避免自己定义,从而减少代码的体积。
除了这个优点意外
babel-runtime
三个主要部分:core.js
+ helpers
+ regenerator
当然除了上面的方法,通过按需引入 polyfills
和 transforms
更能带来更多的体积优化。
减少对不必要浏览器的兼容来减少 polyfills
和 transforms
的引入:
{
"presets": [
["env", {
"targets": {
// The % refers to the global coverage of users from browserslist
"browsers": [ ">0.25%", "not ie 11", "not op_mini all"]
}
}]
]
}
答疑
(1)可能你发现了我并没有安装babel-cli
,为什么我能使用babel
命令?
因为我在全局安装了babel
。
(2) browserslistrc 配置更改之后 babel编译后的代码怎么没有改变。或者说 browserslistrc 影响babel编译的具体表现是怎么样的?
暂时我也不知道,我正在研究,后面更新。
参考
[1] browserslist
[2] browserslist-example
[3] browserslist-queries
由于本人表达能力真的很差,表达不够清楚还望大家多多包涵。