Babel是什么
套用官网的一句话:
Babel is a JavaScript compiler. Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in old browsers or environments.
就是说,Babel是一个JavaScript编译器,是一个能把ES6及以上的代码编译成ES5代码的工具链。
提取两个关键字:编译器、工具链。
归结一点就是,Babel首先是一个编译器。然后,所有代码转换(ES6=>ES5)处理都是通过一个一个插件去做的。
对应起来,就是:Babel = babel-core + babel-plugin-xx
babel-core
babel-core就是babel的编译器。
核心的一个方法就是 babel.transform(code: string, options?: Object)
。
就是把我们代码转换成一个对象,这个对象包含:生成的代码,Source Map,AST。
babel-plugin
babel-plugin非常多,为了方便使用,babel引入一个概念叫babel-preset。
babel-preset
babel-preset本质就是插件的集合,比如有:babel-preset-es2015, babel-preset-es2016, babel-preset-es2017。
还有一个babel-preset-env
就是所有babel-preset的集合。
上面这些都是ECMA标准定义好的。
babel-stage
但是同时,又有一些很实用的草案,或者非常大几率会被ECMA采用的一些标准或者规范。
这么一来,就有了一个叫babel-stage-x
,目前有:babel-stage-0, …, babel-stage-3
然后,有意思的是,也是为了方便使用,在用的时候你只需要引入最近的最小版本,就默认引入了所有大于它的版本。
比如:直接使用babel-stage-0
,就相当于使用了babel-stage-0, …, babel-stage-3,也就相当于使用了所有的babel-stage。
其他
之外,还有babel-preset-flow, babel-preset-react, babel-preset-minify。
这个重要是配合社区需要,针对某种特别的语法,做的转换处理。
Babel-Polyfill
但是,Babel并不能转换所有的新特性,主要是原型链上的方法。比如:"footbar".includes("foo")
,这个babel是无法转换的。
那为什么呢?主要有两个原因:
- JavaScript是弱类型语言,变量的定义有:var, let, const。除了const之后,前两个都是随时可以更改变量类型的。
- Babel本身还没有强大到知道你调用的这个方法一定是ES5+里新加的,万一你有自己的实现和定义呢?
Babel-Polyfill的本质就是实现在ES6,ES7规范里定义的原型链方法,静态方法,以及新的全局变量。
它会直接修改全局对象,定义全局变量。
这样一来,你就必须在入口引入babel-polyfill,或者在打包的地方加到里面。
最后,需要指出的是,Babel-Polyfill并不能实现所有的新特性和新方法。比如:Reflect
。
Babel-Runtime
通过上面的介绍,看着是所有的问题都已经解决了。
当然,事实上也是如此。
那为什么还需要说babel-runtime
呢?
这就要从babel-plugin说起了,plugin的默认行为是直接把对应代码编译到输出的代码里,如果一个js里有到多地方用到了同一个东西,那么,每次引入的地方,就会重复生成同样的代码。这样就会造成不必要的冗余代码。比如:
// a.js
import B from './b.js';
import C from './c.js';
// b.js
Promise.resolve().then(()=> {
console.log('b');
});
// c.js
Promise.resolve().then(()=> {
console.log('c');
});
// 编译后 a.js
// 包含两段一模一样的Promise代码,分别在编译后的b.js和c.js中。
(function(module, exports) {
// b.js的Promise源码
})
(function(module, exports) {
// c.js的Promise源码
})
(function(module, exports, __webpack_require__) {
var A = __webpack_require__(1);
var B = __webpack_require__(2);
})
这么一来,就有了babel-runtime
,它的出现就是为了解决这个冗余的问题。
那么它是怎么解决的呢?
我们先来看看babel-runtime的配置:
helpers
通过模块引入的方式来替代之前的直接行内输出的帮助方法(inline Babel helpers),像编译class关键字的classCallCheck,关键字extends。
默认是true,即使用模块引入的方式。总的来说,这样做主要还是考虑到能减少代码的。
polyfill
判断是否要引入了core-js,设置为true的话,新的静态方法和全局变量就会做对应的编译。
可以理解为按需加载需要的东西。但是对某些原型链上的方法,也只能说无能无力了。
为什么呢?主要还是因为js是弱语言,可以随时改变类型,这样一来就无法正确分析当前调用的方法名是你自己定义的,还是原型上的。
如果你已经在全局引入了babel-polyfill的话,这块就需要设置为false。当然,babel-polyfill体量是非常的大。
默认为true。
regenerator
是否直接使用全局的生成器函数。也就是说,已经在全局里定义了生成器函数。
默认为ture,即不使用全局生成器函数。
moduleName
自定义这个runtime的名字,这样可以在代码里直接引用(主要是使用helpers),默认babel-runtime
。
最佳实践
如果不是特别关心代码大小的话,最好的方式是直接用babel-polyfill + babel-runtime
模式。即:
- 在代码第一行或打包配置里引入
babel-polyfill
。 - 配置.babelrc或package.json
.babelrc:
{
"presets": [
"env",
"stage-0"
],
"plugins": [
"transform-runtime"
]
}
package.json:
{
"babel": {
....babelrc
}
}
简单优化版
因为babel-polyfill确实非常的大,如果想做优化的话,考虑只引入自己需要的那部分。
比如,绝大部分情况下,我们只用到Array, Object, String。那么我们只引入对应的ES6, ES7代码,即:
require('core-js/es6/array');
require('core-js/es6/string');
require('core-js/es6/object');
require('core-js/es7/array');
require('core-js/es7/string');
require('core-js/es7/object');
babel配置:
{
"presets": [
"env",
"stage-0"
],
"plugins": [
"transform-runtime"
]
}
不足
会造成必要的冗余,主要体现在静态方法上。
比如:Array.from
,其实我们已经引入了,但是在代码里依旧会编译。
高级优化版
说到这一点,必须说一下,以前我们在IE8的ES5兼容里的做法。
那就是通过HACK的方式,也就是在HTML里加上IE8及一下专属的HACK模式:
<!--[if lt IE 9]>
<script src="/s/es5-shim-50bbaee56e.js" crossorigin="anonymous"></script>
<script src="/s/es5-sham-250746e0b5.js" crossorigin="anonymous"></script>
<![endif]-->
同理,其实随着浏览器不断的更新,真正不支持的浏览器占比越来越小。我们只需要针对不支持的浏览器额外加载相应的polyfill代码即可。
一种是,代码里判断,即:使用Promise来判断是否支持ES6,不支持,则加载对应的js:
if (!window.Promise) {
//load es6 script synchronously
}
另一种是,通过User-Agent判断,一般情况下只要判断User-Agent有没有Chrome/并且不是iOS即可:
// xx.pug
- const userAgent = (Context.get('User-Agent') || '');
unless userAgent.includes('Chrome/') && !userAgent.includes('iOS')
script(src="./path/to/es6.js")
参考
原文网址:https://yss.github.io/2018/08/16/the-guide-of-babel.html
转载请注明。谢谢~