polyfill

494 阅读5分钟

polyfill实现的关键在babel-loader的useBuiltIns参数,

const path = require('path');
module.exports = {
    mode: 'development',
    devtool: false,
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'main.js'
    },
    module: {
        rules: [
            {
                test: /.js?$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        targets: {
                        		browsers: ["last 2 versions"]
                        },
                       	presets: [
                          	["@babel/preset-env", { useBuiltIns: false }]
                        ]
                    }
                }
            }
        ]
    },
    plugins: []
};

如果给useBuiltIns设置值是false,那所有的polyfill都会被引入,target.browsers参数将会被忽略


所以更多的时候我们会将useBuiltIns值设置为"entry",但设置为entry需要注意以下问题:

  • 在项目入口引入一次(多次引入会报错)
  • "useBuiltIns": "entry" 根据配置的浏览器兼容,引入浏览器不兼容的 polyfill。需要在入口文件手动添加 import '@babel/polyfill',会自动根据 browserslist 替换成浏览器不兼容的所有 polyfill
  • 这里需要指定 core-js 的版本,corejs默认是2,
  • 如果配置 corejs: 3, 则入口文件中引入的import '@babel/polyfill' 需要改成 import 'core-js/stable';import 'regenerator-runtime/runtime';
    • corejs默认是2

corejs设置为3时webpack.config:

{
  test: /.js?$/,
  exclude: /node_modules/,
  use: {
      loader: 'babel-loader',
      options: {
         presets: [
           	[
              	"@babel/preset-env",
              	{
                  	useBuiltIns: 'entry',
                  	corejs: { version: 3 }
                }
            ]
         ]
      }
  }
}

corejs设置为3时入口文件:

import 'core-js/stable';
import 'regenerator-runtime/runtime';
let sum = (a, b) => a + b;
let promise = Promise.resolve();
console.log([1, 2, 3].find(item => item === 2));

corejs设置为2时webpack.config:

{
  test: /.js?$/,
  exclude: /node_modules/,
  use: {
      loader: 'babel-loader',
      options: {
         presets: [
           	[
              	"@babel/preset-env",
              	{
                  	useBuiltIns: 'entry',
                  	corejs: { version: 2 }
                }
            ]
         ]
      }
  }
}

corejs设置为2时入口文件:

import '@babel/polyfill';
let sum = (a, b) => a + b;
let promise = Promise.resolve();
console.log([1, 2, 3].find(item => item === 2));

使用@babel/preset-env,并将useBuiltIns的值设置为entry这个参数看起来很完美,但它实现的方式是给原型添加方法,在我们的例子中就是给Array.prototype添加find方法

于是,有了另一种方法:babel/runtime,使用方式如下:

import Promise from 'babel-runtime/core-js/promise';
import Promise from 'babel-runtime/core-js/array/find';
const p = new Promise(()=> {});
const res = [1,2,3].find(item => item === 2)

使用前,需要在项目中安装babel-runtime这个包:npm i babel-runtime --save-dev

但是这样做,就会添加大量的import引入代码,变得很繁琐,于是有了解决这个问题的办法:

babel-plugin-transform-runtime

安装命令:npm i @babel/plugin-transform-runtime @babel/runtime-corejs3 --save-dev

经测试,需要配合presets一起使用

{
  test: /.js$/,
  exclude: /node_modules/,
  use: {
    loader: 'babel-loader',
    options: {
      targets: {
        "browsers": ["IE 10"]
      },
      presets: [
        ["@babel/preset-env", {
          useBuiltIns: false
        }]
      ],
      plugins: [
        ["@babel/plugin-transform-runtime", {
          corejs: { version: 3 }
        }]
      ]
    }
  }
}

经过测试,presets还是要设置,具体设置为什么值,需要再看babel的文档

从编译后的代码可以看到,插件自动帮我们加上了方法的引用:

var Promise = require("./node_modules/@babel/runtime-corejs3/core-js-stable/promise.js");
var find = require("./node_modules/@babel/runtime-corejs3/core-js-stable/instance/find.js");
let promise = Promise.resolve();
console.log(promise);
console.log([1, 2, 3].find(item => item === 2));
console.log(find.call([1, 2, 3], item => item === 2));

当我们使用 ES6 的静态事件或内置对象时自动引入 babel-runtime/core-js

有时我们转换代码时会生成大量工具方法,例如class的转换:

class A {

}
class B extends A {

}
console.log(new B());

编译后变为:

__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_object_define_property__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @babel/runtime-corejs3/core-js-stable/object/define-property */ "./node_modules/@babel/runtime-corejs3/core-js-stable/object/define-property.js");
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_object_define_property__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs3_core_js_stable_object_define_property__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_object_set_prototype_of__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @babel/runtime-corejs3/core-js-stable/object/set-prototype-of */ "./node_modules/@babel/runtime-corejs3/core-js-stable/object/set-prototype-of.js");
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_object_set_prototype_of__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs3_core_js_stable_object_set_prototype_of__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_object_get_prototype_of__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @babel/runtime-corejs3/core-js-stable/object/get-prototype-of */ "./node_modules/@babel/runtime-corejs3/core-js-stable/object/get-prototype-of.js");
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_object_get_prototype_of__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs3_core_js_stable_object_get_prototype_of__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_reflect_construct__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @babel/runtime-corejs3/core-js-stable/reflect/construct */ "./node_modules/@babel/runtime-corejs3/core-js-stable/reflect/construct.js");
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_reflect_construct__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs3_core_js_stable_reflect_construct__WEBPACK_IMPORTED_MODULE_5__);
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_object_create__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @babel/runtime-corejs3/core-js-stable/object/create */ "./node_modules/@babel/runtime-corejs3/core-js-stable/object/create.js");
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_object_create__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs3_core_js_stable_object_create__WEBPACK_IMPORTED_MODULE_6__);
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_symbol__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime-corejs3/core-js-stable/symbol */ "./node_modules/@babel/runtime-corejs3/core-js-stable/symbol.js");
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_symbol__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs3_core_js_stable_symbol__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_symbol_iterator__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime-corejs3/core-js-stable/symbol/iterator */ "./node_modules/@babel/runtime-corejs3/core-js-stable/symbol/iterator.js");
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_symbol_iterator__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs3_core_js_stable_symbol_iterator__WEBPACK_IMPORTED_MODULE_1__);
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof (_babel_runtime_corejs3_core_js_stable_symbol__WEBPACK_IMPORTED_MODULE_0___default()) && "symbol" == typeof (_babel_runtime_corejs3_core_js_stable_symbol_iterator__WEBPACK_IMPORTED_MODULE_1___default()) ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof (_babel_runtime_corejs3_core_js_stable_symbol__WEBPACK_IMPORTED_MODULE_0___default()) && obj.constructor === (_babel_runtime_corejs3_core_js_stable_symbol__WEBPACK_IMPORTED_MODULE_0___default()) && obj !== (_babel_runtime_corejs3_core_js_stable_symbol__WEBPACK_IMPORTED_MODULE_0___default().prototype) ? "symbol" : typeof obj; }, _typeof(obj); }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = _babel_runtime_corejs3_core_js_stable_object_create__WEBPACK_IMPORTED_MODULE_6___default()(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); _babel_runtime_corejs3_core_js_stable_object_define_property__WEBPACK_IMPORTED_MODULE_2___default()(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }

function _setPrototypeOf(o, p) { _setPrototypeOf = (_babel_runtime_corejs3_core_js_stable_object_set_prototype_of__WEBPACK_IMPORTED_MODULE_3___default()) || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = _babel_runtime_corejs3_core_js_stable_reflect_construct__WEBPACK_IMPORTED_MODULE_5___default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }

function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); }

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(_babel_runtime_corejs3_core_js_stable_reflect_construct__WEBPACK_IMPORTED_MODULE_5___default())) return false; if ((_babel_runtime_corejs3_core_js_stable_reflect_construct__WEBPACK_IMPORTED_MODULE_5___default().sham)) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(_babel_runtime_corejs3_core_js_stable_reflect_construct__WEBPACK_IMPORTED_MODULE_5___default()(Boolean, [], function () {})); return true; } catch (e) { return false; } }

function _getPrototypeOf(o) { _getPrototypeOf = (_babel_runtime_corejs3_core_js_stable_object_set_prototype_of__WEBPACK_IMPORTED_MODULE_3___default()) ? (_babel_runtime_corejs3_core_js_stable_object_get_prototype_of__WEBPACK_IMPORTED_MODULE_4___default()) : function _getPrototypeOf(o) { return o.__proto__ || _babel_runtime_corejs3_core_js_stable_object_get_prototype_of__WEBPACK_IMPORTED_MODULE_4___default()(o); }; return _getPrototypeOf(o); }

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; _babel_runtime_corejs3_core_js_stable_object_define_property__WEBPACK_IMPORTED_MODULE_2___default()(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); _babel_runtime_corejs3_core_js_stable_object_define_property__WEBPACK_IMPORTED_MODULE_2___default()(Constructor, "prototype", { writable: false }); return Constructor; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var A = /*#__PURE__*/_createClass(function A() {
  _classCallCheck(this, A);
});

var B = /*#__PURE__*/function (_A) {
  _inherits(B, _A);

  var _super = _createSuper(B);

  function B() {
    _classCallCheck(this, B);

    return _super.apply(this, arguments);
  }

  return _createClass(B);
}(A);

console.log(new B());

为了避免这些内联的工具方法在很多地方重复出现,我们可以将@babel/plugin-transform-runtime的helpers属性设置为true

{
  test: /.js$/,
  exclude: /node_modules/,
  use: {
    loader: 'babel-loader',
    options: {
      targets: {
        "browsers": ["IE 10"]
      },
      presets: [
        ["@babel/preset-env", {
          useBuiltIns: false
        }]
      ],
      plugins: [
        ["@babel/plugin-transform-runtime", {
          corejs: { version: 3 },
          helpers: true
        }]
      ]
    }
  }
}

打包后发现,一部分工具方法变成了模块引入的方式,例如possibleConstructorReturn、getPrototypeOf、createClass:

__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_reflect_construct__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime-corejs3/core-js-stable/reflect/construct */ "./node_modules/@babel/runtime-corejs3/core-js-stable/reflect/construct.js");
/* harmony import */ var _babel_runtime_corejs3_core_js_stable_reflect_construct__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_corejs3_core_js_stable_reflect_construct__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _babel_runtime_corejs3_helpers_inherits__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime-corejs3/helpers/inherits */ "./node_modules/@babel/runtime-corejs3/helpers/esm/inherits.js");
/* harmony import */ var _babel_runtime_corejs3_helpers_possibleConstructorReturn__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @babel/runtime-corejs3/helpers/possibleConstructorReturn */ "./node_modules/@babel/runtime-corejs3/helpers/esm/possibleConstructorReturn.js");
/* harmony import */ var _babel_runtime_corejs3_helpers_getPrototypeOf__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @babel/runtime-corejs3/helpers/getPrototypeOf */ "./node_modules/@babel/runtime-corejs3/helpers/esm/getPrototypeOf.js");
/* harmony import */ var _babel_runtime_corejs3_helpers_createClass__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @babel/runtime-corejs3/helpers/createClass */ "./node_modules/@babel/runtime-corejs3/helpers/esm/createClass.js");
/* harmony import */ var _babel_runtime_corejs3_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @babel/runtime-corejs3/helpers/classCallCheck */ "./node_modules/@babel/runtime-corejs3/helpers/esm/classCallCheck.js");
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0,_babel_runtime_corejs3_helpers_getPrototypeOf__WEBPACK_IMPORTED_MODULE_3__["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0,_babel_runtime_corejs3_helpers_getPrototypeOf__WEBPACK_IMPORTED_MODULE_3__["default"])(this).constructor; result = _babel_runtime_corejs3_core_js_stable_reflect_construct__WEBPACK_IMPORTED_MODULE_0___default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0,_babel_runtime_corejs3_helpers_possibleConstructorReturn__WEBPACK_IMPORTED_MODULE_2__["default"])(this, result); }; }

function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(_babel_runtime_corejs3_core_js_stable_reflect_construct__WEBPACK_IMPORTED_MODULE_0___default())) return false; if ((_babel_runtime_corejs3_core_js_stable_reflect_construct__WEBPACK_IMPORTED_MODULE_0___default().sham)) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(_babel_runtime_corejs3_core_js_stable_reflect_construct__WEBPACK_IMPORTED_MODULE_0___default()(Boolean, [], function () {})); return true; } catch (e) { return false; } }

var A = /*#__PURE__*/(0,_babel_runtime_corejs3_helpers_createClass__WEBPACK_IMPORTED_MODULE_4__["default"])(function A() {
  (0,_babel_runtime_corejs3_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_5__["default"])(this, A);
});

var B = /*#__PURE__*/function (_A) {
  (0,_babel_runtime_corejs3_helpers_inherits__WEBPACK_IMPORTED_MODULE_1__["default"])(B, _A);

  var _super = _createSuper(B);

  function B() {
    (0,_babel_runtime_corejs3_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_5__["default"])(this, B);

    return _super.apply(this, arguments);
  }

  return (0,_babel_runtime_corejs3_helpers_createClass__WEBPACK_IMPORTED_MODULE_4__["default"])(B);
}(A);

console.log(new B());

这样做的代价就是会多一些公共模块出来,如果这些所谓的公共模块引用次数较少,可能会导致包体积反而变大

@babel/plugin-transform-runtime还有一个参数:regenerator,作用是 是否开启generator函数转换成使用regenerator runtime来避免污染全局域(原理、如何生效待学习)

babel最佳实践

项目开发:

  • useBuiltIns使用usage
  • plugin-transform-runtime只使用其移除内联复用的辅助函数的特性,减小打包体积
{
  "presets": [
      [
          "@babel/preset-env",
          {
              "useBuiltIns": "usage",
              "corejs": 3
          }
      ]
  ],
  "plugins": [
      [
          "@babel/plugin-transform-runtime",
          {
              "corejs": false
          }
      ]
  ]
}

项目开发是自己业务代码,只要自己开发测试能跑通,直接上线了,未来不会有别人来调用你的代码,所以对修改Array、Object等的原型添加方法这种污染全局作用域的方式容忍度较高

类库开发:

  • 类库开发尽量不使用污染全局环境的polyfill,因此@babel/preset-env只发挥语法转换的功能
  • polyfill由@babel/plugin-transform-runtime来处理,推荐使用core-js@3
{
    "presets": [
        [
            "@babel/preset-env"
        ]
    ],
    "plugins": [
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs": {
                    "version": 3
                }
            }
        ]
    ]
}