babel是什么
babel是一个js编译器
随着时间推移,JavaScript也在慢慢进化,新的特性和语法随之出现,然而各个浏览器厂商并没有完全的支持,所以要有个工具,把新的特性和语法翻译成浏览器都认可的标准语法,Babel应运而生,它就是这个工具,ES6/ES7/ES8 => Babel => ES5。
babel配置
@babel/cli @babel/core
@babel/cli是babel的命令行工具,主要提供babel命令。
babel 的核心功能包含在 @babel/core 模块中。没有它,在 babel 的世界里注定寸步难行。不安装 @babel/core,无法使用 babel 进行编译。
命令行安装
npm install --save-dev @babel/core @babel/cli
现在你就可以在项目中使用 babel 进行编译啦
首先创建一个新项目,例如babel-config,使用 npm init -y 进行初始化,创建 src/index.js,文件内容如下
let func = () => { }
修改 package.json, 在 script 里面新增
// package.json
"scripts": {
"babel": "babel src --out-dir dist"
}
使用 npm run babel 来执行编译,编译成功后看下dist 目录下的 index.js 文件:
let func = () => {};
编译前后的代码是完全一样的。
这是因为 babel 虽然开箱即用,但是什么动作也不做,如果想要 babel 做一些实际的工作,就需要为其添加插件(plugin)。
插件
我们需要创建一个配置文件babel.config.js文件
babel开发者为配置文件提供了多种形式,babel7官方推荐用babel.config.js的形式。也可以用.babelrc,.babelrc.js或者放到package.json中
babel所有功能都建立在各种的plugin上,使用方式是安装相应的plugin再去配置文件中去使用。例如箭头函数转换插件, 安装@babel/plugin-transform-arrow-functions,然后在babel.config.js配置文件中去指定对应的插件
//babel.config.js
{
plugins: ["@babel/plugin-transform-arrow-functions"],
}
然后执行npm run babel,可以看到箭头函数已经被编译完成
let func = function () {};
但是let没有被转化,因为我们刚引入的插件是专门用来转化箭头函数的,所以 let 并没有被转化。那如果想转化 let 就需要引入新的插件。
那 es6 那么多功能,我得一个一个引入?当然不,babel为我们提供了预设preset
preset
preset就是一组插件的集合。
官方针对我们常用的环境编写了一些preset:
- @babel/preset-env
- @babel/preset-react
- @babel/preset-typescript
创建 Preset
如需创建preset,导出一份配置即可。
module.exports = function() {
return {
plugins: [
"pluginA",
"pluginB",
"pluginC",
]
};
}
preset 可以包含其他的 preset,以及带有参数的插件。
module.exports = () => ({
presets: [
require("@babel/preset-env"),
],
plugins: [
[require("@babel/plugin-proposal-class-properties"), { loose: true }],
require("@babel/plugin-proposal-object-rest-spread"),
],
});
@babel/preset-env介绍
@babel/preset-env所包含的插件将支持所有最新的JS特性(不包含 stage 阶段),将其转换成ES5代码。允许我们使用最新的js语法,比如 let,const,箭头函数等等。
什么是stage呢?stage里面包含了当年最新规范的草案,每年更新。
//babel.config.js
{
"presets": ["@babel/preset-env"]
}
再次编译,运行结果:
"use strict";
var func = function func() {};
看似很完美,我们修改下 src/index.js。
let func = () => { }
let arr = [1, 2, 4]
arr.includes(3)
编译出来的结果为:
"use strict";
var func = function func() {};
var arr = [1, 2, 4];
arr.includes(3);
这个编译出来的代码在低版本浏览器中使用的话,显然是有问题的,因为低版本浏览器中数组实例上没有 includes 方法。
babel只转换新的js语法,如箭头函数等,但不转换新的API,这时候需要借助@babel/polyfill,把es的新特性都装进来。
新的API分类两类,一类是Promise、Map、Object等全局对象及其对象自身的方法,例如Object.assign,Promise.resolve;另一类是新的实例方法
@babel/polyfill
polyfill 的中文意思是垫片,用来垫平不同浏览器或者不同环境下的差异,让新的内置函数、实例方法等在低版本浏览器中也可以使用。
@babel/polyfill 模块包括 core-js 和一个自定义的 regenerator runtime 模块,可以模拟完整的 ES2015+ 环境(不包含第4阶段前的提议)。
babel v7.4版之后,官方不推荐再使用@babel/polyfill了,需要直接安装core-js 和 regenerator-runtime去替代之前的@babel/polyfill。
虽然@babel/polyfill还在进行版本升级,但其使用的core-js包为2.x.x版本,而core-js这个包本身已经发布到了3.x.x版本了,@babel/polyfill以后也不会使用3.x.x版本的包了。
core-js@2分支中已经不会再添加新特性,新特性都会添加到core-js@3。
安装core-js和regenerator-runtime:
npm install --save core-js regenerator-runtime
注意:不使用 --save-dev,因为这是一个需要在源码之前运行的polyfill。
我们需要将完整的 polyfill 在代码之前加载,修改我们的 src/index.js:
//import '@babel/polyfill'; (babel v7.4版之后,不推荐使用)
import "core-js/stable";
import "regenerator-runtime/runtime";
let func = () => { }
let arr = [1, 2, 4]
arr.includes(3)
编译出来的结果为:
"use strict";
require("core-js/stable");
require("regenerator-runtime/runtime");
var func = function func() {};
var arr = [1, 2, 4];
arr.includes(3);
此时我们的代码在低版本浏览器中已经能够正常运行了。
@babel/runtime
修改src/index.js中的内容:
class Person {
sayname() {
return 'name'
}
}
var john = new Person()
console.log(john)
编译后生成的代码如下:
"use strict";
require("core-js/stable");
require("regenerator-runtime/runtime");
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
var Person = /*#__PURE__*/function () { function Person() { _classCallCheck(this, Person); } _createClass(Person, [{ key: "sayname", value: function sayname() { return 'name'; } }]); return Person;}();var john = new Person();console.log(john);
可以看到转换后的代码上面增加了好几个函数声明,这就是注入的函数,我们称之为辅助函数。@babel/preset-env在做语法转换的时候,注入了这些函数声明,以便语法转换后使用。
但样这做存在一个问题。在我们正常的前端工程开发的时候,少则几十个js文件,多则上千个。如果每个文件里都使用了class类语法,那会导致每个转换后的文件上部都会注入这些相同的函数声明。这会导致我们打包出来的包非常大。
那么怎么办?一个思路就是,我们把这些函数声明都放在一个npm包里,需要使用的时候直接从这个包里引入到我们的文件里。这样即使上千个文件,也会从相同的包里引用这些函数。通过webpack这一类的构建工具打包的时候,我们只会把使用到的npm包里的函数引入一次,这样就做到了复用,减少了体积。
@babel/runtime就是上面说的这个npm包,@babel/runtime把所有语法转换会用到的辅助函数都集成在了一起。
我们先安装这个包:(@babel-runtime是代码运行时需要的依赖,所以需要作为生产依赖安装)
npm install --save @babel/runtime
然后到node_modules下查看@babel/runtime/helpers下的文件,我们发现_classCallCheck, _defineProperties与 _createClass这个三个辅助函数就在这个helpers下面,我们手动把辅助函数替换掉函数声明,之前文件的代码就变成如下所示:
"use strict";
require("core-js/stable");
require("regenerator-runtime/runtime");
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
var _defineProperties = require("@babel/runtime/helpers/defineProperties");
var _createClass = require("@babel/runtime/helpers/createClass");
var Person = /*#__PURE__*/function () { function Person() { _classCallCheck(this, Person); } _createClass(Person, [{ key: "sayname", value: function sayname() { return 'name'; } }]); return Person;}();
var john = new Person();
console.log(john);
这样就解决了代码复用和最终文件体积大的问题。不过,这么多辅助函数要一个个记住并手动引入,平常人是做不到的,我也做不到。这个时候,Babel插件@babel/plugin-transform-runtime就来帮我们解决这个问题。
@babel/plugin-transform-runtime
这个插件的作用就是自动移除语法转换后内联的辅助函数(inline Babel helpers),使用@babel/runtime/helpers里的辅助函数来替代。这样就减少了我们手动引入的麻烦。
npm install --save-dev @babel/plugin-transform-runtime
现在,我们的Babel配置文件如下:
{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-transform-runtime"]
}
编译后的代码:
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
require("core-js/stable");
require("regenerator-runtime/runtime");
var Person = /*#__PURE__*/function () { function Person() { (0, _classCallCheck2["default"])(this, Person); } (0, _createClass2["default"])(Person, [{ key: "sayname", value: function sayname() { return 'name'; } }]); return Person;}();
var john = new Person();
console.log(john);
可以看到,它生成的代码比我们完全手动引入@babel/runtime里的辅助函数更加优雅。
@babel/plugin-transform-runtime的另一个作用是创建一个沙盒环境来避免对全局环境的污染
以Promise举例子,编译前的代码:
var obj = Promise.resolve();
若使用了babel-polyfill或core-js/stable与regenerator-runtime/runtime来做全局的API补齐,那么编译后的代码仍然是
var obj = Promise.resolve();
polyfill只是补齐了浏览器的window.Promise对象。
若我们不使用polyfill,而开启@babel/plugin-transform-runtime的API转换功能。那么编译后的代码将是
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var obj = _promise["default"].resolve();
@babel/plugin-transform-runtime把我们代码里的Promise变成了_promise[“default”],而_promise[“default”]拥有ES标准里Promise所有的功能。现在,即使浏览器没有Promise,我们的代码也能正常运行。
那么,上面讲的API转换有什么用,明明通过polyfill补齐API的方式也可以使代码在浏览器正常运行?
其实,API转换主要是给开发JS库或npm包等的人用的,我们的前端工程一般仍然使用polyfill补齐API。
可以想象,如果开发JS库的人使用polyfill补齐API,我们前端工程也使用polyfill补齐API,但JS库的polyfill版本或内容与我们前端工程的不一致,那么我们引入该JS库后很可能会导致我们的前端工程出问题。所以,开发JS库或npm包等的人会用到API转换功能。
如何开启@babel/plugin-transform-runtime的API转换功能
安装@babel/runtime-corejs3
npm install --save @babel/runtime-corejs3
修改babel.config.js:
{
"presets": [
"@babel/preset-env"
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3 //corejs取值是false、2和3,默认值是false
}
]
]
}
@babel/runtime-corejs3和@babel/runtime的区别
@babel/runtime我们知道里面存放的是babel做语法转换的辅助函数,@babel/runtime-corejs3是@babel/runtime的进化版,这个npm包里除了包含babel做语法转换的辅助函数,也包含了core-js的API转换函数。
除了这两个包,还有一个@babel/runtime-corejs2的包。它和@babel/runtime-corejs3的功能是一样的,只是里面的函数是针对core-js2版本的。
对于@babel/runtime及其进化版@babel/runtime-corejs2、@babel/runtime-corejs3,我们只需要根据自己的需要安装一个。
如果你不需要对core-js做API转换,那就安装@babel/runtime并把corejs配置项设置为false即可。
如果你需要用core-js2做API转换,那就安装@babel/runtime-corejs2并把corejs配置项设置为2即可。
如果你需要用core-js3做API转换,那就安装@babel/runtime-corejs3并把corejs配置项设置为3即可。
Browserslist 集成
@babel/preset-env会根据配置的目标环境,生成插件列表来编译。
当不需要兼容所有的浏览器和环境时,通过指定目标环境,可以让编译代码保持最小。
官方推荐使用 .browserslistrc 文件来指定目标环境。
//.browserslistrc
> 1%
not ie <= 8
上面的配置含义是,目标环境是市场份额大于1%的浏览器并且不考虑IE8及以下的IE浏览器。
将 .browserslistrc 的内容配置为:
last 2 Chrome versions
然后再执行 npm run babel,你会发现箭头函数不会被编译成ES5,因为 chrome 的最新2个版本都能够支持箭头函数。