Babel6 与Babel7 配置具有一定的差异,在Babel的发展历程中,我们有必要对其配置有一定的了解,才能在项目中正常合理的配置Babel的编译,配置错误会导致出现冗余代码、项目构建产物过大或浏览器不兼容等问题。
依赖安装
pnpm i @babel/core @babel/cli @babel/preset-env -D
默认转换规则
默认只转语法,不转API
新的API
- 新增的全局对象:
Promise、Map、Symbol、Proxy、Iterator等 - 新增的实例方法:
[].find(),''.includes,Object.assign()等
Babel的配置文件
默认会在当前项目根目录查找 .babelrc、.babelrc.js、.babelrc.json、babel.config.json、babel.config.js、package.json
babel.config.js
module.exports = {
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
]
}
Babel7.8及以下版本支持直接json配置
babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
]
}
package.json 配置方式
{
"name": "my-package",
"version": "1.0.0",
"babel": {
"presets": ["@babel/preset-env"],
"plugins": [ ... ],
}
}
预设和插件
babel的配置主要分为 presets预设 和 plugins插件
每一个ES版本都会新增一些新特性,这些新特性,babel 会提供相应的插件 plugins-list,每一个插件对应一个npm包
比如 ES2017新增了 async 和 await,babel提供了 @babel/plugin-transform-async-to-generator 用于将 async/await 转换为 generator 函数,
async function foo() {
await bar();
}
// ==>
var _asyncToGenerator = function (fn) {
...
};
var foo = _asyncToGenerator(function* () {
yield bar();
});
ES2018 新增了对象...展开,babel 提供 @babel/plugin-proposal-object-rest-spread 来转换
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }
// ===>
let n = { x, y, ...z };
console.log(n); // { x: 1, y: 2, a: 3, b: 4 }
由于插件太多,Babel提供的了预设功能,用于简化配置,预设是一系列插件的集合,常用预设有:
@babel/preset-env
@babel/preset-typescript
@babel/preset-react
@babel/preset-flow
预设和插件的的短名称
@babel/preset-env简写@babel/env或env, 可省略preset-@babel/plugin-proposal-object-rest-spread简写@babel/proposal-object-rest-spread,可省略plugin-
// babel.config.js
module.exports = {
"presets": ['@babel/env'],
"plugins": ["@babel/transform-decorators-legacy"]
}
预设和插件的执行顺序
- 插件比预设先执行
- 插件执行顺序是插件数组从前向后执行
- 预设执行顺序是预设数组从后向前执行
预设和插件版本
Babel7以前的版本,使用年代preset,如 babel-preset-es2015、babel-preset-es2016、babel-preset-es2017、babel-preset-latest、babel-preset-stage-0、babel-preset-stage-1、babel-preset-stage-2 等。
Babel7开始推荐使用 @babel/preset-react、@babel/preset-env、@babel/preset-flow、@babel/preset-typescript
常用插件主要是 @babel/plugin-transform-runtime
babel-polyfill
polyfill 是大浏览器中添加缺失的新特性,补齐API和标准对齐,比如,浏览器不支持Promise,那么polyfill 会添加 window.Promise 来支持 promise
polyfill 是为当前环境提供一个垫片。所谓垫片,是指垫平不同浏览器之间差异的东西。polyfill提供了全局的ES6对象以及通过修改原型链 Array.prototype等实现对实例的实现。
Babel polyfill 使用的几种方式
- 直接在html中引用构建好的
polyfill.js - 在工程入口文件中引用
polyfill.js - 在工程入口文件中引用
@babel/polyfill - 在前端工程的入口文件里引入
core-js/stable与regenerator-runtime/runtime; - 在前端工程构建工具的配置文件入口项引入
polyfill.js; - 在前端工程构建工具的配置文件入口项引入
@babel/polyfill; - 在前端工程构建工具的配置文件入口项引入
core-js/stable与regenerator-runtime/runtime;
// import './polyfill.js'
// import '@babel/polyfill'
import "core-js/stable";
import "regenerator-runtime/runtime";
const fn1 = (num) => num + 1;
const path = require('path');
module.exports = {
// entry: ['./polyfill.js', './a.js'],
// entry: ['@babel/polyfill', './a.js'],
entry: ['core-js/stable', 'regenerator-runtime/runtime', './a.js'],
output: {
filename: 'b.js',
path: path.resolve(__dirname, '')
},
mode: 'development'
};
以上引用方式是Babel7以前的使用方式,这种方式都是全量引用所有的新特性垫片,这样引用的 polyfill 包会大增加我们构建的js体积大小,如果我们只用到了其中两三个新特性,那就存在很多冗余的垫片。
@babel/preset-env
@babel/preset-env 是 Babel6 时代 babel-preset-latest的增强版。该预设除了包含所有稳定的转码插件,还可以根据我们设定的目标环境进行针对性转码。
配置
module.exports = {
// 三种配置方式等同
presets: ["@babel/env"],
// presets: [["@babel/env", {}]],
// presets: [["@babel/env"]],
plugins: []
}
@babel/env 是 @babel/preset-env 的简写,核心配置项有 targets、useBuiltIns、modules、corejs
targets
用于设置兼容到哪些目标浏览器,如果不配置,则尝试读取 package.json 和 .browserslistrc 中的配置, browserslist 的配置也同样作用于 autoprefixer、postcss等插件。 如果没有 targets 和 browserslist 配置,则转换所有的ES6语法为ES5版本
useBuiltIns
取值有 usage/entry/false,默认为 false
false不使用polyfill,只转换语法entry会根据目标浏览器环境,引用未支持的所有的polyfill,需要在入口文件引用@babel/polyfillusage会先分析代码中使用到的新特性,只为用到的新特性添加polyfill,不需要手动添加@babel/polyfill,但需要配置corejs的版本,不配置会有警告。
WARNING (@babel/preset-env): We noticed you're using the `useBuiltIns` option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via the `corejs` option.
You should also be sure that the version you pass to the `corejs` option matches the version specified in your `package.json`'s `dependencies` section. If it doesn't, you need to run one of the following commands:
npm install --save core-js@2 npm install --save core-js@3
yarn add core-js@2 yarn add core-js@3
"browserslist": [
"chrome >= 49"
],
配置 usage,只为使用到的新特性提供 polyfill
module.exports = {
presets: [['@babel/env', {
useBuiltIns: 'usage'
}]]
}
源码
const configPromise = Promise.resolve({url: '/login'})
babel转换后
"use strict";
require("core-js/modules/es6.object.to-string.js");
require("core-js/modules/es6.promise.js");
const configPromise = Promise.resolve({
url: '/login'
});
corejs
取值为 2 或 3, 3 是 2 的升级版,会扩展一些新的API的polyfill,比如数组的flat方法,一般使用 3 的版本。
安装相应的模块
npm install --save core-js@2
npm install --save core-js@3
modules
模块语法转换
用于将当前ES6的模块语法转换为其它模块化的语法
取值 amd/umd/systemjs/commonjs/cjs/auto/false,默认为 auto ,会转换为 commonjs 语法
常见的模块化语法有两种:
- ES6的模块法语法用的是
import与export; - commonjs模块化语法是
require与module.exports。
一般建议设置为 false,不转换 es6 的模块语法,方便构建工具如 webpack、rollup 对模块进行静态分析,实现 tree shaking 等优化措施
@babel/plugin-transform-runtime
usage 的缺点: 语法转换后,代码是会注入一些辅助函数,如果有很多个js文件,每个文件顶部都会注入相关辅助函数,有可能会注入导致辅助函数重复,最后用构建工具打包出来的产物会非常大。
class Person {
sayname() {
return 'name'
}
}
转换后
"use strict";
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 Person = /*#__PURE__*/function () {
function Person() {
_classCallCheck(this, Person);
}
_createClass(Person, [{
key: "sayname",
value: function sayname() {
return 'name';
}
}]);
return Person;
}();
为了实现 class 语法,添加了 _classCallCheck、_createClass、_defineProperties几个辅助函数。
@babel/runtime
为了在很多js转换情况下,避免辅助函数直接注入到代码中,babel提供了 @babel/runtime 来提供这些辅助函数单独引用。
@babel/runtime 只是提供了辅助函数模块,但还不能在转换过程中自动替换,这需要 @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"));
var Person = /*#__PURE__*/function () {
function Person() {
(0, _classCallCheck2.default)(this, Person);
}
(0, _createClass2.default)(Person, [{
key: "sayname",
value: function sayname() {
return 'name';
}
}]);
return Person;
}();
函数注入手动改成函数引用,减少了函数直接注入造成的构建产物增大的问题
实际生产中,我们需要同时安装这两个模块,注意由于辅助函数最终是需要打包到构建产物中的,@babel/runtime 需要安装到 dependencies 下
npm install --save @babel/runtime
npm install --save-dev @babel/cli @babel/core @babel/preset-env @babel/plugin-transform-runtime
plugin-transform-runtime 的作用
- 自动移除转换后的内联辅助函数,使用
@babel/runtime/helpers里的辅助函数来替代 - 当代码里使用了
core-js的API,自动引入@babel/runtime-corejs3/core-js-stable/,以此来替代全局引入的core-js/stable; - 当代码里使用了
Generator/async函数,自动引入@babel/runtime/regenerator,以此来替代全局引入的regenerator-runtime/runtime;
2和3的核心功能是使用函数来替换原生api,以达到不污染全局对象的目的( polyfill和core-js/stable与regenerator-runtime/runtime都是直接对原生对象做扩展,会污染原生对象)。
polyfill的方式是直接扩展原生对象,比如浏览器不支持Promise,那么 polyfill 的作用就是在window上实现Promise对象,生成一个window.Promise,这样会污染原生环境。plugin-transform-runtime的方式是添加_promise辅助函数来替换原来的 api
// babel.config.js
module.exports = {
presets: [['@babel/env']],
plugins: [
['@babel/transform-runtime', {
corejs: 3
}]
]
}
指定 corejs 版本,才能对相关的API做替换,指定版本后,需要安装相应的corejs版本,有两种方式
- 安装
@babel/runtime和core-js@3 - 安装
@babel/runtime-corejs3
// 原始代码
var obj = Promise.resolve();
// polyfill
require("core-js/modules/es.promise.js");
var obj = Promise.resolve();
// plugin-transform-runtime
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var obj = _promise["default"].resolve();
默认配置
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": false,
"helpers": true,
"regenerator": true,
"version": "7.0.0-beta.0"
}
]
]
}
absoluteRuntime默认为false, 也就是默认从当前项目根目录下的node_modules下引用@babel/runtime,当前没有的话,需要手动指定路径。helpers默认为true,即自动引入辅助函数替换内联函数。corejs默认为false, 不对不对Promise这一类的API进行转换,仍然使用core-js提供的polyfill。regenerator默认为true,自动对regenerator/asyncapi 进行转换。version这里对应的是@babel/runtime或@babel/runtime-corejs2/3的版本。如果安装了@babel/runtime 的后续版本(或者它们的 corejs 对应版本,比如@babel/runtime-corejs3) ,或者将其列为依赖项,则转换运行时可以使用更高级的特性。
依赖于 @babel/runtime-coregs2@7.7.4,则可以使用
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 2,
"version": "^7.7.4"
}
]
]
}
一般前端业务开发,使用默认值就好,即不用添加任何配置
// regenerator: false (直接修改全局对象,添加polyfill)
require("regenerator-runtime/runtime.js");
// regenerator: true (返回 _regenerator 工具函数)
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
使用场景
业务开发
- 插件
@babel/plugin-transform-runtime使用默认配置即可 @babel/preset-env的useBuiltIns设置为usage,corejs设置为3
module.exports = {
"presets": [
[
"@babel/preset-env",
{
// 不转换模块类型,仍然使用 ES Module,方便构建工具 tree shaking
"modules": false,
// 使用 usage,只为代码用到的新特性提供 polyfill
"useBuiltIns": "usage",
// 使用 corejs3版本提供的 polyfill
"corejs": 3
}
],
],
// 引入辅助函数替换内联函数
"plugins": ["@babel/plugin-transform-runtime"]
}
npm模块开发
- 插件
@babel/plugin-transform-runtime的corejs配置为2或3 - 发布为ES模块时,
@babel/preset-env的modules要设置为false,不转换模块类型,仍然使用 ES Module,方便构建工具 tree shaking
阅读参考