Babel事用来将我们的ES6代码转化为ES5代码的工具。
按照官方的例子,我们首先需要安装对应的npm包:
npm install --save-dev @babel/core @babel/cli @babel/preset-env
然后创建一个babel.config.json
{
"presets": [
[ "@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3"
}
]
]
}
然后我们可以执行
./node_modules/.bin/babel src --out-dir lib
或
npx babel src --out-dir lib
将我们src目录下的代码转化然后输出到lib目录。
@babel/preset-env
这里主要讲解下这个babel的配置,@babel/preset-env是一个处理语法的Plugjin的集合,babel对每种语法的处理都单独抽离成了一个插件,比如处理箭头函数的插件@babel/plugin-transform-arrow-functions,那么如果我们要处理箭头函数,需要进行如下配置:
{
"plugings": [
"@babel/plugin-transform-arrow-functions"
]
}
如果还要处理其他语法,那么每种语法的插件都要这样用npm install安装然后配置在plugins数组里,这样处理起来肯定太麻烦,所以@babel/preset-env就是所有的这些插件的集合,我们只需要引入它就可以了。
targets
我们在presets里配置@babel/preset-env就会处理所有的语法吗?其实这样说是不严谨的,可以看到配置里还有一个targets配置,这个配置是告诉babel我们需要支持哪些浏览器, 只有这些需要支持的浏览器里还没有的那些语法的语法才会被处理,比如说
"targets": {
"ie": "11",
},
说明我们需要支持ie 11浏览器,因为ie 11不支持箭头函数的写法,所以代码里的箭头函数会被转换,如果配置是这样的
"targets": {
"ie": "16",
},
因为ie 16支持箭头函数,所以即使我们在代码里用了箭头函数也是不会被转换的。
browserslist
上面说的targets配置是用来配置我们的项目需要支持哪些浏览器,一般我们不会将这个配置写在targets里,而是写在一个单独的文件中.browserslistrc,或者写在package.json中,比如
// package.json
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
因为不止babel需要用到这些配置,处理css的时候也需要用到这些配置,所以放到一个统一的单独的文件比较好。
useBuiltIns
useBuiltIns是一个非常重要的配置,用来处理对Api的转换,即polyfill。默认@babel/preset-env只会对语法进行转换,即const,let,=>这些,不会对Promise、Map以及数组实例方法includes这些进行转换,对这些API进行转换需要配置useBuiltIns。useBuiltIns有三个可选的配置项
false
usage
entry
false
当useBuiltIns为false时,不对API做处理。
usage
当useBuiltIns为usage时,babel会根据代码自动引入所需的垫片,这时候我们的babel配置如下:
"presets": [
[ "@babel/preset-env",
"useBuiltIns": "usage",
"corejs": "3"
}
]
]
我们的代码如下
new Promise(resolve => resolve(11)).then(num => {});
使用npx babel src --out-dir lib转换后得到的代码如下
"use strict";
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
new Promise(resolve => resolve(11)).then(num => {});
可以看到代码里引入了两个npm库,这两个库就是处理Promise需要的垫片,我们可以理解为这两个库里面定义了一个全局变量Promise,然后实现了这个Promise的功能。
usage时按需引入,我们的文件里使用了什么Api,且这个API在browserslist浏览器还不支持,那么才会引入对应的垫片。
entry
entry也是引入对应的垫片,和usage不同的是entry是全量引入,即不管你代码里有没有使用Promise,都会引入所有需要的垫片,且需要我们自己引入,设置useBuiltIns为entry后,我们需要在我们的主文件里添加一行引入代码
import 'core-js';
这时候我们的配置如下
"presets": [
[ "@babel/preset-env",
"useBuiltIns": "entry",
"corejs": "3"
}
]
]
main.js如下
import 'core-js';
new Promise(resolve => resolve(11)).then(num => {});
生成的代码如下:
"use strict";
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...
new Promise(resolve => resolve(11)).then(num => {});
即当我们配置了useBuiltIns为entry后,babel会自动将我们引入的代码import 'core-js';转换为垫片的引入,这些垫片包括所有我们配置的浏览器列表不支持的功能,不管我们的代码里有没有用到对用的API。
这里引入的垫片数量只和我们设置的浏览器列表有关,比如当我们设置
// .brwoserslistrc
last 1 version
和
// .brwoserslistrc
last 2 versions
这时候引入的垫片数量是不一样的,只需要支持最新的一个版本引入的垫片数量会少一些。
corejs
当我们将useBuiltIns设置为usage或entry后,我们还需要设置一个参数corejs,因为当我们设置useBuiltIns为usage或entry后,我们需要使用core-js这个npm包来对API进行处理,我们需要指定需要使用哪个版本的core-js,大版本主要为2和3,我们可以设置为2、3或更具体的版本号像3.6.5这样,一般指定3就可以了,2已经不再维护了,一些新的API在2里面可能没有。
转换后我们的代码里会引入对应的垫片
require("core-js/modules/es.promise.js");
我们也需要安装这个npm包
npm i core-js
通过以上的配置我们就可以开发项目啦,具体要选择entry还是usage可以自己决定。
@babel/plugin-transform-runtime
另一个非常重要的知识点时@babel/plugin-transform-runtime,这是一个转换API的插件,和上面的垫片功能是一样的,所以很多同学可能就有疑问了,我们通过配置@babel/preset-env的useBuiltIns就可以实现对应的垫片功能了,为什么还需要用到@babel/plugin-transform-runtime呢,造这么多功能相同的轮子干嘛?
其实主要区别是使用useBuiltIns加入的垫片会污染全局变量,而使用@babel/plugin-transform-runtime加入的垫片不会污染全局变量,当我们开发项目时使用useBuiltIns问题还不大,但是当我们开发第三方库时,如果污染了全局变量那就不太合理了,比如说你引入一个jquery库,结果jquery库里把全局的Promise以及数组的一些方法(比如includes)全部重写了一遍,然后你还引入了其它的库,其它的库也重写了一遍这些方法,这不全乱套了吗,说不定哪里就有冲突了。
所以当开发第三方库的时候,我们要使用@babel/plugin-transform-runtime来对API进行转换,当开发项目时,可以直接使用@babel/preset-env的useBuiltIns。
使用@babel/plugin-transform-runtime时对应的配置如下
// babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": false,
// "corejs": "3.6.5"
}
]
],
"plugins": [
["@babel/plugin-transform-runtime", {
"corejs": 3
}]
]
}
可以看到我们将useBuiltIns设置为了false,即我们不使用@babel/preset-env内置的功能来添加垫片,而是使用@babel/plugin-transform-runtime,需要设置一个对应的参数corejs,这个值为2或3,新项目都使用3就可以了。
我们目前的代码如下:
new Promise(resolve => resolve(11)).then(num => {console.log(num)});
然后使用npx babel src --out-dir lib转换后如下:
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
new _promise.default(function (resolve) {
return resolve(11);
}).then(function (num) {
console.log(num);
});
可以看到代码里的Promise不见了,被转换成了_promise.default,而这个_promise是从@babel/runtime-corejs3/core-js-stable/promise导入的,这样就解决了全局变量污染的问题,现在所有的变量作用范围都是在这个文件的作用域内。
而且使用@babel/plugin-transform-runtime可以帮我们减少一些代码体积,比如另外的文件也使用了Promise这个方法,babel不会在每个文件里都定义一遍Promise这个方法,方法都定义在@babel/runtime-corejs3/core-js-stable/promise里面,每个文件只需要引入就可以了。
这里同样需要注意用这个方法还需要引入@babel/runtime-corejs3这个npm包
npm i @babel/runtime-corejs3
一般来说我们使用node 转换后的代码路径是可以直接运行这个文件的进行测试的。
Webpack
Webpack其实就是多了一个babel-loader,babel-loader其实就是做一个桥梁的作用,实际还是调用上面的这些方法作转换。