Babel配置详解
前言
在前端工程化中,Babel
大家 或多或少都有听说过或者使用过,但是大多数情况也都是在网上直接cv一个babel配置,也只是粗略懂得Babel
是什么 ?只是一个将高级语法转化为低级语法的工具,所谓授人以鱼不如授人以渔,这篇文章将会详细分析Babel
的各种配置、使用。
Babel的配置文件
Babel
支持如下格式的配置文件:
.babelrc
.babelrc.js
.babelrc.json
babel.config.json
babel.config.js
package.json
babel.config.json
(requiresv7.8.0
and above) in the root of your projectOr
babel.config.js
if you are using an older Babel version此文中使用最新版本的
Babel
,故使用babel.config.json
输写配置文件
presets(预设)与plugins(插件)
一般在网上查找babel
配置时大家肯定对这两个字眼不陌生,下来讲解下其差别和联系:
presets
预设其实就是一些plugins
插件的集合。
每一个ES版本都会新增一些新特性,根据这些新特性,babel
会提供对应的插件plugins-list,每一个插件其实就是一个npm包,例如ES2017新引入了async
和await
,babel
则会提供与其对应的@babel/plugin-transform-async-to-generator
插件来转译async/await
,ES2018 新增了对象...展开,babel
则提供 @babel/plugin-proposal-object-rest-spread
来转换。
由于插件实在是太多,我们在实际配置babel
时候,不可能一个个将对应的插件引入,Babel
则提供了presets/预设
用于简化配置:
常见的presets
babel-preset-env
最常见的一个智能预设,可以将我们的高版本js代码根据内置的规则以及对应浏览器的环境转译成低版本的js代码。
@babel/preset-env
不会包含任何低于 Stage 3 的 JavaScript 语法提案。如果需要兼容低于Stage 3
阶段的语法则需要额外引入对应的Plugin
进行兼容。
值得注意的是
@babel/preset-env
只针对语法阶段的转译,箭头函数,const/let
等语法。针对一些API
或者ES6+的内置模块
是无法进行polyfill
的,例如
Promise
、Map
、Proxy
、[].find()
等等
babel-preset-react
在React
中书写的jsx
文件最终会被编译成为React.createElement()
方法,实际上就是此预设的作用。
babel-preset-typescript
对于Ts
代码,我们可以采用两种方式去转译其为js
代码:
- 使用
tsc
命令,结合cli
命令行参数方式或者tsconfig
配置文件进行编译ts
代码(推荐ts-loader
)
- 使用
babel
,通过babel-preset-typescript
预设进行代码编译。
关于
presets
的更多配置可以通过官网查看
常见的plugins
babel/plugin-transform-runtime
关于此插件,会在下面的polyfill
中进行详解。
polyfill
在了解什么是polyfill之前,我们先来理清下面三个概念:
- 最新ES语法:例如箭头函数,
const/let
等等 - 最新ES API: 例如
Promise
,Proxy
等等 - 最新ES实例/静态方法:例如
String.prototype.include()
等等
babel-prest-env
只能转译最新的es语法,并不能够转译最新的api以及实例/静态方法,这种时候,如果想在低版本浏览器中识别并使用最新的api等,就需要额外引入polyfill
了。
例如 浏览器不支持Promise,那么polyfill会添加window.Promise来支持promise;或者通过向Array、String等其原型链上添加方法来实现。
我们知道polyfill的作用后,让我们通过搭建项目来看看如何实际使用。
项目搭建
- 新建文件夹 babel_demo,并运行
npm init
初始化 - 安装插件
npm install --save-dev @babel/core @babel/cli
- 在
package.json
中添加脚本
{
+ "scripts": {
+ "build": "babel src -d lib"
+ },
}
- 根目录下新建src文件夹,并添加index.js文件
- 根目录下新建
babel.config.json
文件
经过简单搭建后,我们的项目目录如下:
babel_demo
|—— node_modules
|—— src
| |_ index.js
|—— babel.config.json
|—— package.json
- 安装预设
npm install @babel/preset-env --save-dev
修改配置
// babel.config.json
{
"presets": ["@babel/preset-env"]
}
// package.json
{
+ "browserslist": [
+ "ie 11"
+ ]
}
简单搭建好后,我们继续回到上节中的polyfill
。
在Babel
中,可以通过以下三种包来实现polyfill
:
- @babel/polyfill
- @babel/runtime
- @babel/plugin-transform-runtime
接下来我们挨个探索这三种方式。
babel/polyfill
首先来看第一种实现方式
babel-polyfill
通过向全局对象和内置对象的prototype上添加方法来实现的。
应用babel/polyfill
在babel-preset-env
中存在一个useBuiltIns
参数,这个参数决定了如何在preset-env
中使用@babel/polyfill
。
// babel.config.json
{
"presets": [
["@babel/preset-env", {
// 此参数默认为false
"useBuiltIns": false
}]
]
}
useBuiltIns
--"usage"
|"entry"
|false
false
useBuiltIns
参数默认为false,表示仅仅会转化最新的es语法,并不会转化任何api以及内置方法
entry
当配置此选项时,需要我们在项目入口文件手动引入core-js
,他会根据我们配置的浏览器兼容性(browserList),全量引入不兼容的polyfill
在
Babel7.4。0
之后,@babel/polyfill
被废弃它变成另外两个包的集成。"core-js/stable"; "regenerator-runtime/runtime";
他们的使用方式是一致的,只是在入口文件中引入的包不同了。
修改配置测试:
// src/index.js
import "@babel/polyfill";
const a = Promise.resolve(null);
a.then((res) => {
console.log(res);
});
// babel.config.js
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry"
}
]
]
}
首先观察运行npm run build
后,会在终端输出如下:
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 thecorejs
option.You should also be sure that the version you pass to the
corejs
option matches the version specified in yourpackage.json
'sdependencies
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
大致意思是,我们在使用useBuiltIns
参数时并没有指定corejs
版本,虽然默认会使用core-js@2版本,但是官方建议我们最好通过 corejs
选项明确设置您正在使用的 core-js 版本。
接下来在观察经过babel
打包输出后的index.js文件,
// lib/index.js 打包输出路径
"use strict";
require("core-js/modules/es6.array.copy-within.js");
require("core-js/modules/es6.array.fill.js");
require("core-js/modules/es6.array.filter.js");
require("core-js/modules/es6.array.find.js");
require("core-js/modules/es6.array.find-index.js");
require("core-js/modules/es7.array.flat-map.js");
require("regenerator-runtime/runtime.js");
// xxxxxx 只做示例展示 就不全量贴代码了
var a = Promise.resolve(null);
a.then(function (res) {
console.log(res);
});
可以看到打包后的index.js文件,不仅将我们的箭头函数,const语法做了转译,还一次性引入了全量不兼容的polyfill。
设置"useBuiltIns": "entry"且未指定corejs版本,需在入口文件引入@babel/polyfill包,才会全量引入不兼容的polyfill,如果未在入口文件引入@babel/polyfill,则babel仍然不会进行polyfill。
如果指定corejs版本为"corejs": 3,需将入口文件引入的babel/polyfill包替换为core-js包
具体如何搭配,可以根据实际项目中使用的babel版本来进行选择,因为在
Babel7.4。0
之后,@babel/polyfill
已经被废弃。
core-js 2.0
版本是跟随preset-env
一起安装的,不需要单独安装
core-js 3
版本需要单独安装
usage
在上述配置"useBuiltIns": "entry"
其实是有弊端的,即使我们只使用了Promise
方法,polyfill仍然会全量引入,其中包含了我们并没有使用到的方法。此时就需要"useBuiltIns": "usage"配置了。
此配置会根据配置的浏览器兼容性,以及代码中使用到的api进行polyfill
的按需引入,同时我们不需要在项目入口文件引入babel/polyfill
或者core-js
了。
配置如下:
// babel.config.json
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage",
"core-js": 3
}]
]
}
关于entry和usage的配置有如下差别:
当我们有多个文件时,entry会直接在入口文件一次性并且只会一次引入全量的polyfill
而usage配置,会在各个文件中引入其需要的polyfill,无疑会多出很多冗余代码
所以需要我们具体情况具体分析,配置并不是绝对的谁优谁劣
babel/runtime
首先修改index.js文件做测试:
// src/index.js
class P {}
npm run build
观察编译后文件:
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
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, _toPropertyKey(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 _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var P = /*#__PURE__*/_createClass(function P() {
_classCallCheck(this, P);
});
可以看到转译后的代码包含了一些形如
_typeof
,_defineProperties
,_createClass
等的辅助函数(helpers函数)
如果在不同文件中使用了多处class
等,打包编译后的代码就会多出很多相同的helpers
函数,造成代码冗余。
这种情况,观察node_modules/@babel/runtime/helpers
中,babel/runtime
为我们提供了很多helpers
函数,但是其不会帮助我们自动引入,所以借助@babel/plugin-transform-runtime
来为我们实现自动引入。
babel/plugin-transform-runtime
通过配置@babel/plugin-transform-runtime
可以为我们解决runtime
不能自动引入所对应的helpers
函数问题。
// babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": ["@babel/plugin-transform-runtime"]
}
通过运行npm run build
观察打包后文件:
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var P = /*#__PURE__*/(0, _createClass2.default)(function P() {
(0, _classCallCheck2.default)(this, P);
});
可以注意到 此时是通过@babel/runtime包来引入helpers函数的
而runtime包不止有这个一个,可以通过配置@babel/plugin-transform-runtime的corejs参数来选择引入如下哪种runtime包:
- @babel/runtime:只能处理语法替换
- @babel/runtime-corejs2:增加了
core-js@2
支持全局构造函数和静态方法兼容 - @babel/runtime-corejs3:支持了实例方法的兼容,同时可以避免采用
core-js
引发的全局环境污染
@babel/runtime-corejs@2
只支持全局变量(例如Promise
)和静态方法(例如Array.from
)兼容,而@babel/runtime-corejs@3
在@babel/runtime-corejs@2
的基础上还支持实例方法(例如[].flat()
)兼容。
通过修改不同配置来观察对同一段代码的编译差别:
// src/index.js
class P {}
new Promise();
[].flat();
配置1
// babel.config.json
{
// useBuiltIns 默认false
"presets": ["@babel/preset-env"],
// corejs不指定
"plugins": ["@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 P = /*#__PURE__*/(0, _createClass2.default)(function P() {
(0, _classCallCheck2.default)(this, P);
});
new Promise();
[].flat();
通过babel/runtime包引入对应helpers函数
Promise API不做转译
flat方法不做转译
配置2
// babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 2
}
]
],
"plugins": ["@babel/plugin-transform-runtime"]
}
编译后代码:
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
require("core-js/modules/es6.object.to-string.js");
require("core-js/modules/es6.promise.js");
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var P = /*#__PURE__*/(0, _createClass2.default)(function P() {
(0, _classCallCheck2.default)(this, P);
});
new Promise();
[].flat();
通过babel/runtime包引入对应helpers函数
通过影响全局对象方式转译 Promise API
flat方法不做转译
@babel/preset-env
的corejs
参数配置为3时,将会通过引入corejs
包方式转译flat
方法。
配置3
// babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 2
}
]
]
}
编译后代码:
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
require("core-js/modules/es.array.flat.js");
require("core-js/modules/es.array.unscopables.flat.js");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));
var P = /*#__PURE__*/(0, _createClass2.default)(function P() {
(0, _classCallCheck2.default)(this, P);
});
new _promise.default();
[].flat();
通过babel/runtime-corejs2包引入对应helpers函数
通过babel/runtime-corejs2包转译 Promise API 不影响全局对象
通过core-js包转译 flat实例方法 影响全局对象
配置4
// babel.config.json
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}
编译后代码:
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _flat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/flat"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
var _context;
var P = /*#__PURE__*/(0, _createClass2.default)(function P() {
(0, _classCallCheck2.default)(this, P);
});
new _promise.default();
(0, _flat.default)(_context = []).call(_context);
通过babel/runtime-corejs3包引入对应helpers函数
通过babel/runtime-corejs3包转译 Promise API 不影响全局对象
通过babel/runtime-corejs3包转译 flat实例方法 不影响全局对象
在指定了@babel/plugin-transform-runtime
的corejs
为3的情况,便无需通过配置preset-env
预设的useBuiltIns
参数进行polyfill,即使配置了结果也与此无异。
总结
根据以上理解,在实际项目中,其实一般可以采用两种配置:
@babel/preset-env
+@babel/runtime
+corejs@3
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": ["@babel/plugin-transform-runtime"]
}
普通项目不怕污染全局环境可以采用此配置。形如上述配置2
通过babel/runtime包引入对应helpers函数
通过corejs包影响全局对象方式转译 API 以及 实例方法
@babel/preset-env
+@babel/runtime-corejs3
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}
开发工具类库为了不污染全局环境可以采用此配置。形如上述配置4
通过babel/runtime-corejs3包引入对应helpers函数,并在不污染全局环境的情况下转译API 以及 实例 方法
转载自:https://juejin.cn/post/7203359540561117240