Skip to content

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

定义

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

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

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

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

@babel/polyfill

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

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

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

@babel/polyfill 的使用

原始使用

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

js
import '@babel/polyfill'

结合 @babel/preset-env 使用

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

  1. 当设置 useBuiltInsfalse,或者不设置 useBuiltIns 选项。需要利用 webpackentry 属性,将其设置为数组形式
js
// webpack.config.js
module.exports = {
  entry: ['@babel/polyfill', './src/main.js']
}
  1. 当设置 useBuiltIns'entry',根据配置项 corejs 的值,具体配置也是不同的。'entry'意为入口babel会在入口处寻找 polyfill 并导入全量包。
js
// 当 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 包。只是不需要手动配置而已。

TIP

由于 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 转译后的代码。摘抄了下核心部分:

js
"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-transform-runtime。它会把上面的 helper 工具函数以及 regeneratorRuntime 统一从 @babel/runtime 这个库中导入。