美文网首页
babel polyfill runtime 浅析

babel polyfill runtime 浅析

作者: sitherine | 来源:发表于2019-05-29 11:01 被阅读0次

    作者:weixin_34163741
    来源:CSDN
    原文:https://blog.csdn.net/weixin_34163741/article/details/88015827

    前言

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

    @babel/preset-env

    如果使用preset-env来处理polyfill,需要安装@babel/polyfill。

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

    {
      "presets": [
        [
          "@babel/preset-env",
          {
            "targets": {
               "chrome": "58",
               "ie": "11"
            }
            "useBuiltIns": "entry",
            "modules": false,
            
          }
        ]
      ]
    }
    
    • options.targets
      用来配置需要支持的的环境,不仅支持浏览器,还支持node。
      如果没有配置targets选项,就会读取项目中的browserslist配置项。

    • options.loose
      默认值是false,如果preset-env中包含的plugin支持loose的设置,那么可以通过这个字段来做统一的设置。

    • options.modules
      "amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false,默认值是auto
      用来转换ES6的模块语法。如果使用false,将不会对文件的模块语法进行转化。
      如果要使用webpack中的一些新特性,比如tree shaking 和 sideEffects,就需要设置为false,对ES6的模块文件不做转化,因为这些特性只对ES6的模块有效。

    • options.useBuiltIns
      "usage" | "entry" | false,默认值是false
      这个配置项主要是用来处理@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文件,减少项目的文件体积。

    相关文章

      网友评论

          本文标题:babel polyfill runtime 浅析

          本文链接:https://www.haomeiwen.com/subject/gdeetctx.html