结合Babel 7.4.0 谈一下Babel-runtime 和 Babel-polyfill

12,522 阅读6分钟

摘要:对于初学者而言,对于Babel的使用可能一直稀里糊涂,对于很多参数的配置和区别也都是一知半解。本文将通过一些使用 babel-runtimebabel-polyfill 的简单例子 ,帮助读者区分理解两者的使用场景。

前言

我们知道Babel是一个通用型的JS编译器,通过Babel我们可以把最新标准编写的JS代码向下编译成兼容各种浏览器或Node的通用版本。你可以通过安装预设(presets,一系列同类插件组合)插件(plugins) 告诉Babel应该如何进行代码转译,例如:@babel/preset-env (转译 ES6 ~ ES9 的语法)、 @babel/preset-react (转译React )。

@babel/preset-env

介绍

preset-env是ES语法插件的合集,官方已经不再推荐使用preset-201x之类的包,该包可以通过配置自动兼容代码,包括自动引入polyfill垫片处理新的API(例如:Promise,Generator,Symbol等)以及 实例方法(例如Array.prototype.includes等)。

使用

  1. 在根目录下创建.babelrc配置文件

     {
         "presets": [
             [ "@babel/preset-env", {
                 "targets": {},
                 "useBuiltIns": false,
                 "corejs": false
             }]
         ]
     }
    
    • targets: 针对项目指定生成适应环境的代码。如果不进行配置,babel会转义所有ES6+进行环境适配,十分不推荐该用法。
    • useBuiltIns:该选项用来配合@babel/polyfill进行使用,针对Babel > 7.4.0, 官方不再推荐使用该库,请选择core-js,根据安装core-js版本在corejs选项填写数字2或3。
  2. 安装@babel/preset-env 以及 core-js

    源码

    const test = () => {
       `es8`.padStart(2)
    }
    

    转译结果

    // useBuiltIns: false
    
    "use strict";
    
    var test = function test() {
       'es8'.padStart(2);
    };
    
    // useBuiltIns: "usage" 
    // corejs: 3
    
    "use strict";
    
    require("core-js/modules/es.string.pad-start");
    
    var test = function test() {
       'es8'.padStart(2);
    };
    

    可以看出当使用 useBuiltIns: false 时, Babel只对箭头函数进行了转换,使用useBuiltIns: usage 时,Babel动态引入了 core-js/modules/es.string.pad-start 为全局对象。

注: 我们知道,ES+中不仅包含新增的语法(如箭头函数、类),还有一些实例的扩展(Array.prototype.includes等),以及很多内置函数(如Promise、Symbol)。然而preset-env在不引入polyfill时,对于处理这些应用场景是无能为力的。而为了解决这样的问题,我们通常有两种方法:使用 PolyfillBabel-runtime 进行功能填充。接下来我们会举例说明两者的优缺点以及应用场景。

@babel/polyfill

前文提到了一点 useBuiltIns 引入polyfill处理的简单例子,接下来我们再结合一些例子具体的了解polyfill的使用。 在 Babel > 7.4.0 之前,通常我们会安装 babel-polyfill@babel/polyfill来处理实例方法和ES+新增的内置函数,而7.4.0之后,当我们选择安装 @babel/polyfill时,会收到如下的警告 :

warning @babel/polyfill@7.4.4: � As of Babel 7.4.0, this
package has been deprecated in favor of directly
including core-js/stable (to polyfill ECMAScript
features) and regenerator-runtime/runtime
(needed to use transpiled generator functions):

  > import "core-js/stable";
  > import "regenerator-runtime/runtime";

是不是有点懵逼ㄟ(▔,▔)ㄏ 。什么意思呢?其实polyfill本身就是stable版本的core-js和regenerator-runtime的合集,即 import @babel/polyfill等同于:

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

所以在针对Babel >= 7.4.0 的情况,我们需要安装 core-js 替代 babel-polyfill,而 regenerator-runtime 会在我们安装 @babel/runtime 时自动安装,所以不必单独安装了。

配合引入垫片polyfill的方式根据useBuiltIns的不同可以分为三种,即 entry, usagefalse

源码

/******* useBuiltIns: entry 时添加一下两行代码 ********/ 
import 'core-js/stable';
import 'regenerator-runtime/runtime';
/****************************************************/

const a = new Promise();
let b = new Map()

转译结果

// 1. useBuiltIns: entry
"use strict";

require("core-js/modules/es.symbol");

require("core-js/modules/es.symbol.description");

require("core-js/modules/es.symbol.async-iterator");

// ..... 此处省略400个包

require("regenerator-runtime/runtime");

var a = new Promise();
var b = new Map();


// 2. useBuiltIns: usage
"use strict";

require("core-js/modules/es.array.iterator");

require("core-js/modules/es.map");

require("core-js/modules/es.object.to-string");

require("core-js/modules/es.promise");

require("core-js/modules/es.string.iterator");

require("core-js/modules/web.dom-collections.iterator");

var a = new Promise();
var b = new Map();

// 3. useBuiltIns: false
"use strict";

var a = new Promise();
var b = new Map();

对比三种使用方法我们可以发现,false 只做了语法转换, entry 引入了所有的es扩展包,不管用不用得着一股脑都打包进来,只有 usage 会自动检测代码中用到的功能自动引入模块(注:babel默认不会检测第三方依赖包代码,所以使用 usage 时,可能会出现引入第三方的代码包未注入模块而引发bug)。

所以,这里如果不考虑代码包大小,你可以选择 entry 方式。而如果你需要代码尽可能的精简,则使用 usage,这也是官方推荐的用法。

思考: 到这里是不是觉得项目使用polyfill,写代码已经可以6的飞起了,哈哈~。那我们假设一种应用场景,假设我们需要发布一个类库给到别人使用,我们使用polyfill的方式引入了内置函数Promise,不巧的是别人的本地代码里也封装了一个函数叫Promise,完蛋,真李逵碰上了假李逵,你说你死不死 ̄□ ̄。 所以为了和平,这里就需要我们的 @babel/runtime 粉墨登场了。

@babel/runtime

babel-runtime一般应用于两种场景:

  • 开发类库/工具(生成不污染全局空间和内置对象原型的代码)
  • 借助 @babel/runtime 中帮助函数(helper function)移除冗余工具函数

注: 我们应该经常在其他文档和使用说明中看到 runtime 不支持实例方法,确实在之前的使用中,无论是 Babel 7.0.0 ~ 7.4.0 抑或 Babel < 7.0.0, 对此都无能为力。他们只能通过配置 corejs (可选 false | 2)来决定是否使用 babel/runtime-corejs 替代core-js 抑或 polyfill (可选 true | false)来决定是否全局引入新的内置函数(new built-ins),然而其实这两者并没有根本改变, corejs: 2 实际上等同于 7.0.0 版本之前的 polyfill: true

重点: 真正的改变出现在 Babel 7.4.0 之后,你可以选择引入 @babel/runtime-corejs3,设置 corejs: 3 来帮助您实现对实例方法的支持。

我们接下来基于Babel 7.4.0来看几个例子:

配置项

{
    "presets": [
        [
            "@babel/preset-env"
        ],
    ],
    "plugins": [
        ["@babel/plugin-transform-runtime", {
            "corejs": false // 可选 false | 2 | 3
        }]
    ]
}

源码 -- 移除冗余工具函数

class Person {}

转译结果

// 移除plugins中runtime配置
"use strict";

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

var Person = function Person() {
  _classCallCheck(this, Person);
};


// 引入插件runtime
"use strict";

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

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

var Person = function Person() {
  (0, _classCallCheck2["default"])(this, Person);
};

可以看出,引入runtime,可以避免在多文件时编译生成多次相同的工具函数。

源码 -全局污染和实例方法示例

const a = new Promise();
[1, 2, 3].includes(1)

转译结果

// corejs: false
"use strict";

var a = new Promise();
[1, 2, 3].includes(1);

// corejs: 2
"use strict";

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

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

var a = new _promise["default"]();
[1, 2, 3].includes(1);

// corejs: 3
"use strict";

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

var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));

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

var _context;

var a = new _promise["default"]();
(0, _includes["default"])(_context = [1, 2, 3]).call(_context, 1);

结合一下代码我们可以看出,corejs: false 其实等同于使用 @babel/polyfill 时的 useBuiltIns: false,只对ES语法进行了转换。corejs:2 等同于 Babel 6时的 polyfill: true ,它们都会为代码创建一个沙盒环境,为 core-js 提供假名,这样就做到了不污染全局空间。corejs: 3 是在 corejs: 2的基础上进而解决了之前无法实例方法的窘况,同时也保持了不污染全局空间,简直完美~

总结

  1. Babel < 7.4.0
    • 开发类库, 选择 @babel/runtime
    • 内部项目,@babel/polyfill
  2. Babel >= 7.4.0,啥也不说,直接上 @babel/runtime吧,因为你要的全都有啊