babel polyfill runtime 浅析

前言

babel只能转义ES6语法,好比箭头函数,可是遇到ES6新增的api就无能为力了,好比Promise和includes。对于这些新增的api,须要polyfill去作兼容。babel提供了两个plugin来处理这些api的polyfill。node

@babel/preset-env

若是使用preset-env来处理polyfill,须要安装@babel/polyfill。webpack

@babel/preset-env经过配置项,可以智能的帮你处理ES6的语法。它经过读取项目中的browserslist配置,来决定须要对哪些语法进行处理。一个配置的例子es6

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
           "chrome": "58",
           "ie": "11"
        }
        "useBuiltIns": "entry",
        "modules": false,
        
      }
    ]
  ]
}
复制代码

options.targets

用来配置须要支持的的环境,不只支持浏览器,还支持node。web

若是没有配置targets选项,就会读取项目中的browserslist配置项。chrome

options.loose

默认值是false,若是preset-env中包含的plugin支持loose的设置,那么能够经过这个字段来作统一的设置。api

options.modules

"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false,默认值是autopromise

用来转换ES6的模块语法。若是使用false,将不会对文件的模块语法进行转化。浏览器

若是要使用webpack中的一些新特性,好比tree shaking 和 sideEffects,就须要设置为false,对ES6的模块文件不作转化,由于这些特性只对ES6的模块有效。bash

options.useBuiltIns

"usage" | "entry" | false,默认值是falsebabel

这个配置项主要是用来处理@babel/polyfill。

  • 设置为false时,会把@babel/polyfill这个包引进来,忽略targets配置项和项目里的browserslist配置
  • 设置为entry时,在整个项目里,只须要引入一次@babel/polyfill,它会根据targets和browserslist,而后只加载目标环境不支持的api文件
  • 设置为usage时,babel会在目标环境的基础上,只加载项目里用的那些不支持的api的文件,作到按需加载

其余的一些配置项能够看 官方文档

@babel/plugin-transform-runtime

这个插件能够经过一些helper函数的注入来减小语法转换函数的开销。

const c = {...b};
复制代码

经过babel编译之后,对象的解构语法会被编译为下面的文件

var _extends = Object.assign || function (target) { 
  for (var i = 1; i < arguments.length; i++) { 
    var source = arguments[i]; 
    for (var key in source) { 
      if (Object.prototype.hasOwnProperty.call(source, key)) {
        target[key] = source[key];   
      }
    }
  } 
  return target;
};

var c = _extends({}, b);
复制代码

若是不少文件都用到了解构语法,那么每一个文件都会生成一个相同的_extends方法,@babel/plugin-transform-runtime会使用helper函数来处理对应的语法,避免这种重复定义方法的问题,使用这个插件后,会生成下面的文件

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

var c = (0, _objectSpread2.default)({}, b);
复制代码

对插件进行配置

{
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": false,
        "helpers": true,
        "regenerator": true,
        "useESModules": false
      }
    ]
  ]
}
复制代码

babel7相比于babel6,删除了polyfill和useBuiltIns这两个配置。

options.corejs

boolean or number,默认值是false

e.g. ['@babel/plugin-transform-runtime', { corejs: 2 }]

当设置为false时,只对语法进行转换,不对api进行处理

当设置为2的时候,须要安装@babel/runtime-corejs2,这时会对api进行处理。这里须要注意,不能polyfill Array.includes这种须要重写property的api,这种会污染全局变量,须要在项目里使用@babel/polyfill来处理。

options.helpers

默认值是true,用来开启是否使用helper函数来重写语法转换的函数。

options.useESModules

默认值是false,是否对文件使用ES的模块语法,使用ES的模块语法能够减小文件的大小。

// useESModules:false

exports.__esModule = true;

exports.default = function(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
};
复制代码
// useESModules:true

export default function(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}
复制代码

@babel/plugin-transform-runtime默认状况下安装@babel/runtime这个库,即corejs为false时使用;当corejs设置为2时,须要安装使用@babel/runtime-corejs2。runtime和runtime-corejs2这两个库惟一的区别是,corejs2这个库增长了对core-js这个库的依赖,而core-js是用来对ES6各个语法polyfill的库,因此在corejs为false的状况下,只能作语法的转换,并不能polyfill任何api。

@babel/polyfill && @babbel/runtime

polyfill和runtime均可以用来对api进行垫片处理,可是二者还有必定的不一样。使用polyfill的时候,会污染全局变量;而runtime的时候,会使用局部变量来处理,不会污染全局变量。这也是为何runtime没法处理原型上的api的缘由,由于要模拟这些api,必需要污染全局变量。

core-js

@babel/polyfill和@babel/runtime-corejs2都使用了core-js(v2)这个库来进行api的处理。

这个库有两个核心的文件夹,分别是library和modules。runtime使用library这个文件夹,polyfill使用modules这个文件夹。

  • library使用helper的方式,局部实现某个api,不会污染全局变量
  • modules以污染全局变量的方法来实现api

library和modules包含的文件基本相同,最大的不一样是_export.js这个文件。

core-js/modules/_exports.js文件以下

var global = require('./_global');
var core = require('./_core');
var hide = require('./_hide');
var redefine = require('./_redefine');
var ctx = require('./_ctx');
var PROTOTYPE = 'prototype';

var $export = function (type, name, source) {
  var IS_FORCED = type & $export.F;
  var IS_GLOBAL = type & $export.G;
  var IS_STATIC = type & $export.S;
  var IS_PROTO = type & $export.P;
  var IS_BIND = type & $export.B;
  var target = IS_GLOBAL ? global : IS_STATIC ? global[name] || (global[name] = {}) : (global[name] || {})[PROTOTYPE];
  var exports = IS_GLOBAL ? core : core[name] || (core[name] = {});
  var expProto = exports[PROTOTYPE] || (exports[PROTOTYPE] = {});
  var key, own, out, exp;
  if (IS_GLOBAL) source = name;
  for (key in source) {
    // contains in native
    own = !IS_FORCED && target && target[key] !== undefined;
    // export native or passed
    out = (own ? target : source)[key];
    // bind timers to global for call from export context
    exp = IS_BIND && own ? ctx(out, global) : IS_PROTO && typeof out == 'function' ? ctx(Function.call, out) : out;
    // extend global
    if (target) redefine(target, key, out, type & $export.U);
    // export
    if (exports[key] != out) hide(exports, key, exp);
    if (IS_PROTO && expProto[key] != out) expProto[key] = out;
  }
};
global.core = core;
// type bitmap
$export.F = 1;   // forced
$export.G = 2;   // global
$export.S = 4;   // static
$export.P = 8;   // proto
$export.B = 16;  // bind
$export.W = 32;  // wrap
$export.U = 64;  // safe
$export.R = 128; // real proto method for `library`
module.exports = $export;
复制代码

core-js/library/_exports.js文件以下

var global = require('./_global');
var core = require('./_core');
var ctx = require('./_ctx');
var hide = require('./_hide');
var has = require('./_has');
var PROTOTYPE = 'prototype';

var $export = function (type, name, source) {
  var IS_FORCED = type & $export.F;
  var IS_GLOBAL = type & $export.G;
  var IS_STATIC = type & $export.S;
  var IS_PROTO = type & $export.P;
  var IS_BIND = type & $export.B;
  var IS_WRAP = type & $export.W;
  var exports = IS_GLOBAL ? core : core[name] || (core[name] = {});
  var expProto = exports[PROTOTYPE];
  var target = IS_GLOBAL ? global : IS_STATIC ? global[name] : (global[name] || {})[PROTOTYPE];
  var key, own, out;
  if (IS_GLOBAL) source = name;
  for (key in source) {
    // contains in native
    own = !IS_FORCED && target && target[key] !== undefined;
    if (own && has(exports, key)) continue;
    // export native or passed
    out = own ? target[key] : source[key];
    // prevent global pollution for namespaces
    exports[key] = IS_GLOBAL && typeof target[key] != 'function' ? source[key]
    // bind timers to global for call from export context
    : IS_BIND && own ? ctx(out, global)
    // wrap global constructors for prevent change them in library
    : IS_WRAP && target[key] == out ? (function (C) {
      var F = function (a, b, c) {
        if (this instanceof C) {
          switch (arguments.length) {
            case 0: return new C();
            case 1: return new C(a);
            case 2: return new C(a, b);
          } return new C(a, b, c);
        } return C.apply(this, arguments);
      };
      F[PROTOTYPE] = C[PROTOTYPE];
      return F;
    // make static versions for prototype methods
    })(out) : IS_PROTO && typeof out == 'function' ? ctx(Function.call, out) : out;
    // export proto methods to core.%CONSTRUCTOR%.methods.%NAME%
    if (IS_PROTO) {
      (exports.virtual || (exports.virtual = {}))[key] = out;
      // export proto methods to core.%CONSTRUCTOR%.prototype.%NAME%
      if (type & $export.R && expProto && !expProto[key]) hide(expProto, key, out);
    }
  }
};
// type bitmap
$export.F = 1;   // forced
$export.G = 2;   // global
$export.S = 4;   // static
$export.P = 8;   // proto
$export.B = 16;  // bind
$export.W = 32;  // wrap
$export.U = 64;  // safe
$export.R = 128; // real proto method for `library`
module.exports = $export;

复制代码

能够看出,library下的这个$export方法,会实现一个wrapper函数,防止污染全局变量。

var p = new Promise();


// @babel/polyfill
require("core-js/modules/es6.promise");
var p = new Promise();


// @babel/runtime-corejs2
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
var a = new _promise.default();

复制代码

从上面这个例子能够看出,对于Promise这个api,@babel/polyfill引用了core-js/modules中的es6.promise.js文件,由于是对全局变量进行处理,因此赋值语句不用作处理;@babel/runtime-corejs2会生成一个局部变量_promise,而后把Promise都替换成_promise,这样就不会污染全局变量了。

项目和库开发

在项目开发的时候,使用@babel/polyfill;在开发库的时候,使用runtime。

若是在库开发的时候,用到了一些新的api,能够选择不要处理,把选择权留给开发者,让开发者在项目中使用@babel/polyfill去处理。

由于在项目中都会排除node_modules里面的js文件,加快项目的编译速度。出于这个考虑,不建议使用把env的useBuiltIns设置为usage,这样可能会致使node_modules里面的库使用了某些须要polyfill的api,可是咱们又没有引入对应的polyfill文件,在某些环境会出现bug。

在项目里,建议使用entry这个配置,并配合browserslist,对于某些已经被目标环境支持的api不用引入对应的polyfill文件,减小项目的文件体积。

相关文章
相关标签/搜索