babel 是什么
Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
配置babel
Babel 也有配置文件!许多其他工具都有类似的配置文件:ESLint (.eslintrc)、Prettier (.prettierrc)。
所有 Babel API 参数 都可以被配置。然而,如果该参数需要用到 JavaScript 代码,你可能需要使用 JavaScript 代码版的 配置文件。
1.在package.json中设置babel字段。
可以选择将babelrc中的配置信息作为 babel 键(key)的值添加到 package.json 文件中,如下所示:
{
"name": "my-package",
"version": "1.0.0",
"babel": {
"presets": [ ... ],
"plugins": [ ... ],
}
}
2..babelrc文件或.babelrc.js
.babelrc和.babelrc.js是同一种配置方式,只是文件格式不同,一个是json文件,一个是js文件。
在你的项目中创建名为.babelrc或者.babelrc.js的文件,并输入以下内容。
//.babelrc
{
"presets": [...],
"plugins": [...]
}
//.babelrc.js
module.exports = {
presets: [...],
plugins: [...]
};
babel.config.js文件
babel.config.js写法和.babelrc.js一样,但是babel.config.js是针对整个项目,一个项目只有一个放在项目根目录。如果两种类型的配置文件都存在,.babelrc会覆盖babel.config.js的配置。
//babel.config.js
module.exports = {
presets: [...],
plugins: [...]
};
使用babel
我们使用npm init -y 来创建一个例子,然后需要安装@babel/cli 和 @babel/core这两个babel插件。分别是babel编译核心包和命令行执行babel命令工具。
npm install --save-dev @babel/core @babel/cli
项目结构如下:
通过命令把 index 文件用 babel 编译成compiled.js。
npx babel index.js --out-file compiled.js
或者编译整个 src 目录下的文件并输出到 dist 目录,输出目录可以通过 --out-dir 或 -d 指定。这不会覆盖 dist 目录下的任何其他文件或目录。
npx babel src --out-dir dist
这里我们使用npx babel src -d dist进行编译,得到编译后的文件目录:
我们对比src/index.js和编译后的dist/index.js:
编译好的文件没有任何变化,这是因为我们没有提供编译转化的插件。接下来我们通过配置babel,将index.js中的let和箭头函数转化为var和function。
npm i @babel/plugin-transform-arrow-functions -D
配置babel,这里我在根目录创建babel.config.js进行全局配置babel。
module.exports = {
plugins: ['@babel/plugin-transform-arrow-functions']
};
配置好了之后,继续使用npx babel src -d dist进行编译
这次我们发现,箭头函数已经被编译成普通函数了。如果我们需要将let转化成var,可以继续安装对应的插件。
但是有个问题,我们总不能一个个的引入这些插件,来对应转化我们用到的每个新特性,这是非常麻烦的,于是有了一个东西叫做预设(Presets)。
Babel 的预设(preset)可以被看作是一组 Babel 插件和/或 options 配置的可共享模块。使用一个预设就是将这个预设规定的全部插件安装并使用。
preset-env
我们安装一下preset-env这个预设:
npm install --save-dev @babel/preset-env
在babel.config.js进行配置:
module.exports = {
presets: ['@babel/preset-env']
};
我们在配置中将之前的转化箭头函数的插件删除了,加入了presets,然后进行编译:
发现let和和箭头函数通过预设都转化为了var和普通函数。
我们在index.js添加一个map方法,再次进行编译:
发现语法层面都发生了变化,但是对于新的内置函数(Promise,Set,Map),静态方法(Array.from,Object.assign),实例方法(Array.prototype.includes)等就无法转化,这时我们就需要加入@bable/polyfill。
babel-polyfill
Babel 包含一个polyfill,其中包含一个自定义的regenerator 运行时和core-js。
这将模拟一个完整的 ES2015+ 环境,并且旨在用于应用程序而不是库/工具。
比如我们代码中用到了map方法,但是目标浏览器不支持map,引入babel-polyfill就会给数组添加一个map方法,代码执行的时候使用的map其实是babel-polyfill模拟出来功能,这样虽然污染了数组的静态方法,但是确实实现了兼容。
这意味着您可以使用新的内置函数,如Promiseor WeakMap、静态方法(如Array.fromor Object.assign)、实例方法(如Array.prototype.includes)和生成器函数(前提是您使用了regenerator插件)。String为了做到这一点,polyfill 添加到全局范围以及原生原型。
安装babel-polyfill:
npm install --save @babel/polyfill
我们可以对babel-polyfill进行按需加载,因为@babel/polyfill体积比较大,整体引入既增加项目体积,又污染了过多的变量,所以更推荐使用preset-env来按需引入polyfill。
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage', // usage-按需引入 entry-入口引入(整体引入) false-不引入polyfill
corejs: 3 // 2-corejs@2 3-corejs@3
}
]
]
};
🚨 从 Babel 7.4.0 开始,@babel/polyfill已经被弃用,取而代之的是直接包含core-js/stable(以填充 ECMAScript 特性)和regenerator-runtime/runtime(需要使用转译的生成器函数)。
corejs 是一个给低版本的浏览器提供接口的库,也是polyfill功能实现的核心,此处指定的是引入corejs的版本,需要通过npm安装指定版本的corejs库作为生产依赖。
npm i core-js@3
执行编译:
可以看到在使用使用map方法之前,提前从core-js引入了相应的polyfill。map方法其实是babel-polyfill模拟出来功能,这样虽然污染了Array的静态方法,但是确实实现了兼容。
plugin-transform-runtime
当我们编译一个class时,会发现多了好多自定义的函数。
我们在index.js里面加入一个class,进行编译:
"use strict";
require("core-js/modules/es.object.define-property.js");
require("core-js/modules/es.array.map.js");
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); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
var fn = function fn() {
console.log('这是一个箭头函数');
};
var list = [1, 2, 3].map(function (item) {
return item * 2;
});
var Student = /*#__PURE__*/function () {
function Student(age) {
_classCallCheck(this, Student);
this.age = age;
}
_createClass(Student, [{
key: "sayAge",
value: function sayAge() {
console.log(this.age);
}
}]);
return Student;
}();
现在只有一个index文件需要转换,实际项目中可能有无数个文件需要转化,如果每一个转换后的文件中都存在相同的函数,那岂不是浪费了,怎么才能把重复的函数去掉呢?
上面出现的_classCallCheck,_defineProperties,_createClass三个函数叫做工具函数,是在编译阶段辅助 Babel 的函数。
这个时候plugin-transform-runtime就起到作用了。这个插件会将这些工具函数转换成引入的形式。
安装plugin-transform-runtime:
npm install --save-dev @babel/plugin-transform-runtime
使用plugin-transform-runtime还需要安装@babel/runtime作为生产依赖项(因为它用于“运行时”)。
npm install --save @babel/runtime
配置babel.config.js:
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage', // usage-按需引入 entry-入口引入(整体引入) false-不引入polyfill
corejs: 3, // 2-corejs@2 3-corejs@3
},
],
],
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/modules/es.array.map.js");
var fn = function fn() {
console.log('这是一个箭头函数');
};
var list = [1, 2, 3].map(function (item) {
return item * 2;
});
var Student = /*#__PURE__*/function () {
function Student(age) {
(0, _classCallCheck2["default"])(this, Student);
this.age = age;
}
(0, _createClass2["default"])(Student, [{
key: "sayAge",
value: function sayAge() {
console.log(this.age);
}
}]);
return Student;
}();
我们看到babel编译时工具函数统一隔离到babel-runtime提供的helper模块中,直接从helper模块加载,不在每个文件中重复的定义辅助函数,从而减少包的尺寸。
babel-polyfill是通过向全局对象和内置对象的prototype上添加方法来实现的,有一个问题就是引入文件会污染变量,其实plugin-transform-runtime也提供了一种runtime的polyfill。
我们修改下babel.config.js:
module.exports = {
presets: ['@babel/preset-env'],
plugins: [['@babel/plugin-transform-runtime', { corejs: 3 }]],
};
然后执行一下编译看一下区别:
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));
var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map"));
var _context;
var fn = function fn() {
console.log('这是一个箭头函数');
};
var list = (0, _map["default"])(_context = [1, 2, 3]).call(_context, function (item) {
return item * 2;
});
var Student = /*#__PURE__*/function () {
function Student(age) {
(0, _classCallCheck2["default"])(this, Student);
this.age = age;
}
(0, _createClass2["default"])(Student, [{
key: "sayAge",
value: function sayAge() {
console.log(this.age);
}
}]);
return Student;
}();
可以看到这次编译结果和之前的区别是创建了一个_map方法来模拟数组的map方法,使用数组map方法会调用我们创建的_map,这样就不会污染数组上的map方法。
plugin-transform-runtime 插件借助babel-runtime实现了下面两个重要的功能:
- 对工具函数的复用,解决转译语法层时出现的代码冗余
- 解决转译api(方法)层出现的全局变量污染