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。