likes
comments
collection
share

Babel配置详解

作者站长头像
站长
· 阅读数 39

前言

在前端工程化中,Babel大家 或多或少都有听说过或者使用过,但是大多数情况也都是在网上直接cv一个babel配置,也只是粗略懂得Babel是什么 ?只是一个将高级语法转化为低级语法的工具,所谓授人以鱼不如授人以渔,这篇文章将会详细分析Babel的各种配置、使用。

Babel的配置文件

Babel支持如下格式的配置文件:

  • .babelrc
  • .babelrc.js
  • .babelrc.json
  • babel.config.json
  • babel.config.js
  • package.json

babel.config.json (requires v7.8.0 and above) in the root of your project

Or 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新引入了asyncawaitbabel则会提供与其对应的@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的,

例如PromiseMapProxy[].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: 例如PromiseProxy等等
  • 最新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 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

大致意思是,我们在使用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-envcorejs参数配置为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-runtimecorejs为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
评论
请登录