babel是干嘛的?
现在javascript的正式版本已经到ES2017了,也就是ES8,说明一下:
- ES6 === ES2015
- ES7 === ES2016
- ES8 === ES2017
我们现在用的新版本的javascript语法(主要是ES6)与一些浏览器不兼容,那么就需要把我们的代码编译并生成浏览器兼容的语法(默认是ES5),这就是babel需要做的工作。
如何使用?
Babel 是一个编译器,编译我们的代码分为3步:
- 解析(得到源代码,什么都不做)
- 转换(将ES6等不兼容语法转换成ES5)
- 生成代码
最重要的是第二步:转换,需要通过插件来定义转换规则。
Babel提供了很多的接口,供我们编写自己的插件,转换我们的代码。
what? 要自己写插件?
当然不需要我们自己写啦!
babel官方提供了很多的插件,比如解析箭头函数的插件:
es2015-arrow-functions
这个插件只能转换箭头函数,还有许多的插件,官方把它们集成到了preset中。
如果使用webpack等构建工具,通常有一个.babelrc
文件,这个就是babel 的配置文件,配置文件中有两个大项presets
和pliguns
{
"presets": [
["env", {
"debug": true,
"modules": false,
"targets": {
"browsers": ["> 1%", "last 3 versions", "not ie <= 9"]
}
}]
],
"plugins": ["transform-runtime"]
}
presets 和 plugins 的区别
presets其实是多个plugin的集合。
例如:ES2015的插件集合:
- check-es2015-constants
- es2015-arrow-functions
- es2015-block-scoped-functions
- es2015-block-scoping
- es2015-classes
- es2015-computed-properties
- es2015-destructuring
- es2015-duplicate-keys
- es2015-for-of
- es2015-function-name
- es2015-literals
- es2015-object-super
- es2015-parameters
- es2015-shorthand-properties
- es2015-spread
- es2015-sticky-regex
- es2015-template-literals
- es2015-typeof-symbol
- es2015-unicode-regex
如果ES2015这个插件集合还不能满足我们的需要,就需要添加其他的插件。
例如,我们在做vue和react开发的时候需要用到 jsx ,这个时候就需要一些其他的插件:
vue的jsx支持
- transform-vue-jsx
react的 jsx 支持
- react
需要polyfill的支持,但是又不想直接引入polyfill(有弊端),可以使用
- transform-runtime
note: 所有的这些插件的前缀都是 babel-preset-
和babel-plugin-
,只是如果插件名前缀是以 "babel-plugin-"和babel-preset-
开头,那么就可以省略z这些前缀
env 和 es2015、es2016、es2017和 stage-x 的区别
env同时包含了es2015、es2016、es2017以及最新版本,也是官方推荐的。
每年每个 preset 只编译当年批准的内容。 而 babel-preset-env 相当于 es2015 ,es2016 ,es2017 及最新版本。
所以es2015(ES6)会编译2015年通过javascript提案,成为正式版本的那一部分语法。 以此类推。
es2015、es2016、es2017编译javscript正式版本的那一部分,stage-x会编译未被批准为 JavaScript 的正式版本,即试用和实验阶段的javascript提案。
TC39 将提案分为以下几个阶段:
- Stage 0 - 稻草人: 只是一个想法,可能是 babel 插件。
- Stage 1 - 提案: 初步尝试。
- Stage 2 - 初稿: 完成初步规范。
- Stage 3 - 候选: 完成规范和浏览器初步实现。
- Stage 4 - 完成: 将被添加到下一年度发布。
一般在开发项目时会加上stage-2。
babel-plugin-presets 和 polyfill 的区别
babel是将你的语法转换为你目标浏览器支持的语法,默认转换es5,例如:
const a=10; ===> var a = 10;
let a = 20; ===> var a =20;
a=>10;(箭头函数) ===> (function(a) {
return 10;
});
polyfill补充一些对象的实例方法、静态方法的支持,以及可以使用新的内置对象:
//新的内置对象
new Promise((resolve,reject)=>{});
new WeakMap();
//静态方法:
Array.from
//实例方法:
Object.assign
Array.prototype.includes
transform-runtime 和 polyfill 的区别
普通导入polyfill的方法是在入口文件导入:
entry:{
index:["babel-polyfill","index.js"]
}
在代码中导入:
//index.js
import "babel-polyfill"
transform-runtime 是一个插件,在.babelrc中配置,会根据的你的代码,自动引用按需要被转换的polyfill。
主要有两个不同:
- import “babel-polyfill” 是一次性全部导入,有时候你只是用了其中一小部分需要转换的实例方法/静态方法,导入了很大一个文件,其中很大一部分是没有必要的,增加文件体积,虽然可以按需引入,但是特别麻烦。
- import “babel-polyfill” 是直接全局导入的(没看过源码,估计是 直接在内置对象上修改了原型),会污染全局变量。
我们做个试验,npm init一个项目:
//package.js
{
"name": "babeltest",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "babel src -d dist" //加上这个命令
},
"author": "",
"license": "ISC",
//下载下面这些依赖
"devDependencies": {
"@babel/core": "^7.2.2",
"babel-cli": "^6.26.0",
"babel-loader": "^8.0.5",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.7.0"
},
"dependencies": {
"babel-polyfill": "^6.26.0"
}
}
//.babelrc
{
"presets": [
["env", {
"debug": true,
"modules": false,
"targets": {
"browsers": ["> 1%", "last 3 versions", "not ie <= 9"]
}
}]
],
"plugins": ["transform-runtime"]
}
测试代码:
//index.js
import './child'
const a = 10;
let b = 20;
a => 10;
var assignObj = Object.assign({});
var fromArr = Array.from([1, 2, 3])
var promise = new Promise(resolve => console.log('promise'))
//chiild.js
const a = 10;
let b = 20;
a => 10;
Object.assign({});
Array.from([1, 2, 3])
运行命令:
npm run build
得到编译后的文件:
//index.js
import _Promise from 'babel-runtime/core-js/promise';
import _Array$from from 'babel-runtime/core-js/array/from';
import _Object$assign from 'babel-runtime/core-js/object/assign';
import './child';
var a = 10;
var b = 20;
(function (a) {
return 10;
});
var assignObj = _Object$assign({});
var fromArr = _Array$from([1, 2, 3]);
var promise = new _Promise(function (resolve) {
return console.log('promise');
});
//child.js
import _Array$from from "babel-runtime/core-js/array/from";
import _Object$assign from "babel-runtime/core-js/object/assign";
var a = 10;
var b = 20;
(function (a) {
return 10;
});
_Object$assign({});
_Array$from([1, 2, 3]);
可以代码都被转换成了ES5的语法,并且一些实例方法和静态方法已经被替换,这些替换的函数(_Array
f
r
o
m
,
O
b
j
e
c
t
from,_Object
from,Objectassign)就是polyfill,
我们可以看到index.js和child.js中根据需要,各自引入了一部分polyfill,这就是transform-runtime的好处。
最后说一下.babelrc的配置
{
"presets": [
["env", {
"debug": true,
"targets": {
"browsers": ["> 1%", "last 3 versions", "not ie <= 9"]
}
}],
"stage-2"
],
"plugins": ["transform-runtime"]
}
presets:即官方的插件合集,官方推荐env
,省略了前缀。
配置项有很多,一般只需要用到targets
属性,说明你的web应用需要兼容到那一部分的浏览器。
plugin
一般会加上transform-runtime
,自动帮你加载你需要的polyfill
。
感兴趣的可以看一下babel应用插件时的转换顺序:presets和plugin的转换排序