@babel/plugin-transform-runtime

本节主要讲@babel/plugin-transform-runtime以及@babel/runtime。

在我们用Babel做语法转换的时候(注意,这里是单纯的做语法转换,暂时不使用polyfill补齐API),需要Babel在转换后的代码里注入一些函数才能正常工作,先看一个例子。

github配套代码是babel13的例子。

Babel配置文件如下,用@babel/preset-env做语法转换:

  {
    "presets": [
      "@babel/env"
    ],
    "plugins": [

    ]
  }

转换前的代码使用了ES6的class类语法:

  class Person {
    sayname() {
      return 'name'
    }
  }

  var john = new Person()
  console.log(john)

Babel转码后生成的代码如下:

  "use strict";

  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; }

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

    _createClass(Person, [{
      key: "sayname",
      value: function sayname() {
        return 'name';
      }
    }]);

    return Person;
  }();

  var john = new Person();
  console.log(john);

可以看到转换后的代码上面增加了好几个函数声明,这就是注入的函数,我们称之为辅助函数。@babel/preset-env在做语法转换的时候,注入了这些函数声明,以便语法转换后使用。

但样这做存在一个问题。在我们正常的前端工程开发的时候,少则几十个js文件,多则上千个。如果每个文件里都使用了class类语法,那会导致每个转换后的文件上部都会注入这些相同的函数声明。这会导致我们用构建工具打包出来的包非常大。

那么怎么办?一个思路就是,我们把这些函数声明都放在一个npm包里,需要使用的时候直接从这个包里引入到我们的文件里。这样即使上千个文件,也会从相同的包里引用这些函数。通过webpack这一类的构建工具打包的时候,我们只会把使用到的npm包里的函数引入一次,这样就做到了复用,减少了体积。

@babel/runtime就是上面说的这个npm包,@babel/runtime把所有语法转换会用到的辅助函数都集成在了一起。

我们先安装这个包:

  npm install --save @babel/runtime
  npm install --save-dev @babel/cli @babel/core  @babel/preset-env

然后到node_modules目录下看一下这个包结构

babel教程,@babel/plugin-transform-runtime的使用

_classCallCheck, _defineProperties与 _createClass这个三个辅助函数就在图片所示的位置,我们直接引入即可。

github配套代码是babel13a的例子。

我们手动把辅助函数替换掉函数声明,之前文件的代码就变成如下所示:

  "use strict";

  var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
  var _defineProperties = require("@babel/runtime/helpers/defineProperties");
  var _createClass = require("@babel/runtime/helpers/createClass");

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

    _createClass(Person, [{
      key: "sayname",
      value: function sayname() {
        return 'name';
      }
    }]);

    return Person;
  }();

  var john = new Person();
  console.log(john);

这样就解决了代码复用和最终文件体积大的问题。不过,这么多辅助函数要一个个记住并手动引入,平常人是做不到的,我也做不到。这个时候,Babel插件@babel/plugin-transform-runtime就来帮我们解决这个问题。

@babel/plugin-transform-runtime有三大作用,其中之一就是自动移除语法转换后内联的辅助函数(inline Babel helpers),使用@babel/runtime/helpers里的辅助函数来替代。这样就减少了我们手动引入的麻烦。

github配套代码是babel13b的例子。

现在我们除了安装@babel/runtime包提供辅助函数模块,还要安装Babel插件@babel/plugin-transform-runtime来自动替换辅助函数:

  npm install --save @babel/runtime
  npm install --save-dev @babel/cli @babel/core  @babel/preset-env @babel/plugin-transform-runtime

现在,我们的Babel配置文件如下:

  {
    "presets": [
      "@babel/env"
    ],
    "plugins": [
      "@babel/plugin-transform-runtime"
    ]
  }

转换前a.js代码:

  class Person {
    sayname() {
      return 'name'
    }
  }

  var john = new Person()
  console.log(john)

执行"npx babel a.js -o b.js"命令后,转换生成的b.js里代码如下:

  "use strict";

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

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

  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

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

    (0, _createClass2["default"])(Person, [{
      key: "sayname",
      value: function sayname() {
        return 'name';
      }
    }]);
    return Person;
  }();

  var john = new Person();
  console.log(john);

可以看到,它生成的代码比我们完全手动引入@babel/runtime里的辅助函数更加优雅。实际前端开发的时候,我们除了安装@babel/runtime这个包外,一定会安装@babel/plugin-transform-runtime这个Babel插件包的。

下一节接着讲@babel/plugin-transform-runtime的使用。

注:

1.每个转换后的文件上部都会注入这些相同的函数声明,那为何不用webpack一类的打包工具去掉重复的函数声明,而是要单独再引一个辅助函数包?

webpack在构建的时候,是基于模块来做去重工作的。每一个函数声明都是引用类型,在堆内存不同的空间存放,缺少唯一的地址来找到他们。所以webpack本身是做不到把每个文件的相同函数声明去重的。因此我们需要单独的辅助函数包,这样webpack打包的时候会基于模块来做去重工作。

笔记与思考

  1. 文中提到: @babel/runtime里存放了Babel做语法转换的辅助函数, 而@babel/preset-env预设的作用就是语法转换,那么是不是我可以理解为如果使用@babel/runtime后,@babel/preset-env预设都不需要引入

    1. 需要的,我的理解是:
      @babel/preset-env 做语法转换。 @babel/runtime提供函数包, @babel/plugin-transform-runtime 将转换后的内联的辅助函数替换成函数包的链接,减少冗余。这三个包都与语法转换相关,和polyfill的使用是两码事。(引入polyfill文件后最直接的变化就是引入了若干特性包,使得新api在某些低版本环境下可用)

      下面是我觉得比较通俗的解释:
      语法转换:把es6的语法糖使用以前的写法解释一次
      api补齐:也就是引入polyfill,使得低版本环境本不具有的方法被定义

    2. 上面一条回复写的有问题,下面是修正后的回复。

      需要的
      一、
      @babel/preset-env 做语法转换。 @babel/runtime提供函数包, @babel/plugin-transform-runtime 将转换后的内联的辅助函数替换成函数包的链接,减少冗余。这三个包从这个角度上(其他的角度请见第三点)都与语法转换相关,和polyfill的使用是两码事。(引入polyfill文件后最直接的变化就是引入了若干特性包,使得新api在某些低版本环境下可用)

      二、
      下面是我觉得比较通俗的解释:
      语法转换:把es6的语法糖使用以前的写法解释一次
      api补齐:也就是引入polyfill,使得低版本环境本不具有的方法被定义

      三、
      上面只提到了 @babel/plugin-transform-runtime 的第一个作用,这个包还有两个作用:

      2.当代码里使用了core-js的API,自动引入@babel/runtime-corejs3/core-js-stable/,以此来替代全局引入的core-js/stable;

      3.当代码里使用了Generator/async函数,自动引入@babel/runtime/regenerator,以此来替代全局引入的regenerator-runtime/runtime;

      这样使用的话,不引入polyfill,而是使用 @babel/plugin-transform-runtime 的api转换功能,可以防止api全局污染。

    3. 需要,第一段就指出了,preset-env会在每个使用到promise等语法的时候,注入辅助函数,而不是从@babel/runtime/helper拿。
      而我们记不住require的辅助函数,又引入了@babel/plugin-transform-runtime

  2. 看完全文发现平时理解错了。重新梳理了下,现在应该没理解错了吧...

    传统的转译是: 语法转换(借助预设) + API转换(借助polyfill)

    1)对于语法转换:@babel/runtime提供语法转换用的辅助函数,它并不提供转换,只是作为辅助存在。

    2)对于API转换: @babel/runtime提供API转换用的辅助函数,作为辅助存在。如果想通过它做转换,将其换为@babel/runtime-corejs3就可完成API转换,此时也无需再借助pollyfill

  3. 不知道我理解错没有:
    @babel/preset-env 会将ES6的语法转换成ES5的语法
    @babel/plugin-transform-runtime 的作用是自动移除语法“转换后”内联的辅助函数
    这个转换后说明@babel/plugin-transform-runtime会在@babel/preset-env之后执行
    但是前面又说,插件(plugins)会在预设(preset)之前执行
    这不是矛盾的吗
    虚心学习,诚心恳求各位大佬指点!

  4. 我用的是webpack5.75,我发现@babel/runtime这个包不用手动安装了,打包时会自动按需加载需要的模块到运行时依赖中。

  5. 个人理解
    @babel/preset-env,做语法转换,转换成如下代码。带来的问题是,大量js代码中重复的函数声明导致最终文件体积大的问题。
    ---------
    函数声明
    转换后的代码
    -----------

    @babel/runtime,可以将函数声明(辅助函数)都放到一个包里,这样转换后的js中直接引用就可以了。带来的问题是,需要手动引入,工作量太大。
    -----------
    var xxx = require(xxx)
    转换后的代码
    -----------

    @babel/plugin-transform-runtime,自动引入,解决手动引入带的麻烦

发表评论

邮箱地址不会被公开。 必填项已用*标注