前端工程化之Babel

2,017 阅读9分钟

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

本文首发于个人博客

1.前言

2021-10-18更新:在github上添加了一个示例仓库,配合食用更佳。

Babel是什么?

Babel是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

Babel 的中文解释是巴别塔。在《圣经》当中,有这样的故事。当时地上的人们都说同一种语言,当人们离开东方之后,他们来到了示拿之地。在那里,人们想方设法烧砖好让他们能够造出一座城和一座高耸入云的塔来传播自己的名声,以免他们分散到世界各地。上帝来到人间后看到了这座城和这座塔,说一群只说一种语言的人以后便没有他们做不成的事了;于是上帝将他们的语言打乱,这样他们就不能听懂对方说什么了,还把他们分散到了世界各地,这座城市也停止了修建。这座城市就被称为巴别城,这座塔即是巴别塔

可以将 Babel 看做语言的统一Babel 的作者将其取名为 Babel,是有相当一部分野心的。他是想将目前不同的 JS 语言规范,统一成一种,实现不同浏览器下的兼容。

截止到目前,babel 也有过很多次更新了。该文主要将以 babel@7.0 版本来理解 babel 的使用。

Babel做什么?

常用的功能有3种:

  1. 语法转换。譬如将 es6 的箭头函数转换为低版本浏览器兼容的普通函数。
  2. 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过 @babel/polyfill 模块)。
  3. 源码转换。譬如转换 typescriptjsx等。

了解步骤

下面我们会从 CLIpluginspresets 以及 polyfill 等一步步来了解babel的用法。

准备工作

  • 新建目录。
  • 执行 yarn init -y
  • 创建 src目录。
  • src 下创建入口文件 main.js

main.js 文件的内容如下:

// 1.arrow function
const arrowFun = () => {
  console.log('arrow-function', this)
}

// 2.class
class Person {
  constructor() {
    this.name = name
  }
  say() {
    alert('hello')
  }
}

// 3.promise es6新增
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    if (Math.random() * 10 >= 5) {
      resolve('大于5')
    } else {
      reject('小于5')
    }
  }, 2000)
})

// 4.async await es7
async function fn() {
  try {
    const result = await promise
    console.log(result)
  } catch(err) {
    console.warn('error', err)
  }
  console.log('--- after promise ---')
}

fn()

// 5.includes
const flag = [1, 2, 3].includes(1)
console.log('includes', flag)

之所以创建这些例子,是有原因的。当前目录结果大致如下:

.
├── package.json
├── src
│   └── main.js
└── yarn.lock

下文的讲述都是基于该项目

2.CLI

1.脚手架@babel/cli

babel 作为一门转换工具。它自带了一套自己的脚手架工具。

新建一个项目,执行:

yarn add @babel/cli -D

babel@7.0 版本开始,babel 会将它所有的官方包都放在 @babel 这个命名空间下。其实这并不是 babel 的特例。而是 npm 对于它自身包管理的一种优化。 babel 只是顺道遵守了而已。

这样的话,我们就可以使用 babel 命令行。由于环境变量的原因,所以我们配置下 package.json (你也可以利用 npx 取代这种方式)。利用 npm 来执行 babel:

{
  "scripts": {
    "compiler": "babel './src/main.js' --out-dir dist"
  }
}

当执行 yarn compiler 时,按照道理来说babel 就会将 src 目录下的 main.js,转译到 dist 目录中。

但现在执行 yarn compiler 后,会发现 dist/main.js 文件中的代码并没有任何转换迹象。

2.核心库@babel/core

babel的核心功能包含在 @babel/core 模块中。安装:

yarn add @babel/core -D

这时执行 yarn compiler, 代码仍然不会转换。

可以将 @babel/core 理解成一个核心转换模块函数,它的执行依赖于 options 配置。如果没有 options ,那么这个函数什么也不会做。

function core (code, options = []) {
  options.forEach(item => {
    code += item
  })
  return code
}

而这些 optionsbabel 中对应的就是 plugins

3.插件plugins

安装下转换箭头函数的插件:

yarn add @babel/plugin-transform-arrow-functions -D

package.json 中的 scripts 添加命令:

{
  "scripts": {
    "compiler:plugin": "babel './src/main.js' --out-dir dist --plugins=@babel/plugin-transform-arrow-functions"
  }
}

再次执行 yarn compiler:plugin,会发现 main.js 中的箭头已被转换:

var _this = this;

// 1.arrow function
const arrowFun = function () {
  console.log('arrow-function', _this);
};

4.预设presets

presets 其实就是一组 plugins 的集合。官方提供的预设有四组:

  • @babel/preset-env
  • @babel/preset-flow
  • @babel/preset-react
  • @babel/preset-typescript

其中 @babel/preset-env 代表的是对于环境 environment 的预设。现在我们使用 --presets 来代替上面使用的 --plugins

{
  "scripts": {
    "compiler:preset": "babel './src/main.js' --out-dir dist --presets=@babel/preset-env"
  }
}

执行 yarn compiler:preset。会发现代码已经被转换成ES5代码(除了需要@babel/polyfill的语法。如promise等。)

另外直接执行转译过后的代码,会有报错 ReferenceError: regeneratorRuntime is not defined。这是因为 async await 的实际运行也需要 @babel/polyfill

具体代码可见下方。

在未指定浏览器目标的情况下,@babel/preset-env 会将所有 ES2015-ES2020 代码转换为与ES5兼容。 但是不建议以这种方式使用 @babel/preset-env ,因为它没有利用针对特定环境/版本的功能。

"use strict";

var _this = void 0;

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

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); return Constructor; }

// 1.arrow function
var arrowFun = function arrowFun() {
  console.log('arrow-function', _this);
}; 

// 2.class
var Person = /*#__PURE__*/function () {
  function Person() {
    _classCallCheck(this, Person);

    this.name = name;
  }

  _createClass(Person, [{
    key: "say",
    value: function say() {
      alert('hello');
    }
  }]);

  return Person;
}(); 

// 3.promise es6新增
var promise = new Promise(function (resolve, reject) {
  setTimeout(function () {
    if (Math.random() * 10 >= 5) {
      resolve('大于5');
    } else {
      reject('小于5');
    }
  }, 2000);
});

// 4.async await es7
function fn() {
  return _fn.apply(this, arguments);
}

function _fn() {
  _fn = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
    var result;
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.prev = 0;
            _context.next = 3;
            return promise;

          case 3:
            result = _context.sent;
            console.log(result);
            _context.next = 10;
            break;

          case 7:
            _context.prev = 7;
            _context.t0 = _context["catch"](0);
            console.warn('error', _context.t0);

          case 10:
            console.log('--- after promise ---');

          case 11:
          case "end":
            return _context.stop();
        }
      }
    }, _callee, null, [[0, 7]]);
  }));
  return _fn.apply(this, arguments);
}
fn(); 

// 5.includes
var flag = [1, 2, 3].includes(1);
console.log('includes', flag); 

5.总结

这一章,主要来了解快速使用 babel 需要安装什么。

  • @babel/cli, 官方脚手架。
  • @babel/core, 核心转换模块。
  • @babel/plugins 或者 @babel/presets, 具体的转换规则。
  • @babel/polyfill, 低版本浏览器需要腻子。不过 babel 已经有新的使用方法来弥补 @babel/polyfill 存在的缺点。

后面我们会循序渐进的来深入了解。

3.config files

在前一章,使用的 yarn compiler:plugins 以及 yarn compiler:presets,都是直接在命令行中配置了 pluginspresets

这种方式不够优雅,而且不够友好。所以在理解 pluginspresets 之前,先来了解下 babel 提供的专门的配置文件来替代这种方式。

该配置文件有两种类型:

  1. 项目范围的配置
  • babel.config.json 文件。可使用扩展名有 .json.js.cjs.mjs
  1. 相对文件配置
  • .babelrc.json 文件。可使用扩展名有 .json.js.cjs.mjs。或者直接命名为 .babelrc
  • package.json 文件。 其中有 keybabel 的设置。

在项目根目录下设置这两种类型的文件。babel 在执行的时候会自动寻找。

1.项目范围的配置

babel@7.0 开始,具有了根目录的概念。默认为当前工作目录。(babel 命令执行的目录)。

项目范围的配置,默认的搜索行为是,在当前执行转译命令的目录中直接寻找babel.config.json,找到的话,正常编译,否则不会编译。

babel-config-demo/
.
├── package.json
├── node_modules
├── src
│   ├── main.js
│   └── package.json
├── babel.config.js
└── yarn.lock

假设在项目 babel-config-demo中的目录结构如上。

那么当在 babel-config-demo/ 目录下执行 babel 命令时,是可以正常转码的。而在 babel-config-demo/src 下无法转码。

另外也可以使用 configFile 选项来指明具体的 babel.config.json 路径。需要注意的是,该选项仅允许利用编程方式使用。即创建 js 文件,手动引入 @babel/core, 调用 api

此外,babel.config.json 也能对 node_modulessymlinked packages 内的文件进行转码。而 .babelrc.json 则不能。

从上面的论述中,我们简单总结下 babel.config.json 类文件的特点:

  • 必须存在于执行目录。执行命令的目录下,必须能找到 babel.config.json
  • 如果项目特殊,可以利用 configFile 选项,显式的指定 babel.config.json 的位置。
  • babel.config.json 也能对 node_modulessymlinked packages 内的文件进行转码。

2.相对文件的配置

相对文件的配置,默认搜索行为是,根据执行命令先定位到转译的目标文件,基于目标文件的位置,逐次向上排查.babelrc.json。此搜索过程中,有两点注意:

  • 在此搜索过程中,一旦遇到 package.json 时,此搜索就会停止。
  • 如果找到了 .babelrc.json ,则此 .babelrc.json 必须与执行命令在同一级目录。否则 .babelrc.json 会被忽略,文件不会转译。

示例一

babelrc-demo/
.
├── package.json
├── node_modules
├── src
│   ├── main.js
│   └── package.json
├── .babelrc
└── yarn.lock

babelrc-demo/ 下执行 yarn compiler 命令时,会先定位到 src/main.js 。然后顺此文件向上查找 .babelrc。但由于 src 文件夹内有 package.json,所以查找会立刻停止,直接在 src 下寻找 .babelrc。可想而知,并不会正常执行。

示例二

babelrc-demo/
.
├── package.json
├── node_modules
├── src
|   └── .babelrc
│   ├── main.js
│   └── package.json
├── .babelrc
└── yarn.lock

该例相比上例,在 src 下多了 .babelrc

同样,在 babelrc-demo/ 下执行 yarn compiler 命令时,会先定位到 src/main.js 。虽然此时在 src 下可以找到 .babelrc。但由于命令是在 babelrc-demo/ 下,而 .babelrc 是在 babelrc-demo/src/ 下,两者并不在同一目录,结果也是执行失败。

总结

在大部分情况下,使用项目范围的配置与相对文件的配置,差异并不大。

babel 之所以要将配置文件分为这两类,主要是为了方便开发者管理类似 @babel 这种 mono packages 项目。既能统一集中的管理通用的 babel 配置(项目范围的配置),又能根据各个 package 的实际情况做单独的配置(相对文件的配置)。当两种配置同时找到了的时候,相对文件的配置,将会与项目范围的配置进行合并,然后才应用到子package

4.plugins

就像前文已经提过的,babel 的核心转换功能是在@babel/core@babel/core 本身只是一个转换器,如果没有配置 plugins 声明转换规则的话,babel 什么都不会做。

插件分为两类:转换插件语法插件

转换插件

.babelrc 中配置:

{
  "plugins": ["@babel/plugin-transfrom-arrow-fuctions"]
}

这里列举些 es6 常见的插件:

箭头函数:@babel/plugin-transform-arrow-functions

class: @babel/plugin-transform-classes

for of: @babel/plugin-transform-for-of

语法插件

.babelrc 中配置:

{
  "parserOpts": {
    "plugins": ["jsx", "flow"]
  }
}

在使用某些转换插件的时候,会默认启用对应依赖的语法插件

所以一般我们不用过于注重这类插件的种类和配置。

插件顺序

  • 插件在 presets 前执行。
  • 插件顺序从前往后运行。
  • presets 顺序是颠倒的(从后往前)。这个是为了由于 babel 版本的迭代原因,保证向下兼容。

插件参数

给插件设置参数的时候,可以将插件项写作数组形式

{
  "plugins": [
    [
      "@babel/plugin-transform-arrow-functions", {
        "key": "value"
      }
    ]
  ]
}

5.presets

预设 是一组插件的集合。这是为了方便开发者在实际应用中无需再手动配置各类插件。

官方预设

babel 官方已经提供了一些预设。

  • @babel/preset-env
  • @babel/preset-flow
  • @babel/preset-react
  • @bbael/preset-typescript

更多预设

预设的使用配置跟插件的大致相同。

唯一要注意的是,预设的执行顺序与插件是相反的

@babel/preset-env

该预设可以称作环境预设。

提到环境,有非常重要的两点:代码转换polyfill。恰好 @babel/preset-env 帮我们把这两件事都比较优雅的实现了。

又由于浏览器的版本众多,我们必须在使用预设的时候告诉它,我们的目标浏览器是什么及是什么版本。也就是需要设置 browserslist

为了行文方便,本章节所有 preset-env 皆指代 @babel/preset-env

browserslist

babel 配合 preset-env使用,共有三种配置方法:

  1. .browserslistrc 在项目根目录下添加 .browserslistrc 文件。要注意的是,该文件是全局配置。譬如如果项目有 postcss,那么它也会读取该文件。
> 1%
last 2 versions
not ie <= 8
  1. package.json 中设置 browserslist该配置在项目范围中的作用及优先级同上项。 数组形式:
{
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}

字符串形式:

{
  "browserslist": "> 1%, last 2 versions, not ie <= 8"
}
  1. 给预设 @babel/preset-env 设置 target 参数。优先级在这三者中最高
{
  "presets": [
    [
      "@babel/preset-env", {
        "targets": [
          "> 1%",
          "last 2 versions",
          "not ie <= 8"
        ]
      }
    ]
  ]
}

options

下面是一些常见的设置属性。

  1. targets

string | Array<string> | { [string]: string },defaults to {}

用来设置目标浏览器。

  1. modules

"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false,defaults to auto

用来设置转译后的代码采用何种模块化方式。设置为 false时,将保留采用 ES Module

webpack 项目中,强烈建议设置为 false,将 import 交由 webpack 处理。

因为 webpack 可以对 ES Moduletree shaking

  1. useBuiltIns

"usage"| "entry" | false。default to false

用来配置 preset-env 如何处理 polyfill

  • "usage" 自动按需导入。
  • "entry" 入口文件处引入 polyfillpreset-env 会全量导入 polyfill
  • false 不再使用 polyfill

preset-env 并不内置 polyfill,它只是一系列插件的集合。所以我们在使用该配置属性前需要安装@babel/polyfill

babel@7.4.0 已开始弃用 @babel/polyfill。推荐使用core-js。当使用 core-js 时,需要配合下个属性进行设置。

  1. core-js

2, 3 or { version: 2 | 3, proposals: boolean }, defaults to 2

用来配置 core-js 的版本。

当设置其版本为 23 时,需要对应安装 core-js@2core-js@3

  1. ignoreBrowserslistConfig

Boolean, defaults to false.

配置是否忽略 browserslist 文件及 package.json 中的 browserslist 键。

6.polyfill

首先要明确的一点是,什么是 polyfill? 为什么需要 polyfill?

定义

polyfill 意为腻子。它负责抹平不同环境下的API差异。

这里有一篇之前更新的文章来介绍它。

babel 本身只能转换已经存在的语法,譬如可以将箭头函数转换成普通函数、将class转换成构造函数。

但它不能转换新语法,譬如 Promiseincludesmap 等,这些指的都是在全局或者ObjectArray 等的原型上新增的方法。

@babel/polyfill

该库本来是 babel 提供 polyfill 的独立库。

babel@7.4.0 开始,推荐直接引入这俩库来代替 @babel/polyfill

import 'core-js/stable'
import 'regenerator-runtime/runtime'

@babel/polyfill 的使用

原始使用

直接在入口文件中引入全量包:

import '@babel/polyfill'

结合 @babel/preset-env 使用

babel 不推荐直接在入口文件当中引入 @babel/polyfill。因为这种方式会引入全量包,导致一些不需要的 polyfill 也会加载进去,增大了包的体积。 因此,往往结合 preset-env 使用 polyfill

  1. 当设置 useBuiltInsfalse,或者不设置 useBuiltIns 选项。需要利用 webpackentry 属性,将其设置为数组形式
// webpack.config.js
module.exports = {
  entry: ['@babel/polyfill', './src/main.js']
}
  1. 当设置 useBuiltIns'entry',根据配置项 corejs 的值,具体配置也是不同的。'entry'意为入口babel会在入口处寻找 polyfill 并导入全量包。
// 当 corejs:2 时,入口文件main.js引入
import '@babel/polyfill'
// 额外安装 yarn add core-js@2

// 当 corejs:3 时,入口文件main.js引入
import "core-js/stable"
import "regenerator-runtime/runtime"
// 额外安装 yarn add core-js@3
  1. 当设置 useBuiltIns'usage'polyfill自动按需引入,只会加载用到的 polyfill, 但要注意的是仍需安装 @babel/polyfill 包。只是不需要手动配置而已。

由于 babel 逐渐弃用 polyfill,所以在设置 useBuiltIns 时,有可能会遇见错误:

WARNING: 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

显而易见,babel 推荐使用 core-js。所以解决报错方法是安装 core-js 并在 preset-env 声明其使用版本。

@babel/polyfill 的废弃

@babel/polyfill 被废弃的原因有两个:

  1. 每个转译的文件都可能会生成大量重复的 helper 工具函数,代码冗余,包体积增大。
  2. @babel-polyfill修改全局变量。不利于第三方公共库的使用。

我们来看下使用 preset-env@babel/polyfill 转译后的代码。摘抄了下核心部分:

"use strict";
require("@babel/polyfill");
var _this = void 0;

// ①文件顶部存在大量的 helper 工具函数
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

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); return Constructor; }

// ②这里是 async 的转译代码。可以发现凭空多了 regeneratorRuntime 这个变量。
function _fn() {
  _fn = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
    var result;
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.prev = 0;
            _context.next = 3;
            return promise;

          case 3:
            result = _context.sent;
            console.log(result);
            _context.next = 10;
            break;

          case 7:
            _context.prev = 7;
            _context.t0 = _context["catch"](0);
            console.warn('error', _context.t0);

          case 10:
            console.log('--- after promise ---');

          case 11:
          case "end":
            return _context.stop();
        }
      }
    }, _callee, null, [[0, 7]]);
  }));
  return _fn.apply(this, arguments);
}

从上述代码可以发现两部分问题:

  1. _interopRequireDefaultasyncGeneratorStep_asyncToGenerator_classCallCheck_defineProperties_createClass是由 preset-env 生成的 helper 工具类函数。如果有多个 babel 转译的文件,这些文件中都会存在这些函数。

  2. 多了一个全局变量regeneratorRuntime@babel/polyfill 的引入会暴露这个全局变量以保证代码的正常运行。否则运行代码,会报错 regeneratorRuntime is not defined

这两部分问题的解决是依赖于@babel/plugin-transfrom-runtime。它会把上面的 helper 工具函数以及 regeneratorRuntime 统一从 @babel/runtime 这个库中导入。

7.transform runtime

在上一章的结尾处,我们简单引出了 @babel/plugin-transform-runtime

它主要用来解决 @babel/polyfill 结合 preset-env 使用时出现的问题。

这一章我们详细介绍下它。

安装

@babel/plugin-transform-runtimebabel 的一个插件。它通常需要结合 @babel/runtime 使用。

yarn add @babel/plugin-transform-runtime -D

# 这个库可能不用手动安装 在安装上面插件时,babel可能会自动安装runtime这个库。另外由于是生产依赖,所以这里不加 '-D'
yarn add @babel/runtime

使用原因

@babel/plugin-transform-runtime 用来替代 @babel/polyfill。它的主要优势有两个:

  1. 在转译的单个代码文件中,就会存在很多的 helper 工具函数,如果项目中多个文件转译,可想而知,会存在大量重复的 helper 工具函数,增大项目包体积。

@babel/plugin-transform-runtime 会将所有的 helper 工具函数统一从 @babel/runtime 库中引入。

  1. @babel/polyfill 会污染全局作用域,不利于在第三方库使用。

@babel/plugin-transform-runtime 会创建一个沙盒环境,保证代码环境不被污染。

使用方法

因为 @babel/plugin-transform-runtime 是插件,所以直接用插件形式配置即可。以 babelrc 文件为例:

{
  "plugins": [
    [
      "@babel/plugin-transform-runtime", {
        "corejs": 3,
        "helpers": true,
        "regenerator": true,
        "useESModules": false,
      }
    ]
  ],
  "presets": []
}

配置选项

1.corejs

2, 3 or { version: 2 | 3, proposals: boolean }, defaults to 2

用来声明 @babel/plugin-transform-runtime 转译代码后的 core-js 版本。

该选项相当于替代 polyfill。设置后无需再使用 polyfill。会有对应版本的 @babel/runtime-corejs

core-js@2 仅支持全局变量(例如 Promise )和静态属性(例如 Array.from )。

core-js@3 还支持实例属性 (例如 [].includes )。

所以一般我们设置为 corejs: 3

2.helpers

Boolean, defaults to true.

设置是否将 _asyncToGenerator_classCallCheck_createClasshelpers 转换成 模块化导入。

// polyfill下 是在文件头部声明一堆函数

// transform-runtime下设置 corejs: 3
import _asyncToGenerator from "@babel/runtime-corejs3/helpers/esm/asyncToGenerator";
import _setTimeout from "@babel/runtime-corejs3/core-js-stable/set-timeout";
import _createClass from "@babel/runtime-corejs3/helpers/esm/createClass";

3.regenerator

Boolean, defaults to true.

设置是否将 _regeneratorRuntime 转换成 模块化导入。

// polyfill下 是一个全局变量

// transform-runtime下设置 corejs: 3
import _regeneratorRuntime from "@babel/runtime-corejs3/regenerator"

@babel/runtime

当不设置 @babel/plugin-transform-runtimecorejs 选项时,默认会从 @babel/runtime 中导入 helpers 以及 regenerator

注意:不设置 corejs 时,转译后的代码类同于设置 corejs: 2不支持实例属性

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));

var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

@babel/runtime-corejs2

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");

var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs2/regenerator"));

var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/asyncToGenerator"));

var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));

@babel/runtime-corejs3

import _regeneratorRuntime from "@babel/runtime-corejs3/regenerator";

import _includesInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/includes";

import _asyncToGenerator from "@babel/runtime-corejs3/helpers/esm/asyncToGenerator";

import _setTimeout from "@babel/runtime-corejs3/core-js-stable/set-timeout";

import _Promise from "@babel/runtime-corejs3/core-js-stable/promise";

import _classCallCheck from "@babel/runtime-corejs3/helpers/esm/classCallCheck";

import _createClass from "@babel/runtime-corejs3/helpers/esm/createClass";

8.总结

在前面的介绍之后,我们来总结下 babel@7.0 版本及以上的配置方法。

浏览器兼容方面,通常有两种:@babel/polyfill@babel/plugin-transform-runtime

预设方面,通常采用 @babel/preset-env 即可。根据上面采用的 polyfilltransform-runtime 不同,preset-env 的配置有所差异。

下面给出对应配置:

polyfill

// babel.config.js
module.exports = {
 presets: [
   ["@babel/preset-env", {
    // 默认采用ES Module, 利于webpack进行tree shaking
    modules: false,
    // 可选值 usage entry false(不推荐entry或false)
    useBuiltIns: "usage",
    // corejs 版本(设置了useBuiltIns的话,corejs是必配的)
    corejs: 3,
    // 目标浏览器 preset-env必须设置,或者利用.browerslistrc文件替代
    targets: [
      "> 1%",
      "last 2 versions",
      "not ie <= 8"
    ]
   }]
 ]
}

如果上面 useBuiltIns 设置的是 entry,那么需要在入口文件中手动引入 polyfill

另外根据 corejs 设置的版本不同,引入 polyfill 的方式也不同。如下:

// corejs: 2
import '@babel/polyfill'

// corejs: 3
import "core-js/stable"
import "regenerator-runtime/runtime"

transform-runtime

当然,我们知道,babel 已经不推荐直接导入 polyfill 的方式,转而推荐使用@babel/plugin-transform-runtime 插件。

// babel.config.js
module.exports = {
  presets: [
    ["@babel/preset-env", {
      // 采用 EsModule
      modules: false,
      targets: [
        "> 1%",
        "last 2 versions",
        "not ie <= 8"
      ]
    }]
  ]
  plugins: [
    ["@babel/plugin-transform-runtime", {
      // useBuiltIns属性已被默认设置
      // 推荐corejs设置3,因为这个版本支持实例属性
      corejs: 3
    }]
  ]
}

另外,要注意的是,根据 corejs 的版本,需要安装对应的 runtime-corejs

# corejs: 2
yarn add @babel/runtime-corejs2

# corejs: 3
yarn add @babel/runtime-corejs3