Babel
能做什么?
用于将 ECMAScript 2015+
代码转换为当前和旧浏览器或环境中向后兼容的 JavaScript 版本,功能主要包括以下:
- 转换语法
- 添加目标浏览器中缺少的
Polyfill
功能 - 源代码转换
Presets
Babel
的preset
可以被看作是一组 Babel
插件的集合。 官方预设:
- @babel/preset-env for compiling ES2015+ syntax
- @babel/preset-typescript for TypeScript
- @babel/preset-react for React
- @babel/preset-flow for Flow
Presets
的使用,使用已存在的node_modules
中的npm
包,或者自定义的文件程序。
{
"presets": ["@babel/preset-env", "./myProject/myPreset"]
}
复制代码
Stage-X
:从 Babel 7
开始, Stage-X
已经被弃用,故不在做其他描述。
执行顺序
presets
的执行顺序是从后向前。
{ "presets": ["a", "b", "c"] }
复制代码
先执行c,再b,最后a
Plugins
通过plugins
进行代码转换。 plugins
和presets
一样,也是使用已存在的node_modules
中的npm
包,或者自定义的文件程序。
plugins
执行
plugins
在presets
之前运行plugins
从前向后执行,和presets
相反
Babel
配置文件
-
如果你正在使用
monorepo
或想编译node_modules
,用babel.config.json
-
如果是项目的单个部分,用
.babelrc.json
-
还可以在
package.json
中添加Babel
的配置
{
"name": "my-package",
"version": "1.0.0",
"babel": {
"presets": [ ... ],
"plugins": [ ... ],
}
}
复制代码
推荐使用babel.config.json
。
Babel
上手
@babel/core
是babel
的核心功能库,@babel/cli
是一个允许你从终端使用 babel
的工具。
npm install --save-dev @babel/core @babel/cli
复制代码
配置执行转化命令:
"scripts": {
"build": "babel src --out-dir lib"
}
复制代码
注意:babel
转换是依赖插件的,如果没有配置插件的,输出代码将与输入相同。
例如:通过@babel/plugin-transform-arrow-functions
可以将 ES2015
箭头函数编译为 ES5
。
npm install --save-dev @babel/plugin-transform-arrow-functions
复制代码
配置文件:
// babel.config.json
{
"plugins": ["@babel/plugin-transform-arrow-functions"]
}
复制代码
npm run build
后,编译效果如下:
// 编译前:
const fun = () => {
console.log('fun')
}
// 编译后:
const fun = function () {
console.log('fun');
};
复制代码
但我们的代码中还有其他 ES2015+ 特性需要转换。我们可以使用一个预设插件(
@babel/preset-env
)。它只是一组预先确定的插件,不用在一个一个添加所需插件。此预设是包含所有支持现代 JavaScript(ES2015、ES2016 等)的插件。
Polyfill
Babel 7.4.0 开始,该包已被弃用。取而代之的是直接包含
core-js/stable
(用于填充 ECMAScript 功能)和regenerator-runtime/runtime
(需要使用转译的生成器函数)。
@babel/polyfill
模块包括 core-js
和一个自定义的 regenerator
运行时,用来模拟一个完整的 ES2015+
环境。
有了Polyfill
,我们才能使用新的内置函数(如 Promise
或 WeakMap
)、静态方法(如 Array.from
或 Object.assign
)、实例方法(如 Array.prototype.includes
)和生成器函数(与 regenerator
插件一起使用时)。
关于添加Polyfill
的方式如下:
设置useBuiltIns
为usage
,Babel
现在将检查您的所有代码以查找目标环境中缺少的功能,并且仅加载仅包含所需的 polyfill
。
配置文件:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage"
}
]
]
}
复制代码
npm run build
后,编译效果如下:
// 编译前:
Promise.resolve().finally();
// 编译后:
require("core-js/modules/es7.promise.finally.js");
Promise.resolve().finally();
复制代码
设置useBuiltIns
为entry
。
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "entry"
}
]
]
}
复制代码
然后在我们的入口文件中导入 core-js
(以填充 ECMAScript
功能)和 regenerator
运行时(仅当您正在编译生成器时才需要)来模拟完整的 ES2015+
环境。注意: Babel 7.4.0开始已被弃用@babel/polyfill
。
import "core-js/stable";
import "regenerator-runtime/runtime";
复制代码
减少体积和避免全局污染
Babel
会使用例如 _extend
的功能。结果就是会将此添加到需要它的每个文件中,项目中的多个文件都引用了,就造成了重复。或者如果你不需要像 Array.prototype.includes
这样的实例方法。
那么,就用到@babel/plugin-transform-runtime
这个插件了,它的作用如下:
- 可以重用
Babel
的注入帮助代码以节省代码大小。此插件将引用模块@babel/runtime
以避免编译输出的重复。 - 创建一个沙盒环境,避免污染全局作用域
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime // 作为生产依赖
复制代码
配置文件:
{
"plugins": ["@babel/plugin-transform-runtime"]
}
复制代码
作用1:
编译前:
class Point {
}
复制代码
编译后(没使用@babel/plugin-transform-runtime
插件,代码没有重用):
require("core-js/modules/es6.object.define-property.js");
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; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Point = /*#__PURE__*/_createClass(function Point() {
_classCallCheck(this, Point);
});
复制代码
编译后(使用了@babel/plugin-transform-runtime
插件,代码重用):
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var Point = /*#__PURE__*/(0, _createClass2["default"])(function Point() {
(0, _classCallCheck2["default"])(this, Point);
});
复制代码
比较两次结果,可以看到代码的公用:由直接命名创建函数变为了外部引入。这会有效减少代码体积。
作用2:
编译前:
function* foo() {
console.log('hello')
}
复制代码
编译后(没使用@babel/plugin-transform-runtime
插件,有作用域污染):
var _marked = /*#__PURE__*/regeneratorRuntime.mark(foo);
function foo() {
return regeneratorRuntime.wrap(function foo$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
console.log('hello');
case 1:
case "end":
return _context.stop();
}
}
}, _marked);
}
复制代码
编译后(使用了@babel/plugin-transform-runtime
插件,没有作用域污染):
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _marked = /*#__PURE__*/_regenerator["default"].mark(foo);
function foo() {
return _regenerator["default"].wrap(function foo$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
console.log('hello');
case 1:
case "end":
return _context.stop();
}
}
}, _marked);
}
复制代码
@babel/plugin-transform-runtime
插件会将这些内置插件重命名为 core-js
,而无需再使用 polyfill
。