Skip to content

babel 测试记录

babel 是什么?

babel 是由插件构成的。

执行命令:

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

创建 babel.config.json(需要 bable 版本 7.8.0 以上)

json
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "chrome 44",
        "useBuiltIns": "usage",
        "corejs": "3.27.1"
      }
    ]
  ]
}

然后执行命令,把所有在 src 下目录的代码编译到 lib 目录下。

shell
npx babel src --out-dir lib

配置 preset-env 的 useBuiltIns 为 usage

测试 Arry.fill

以 Arry.fill 函数为例,chrome 最低支持版本 45

babel.config.json

json
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "chrome 44",
        "useBuiltIns": "usage"
      }
    ]
  ]
}

main.js

js
const array1 = [1, 2, 3, 4];
console.log(array1.fill(6));

打包 npx babel src --out-dir lib。

控制台会输出这么句话。 image.png 意思就是叫我们,当 useBuiltIns 设置为 usage 或则 entry 时最好指定 corejs 选项版本。(在 package.json 里 core-js 的版本。)否则 corejs 默认指定 2.x 版本。

打包后的文件。

js
"use strict";

require("core-js/modules/es.array.fill.js");
var array1 = [1, 2, 3, 4];
console.log(array1.fill(6));

经过以下测试,得出结论:

当配置 preset-env 的选项 targets(例如设置为 chrome 44),useBuiltIns 为 usage,babel 会分析你代码使用到的新特性,动态引入目标浏览器缺失的 polyfill。这种方式会修改原型上的方法。

测试 Promise

测试 Promise

ie 所有版本都不支持 Promise。

配置文件。

js
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "ie 11",
        "useBuiltIns": "usage"
      }
    ]
  ]
}

main.js

js
console.log(Promise);

打包。可以看到引入了相关模块。

js
"use strict";

require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
console.log(Promise);

测试实例方法 "foobar".includes("foo")

测试实例方法 "foobar".includes("foo")

chrome 支持字符串实例方法 inclueds 的最低版本为 41。

js
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "chrome 40",
        "useBuiltIns": "usage"
      }
    ]
  ]
}

main.js

js
console.log("foobar".includes("foo"));

打包

js
"use strict";

require("core-js/modules/es.string.includes.js");
console.log("foobar".includes("foo"));

测试数组的 flat 方法

测试数组的 flat 方法。

chrome 支持数组的 flat 方法最低版本为 69

js
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "chrome 68",
        "useBuiltIns": "usage"
      }
    ]
  ]
}

main.js

js
const arr = [1, 2, 3, [1, 2, 3]]
console.log(arr.flat());

打包后。

js
"use strict";

var arr = [1, 2, 3, [1, 2, 3]];
console.log(arr.flat());

然而,打包后并没有引入 flat 的相关模块。

接下来修改配置

diff
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "chrome 40",
        "useBuiltIns": "usage",
+       "corejs": "3"
      }
    ]
  ]
}

再次打包,发现就会引入 flat 的相关模块。

js
"use strict";

require("core-js/modules/es.array.flat.js");
require("core-js/modules/es.array.unscopables.flat.js");
var arr = [1, 2, 3, [1, 2, 3]];
console.log(arr.flat());

得出结论,有些较新的 api 只有 core-js@3 才有,core-js@2 没有。所以推荐使用 core-js@3。

配置 preset-env 的 useBuiltIns 为 entry

先说结论,设置 preset-env 的 useBuiltins 为 entry 时,需要我们自己手动引入全部的 polyfill。

然后 preset-env 会根据 target 来引入目标浏览器不支持的全部特性。不会分析用户写的代码来按需引入特性。

配置

diff
{
  "presets": [
    [
      "@babel/preset-env",
      {
+      "targets": "chrome 70",
+      "useBuiltIns": "entry",
        "corejs": "3",
        "modules": "auto"
      }
    ]
  ]
}

main.js

diff
// stable 代表稳定特性
+ import "core-js/stable"

test()

async function test() {
  console.log('你说多少钱来着?');
  const res = await say()
  console.log(res);
}

function say() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(100)
    }, 1000);
  })
}

注:官网有写这么一段话:

If you are compiling generators or async function to ES5, and you are using a version of @babel/core or @babel/plugin-transform-regenerator older than 7.18.0, you must also load the regenerator runtime package. It is automatically loaded when using @babel/preset-env's useBuiltIns: "usage" option or @babel/plugin-transform-runtime

翻译过来大概是:如果你正在将 generators 或者 async 函数编译为 es5 能识别的语言,并且你使用的 @babel/core 包或者 @babel/plugin-transform-regenerator 包的版本小于 7.18.0,那么你还必须加载 regenerator runtime 包(import "regenerator-runtime/runtime")。于是我想测试如果包版本小于 7.18.0 并且不引入 regenerator runtime 包会发生什么错误。可好像什么问题都不会有。但我看见网上还有很多教程,都告诉你要同时引入两个包:

js
  import "core-js/stable";
  // 最新版的 babel 不引入这个包也没问题。
  import "regenerator-runtime/runtime";

最后我想说的是,有小伙伴能测试出来当使用的 @babel/core 包或者 @babel/plugin-transform-regenerator 包的版本小于 7.18.0 时,并且不引入 regenerator runtime 包会发生什么错误吗?还请大佬告知。虽然为了保险引入也没关系。这个包体积很小。

打包后,可以看到引入了很多 polyfill 函数。

js
"use strict";

require("core-js/modules/es.array.reduce.js");
require("core-js/modules/es.array.reduce-right.js");
require("core-js/modules/es.array.unscopables.flat.js");
require("core-js/modules/es.array.unscopables.flat-map.js");
require("core-js/modules/es.math.hypot.js");
require("core-js/modules/es.object.from-entries.js");
require("core-js/modules/es.regexp.flags.js");
require("core-js/modules/es.typed-array.set.js");
require("core-js/modules/es.typed-array.sort.js");
require("core-js/modules/web.immediate.js");
require("core-js/modules/web.queue-microtask.js");
require("core-js/modules/web.url.to-json.js");
test();
async function test() {
  console.log('你说多少钱来着?');
  const res = await say();
  console.log(res);
}
function say() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(100);
    }, 1000);
  });
}

执行打包后的文件,1s 后成功打印 100

image.png

修改下配置

diff
{
  "presets": [
    [
      "@babel/preset-env",
      {
-       "targets": "chrome 70",
+       "targets": "chrome 100",
        "useBuiltIns": "entry",
        "corejs": "3",
        "modules": "auto"
      }
    ]
  ]
}

重新打包,可以看到引入的 polyfill 少了很多,只有两个了。

js
"use strict";

require("core-js/modules/es.regexp.flags.js");
require("core-js/modules/web.immediate.js");
test();
async function test() {
  console.log('你说多少钱来着?');
  const res = await say();
  console.log(res);
}
function say() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(100);
    }, 1000);
  });
}

配置 preset-env 的 modules 选项

diff
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "chrome 40",
        "useBuiltIns": "usage",
        "corejs": "3",
+       "modules": false
      }
    ]
  ]
}

打包

js
// main.js
const arr = [1, 2, 3, [1, 2, 3]]
console.log(arr.flat());

// 打包后
import "core-js/modules/es.array.flat.js";
import "core-js/modules/es.array.unscopables.flat.js";
var arr = [1, 2, 3, [1, 2, 3]];
console.log(arr.flat());

这个参数项的取值可以是"amd"、"umd" 、 "systemjs" 、 "commonjs" 、"cjs" 、"auto" 、false。在不设置的时候,取默认值"auto"。

该项用来设置是否把 ES6 的模块化语法改成其它模块化语法。

我们常见的模块化语法有两种:(1)ES6 的模块法语法用的是 import 与 export;(2)commonjs 模块化语法是 require 与 module.exports。

在该参数项值是'auto'或不设置的时候,会发现我们转码前的代码里 import 都被转码成 require 了。

如果我们将参数项改成 false,那么就不会对 ES6 模块化进行更改,还是使用 import 引入模块。

使用 ES6 模块化语法有什么好处呢。在使用 Webpack 一类的打包工具,可以进行静态分析,从而可以做 tree shaking 等优化措施。

@babel/runtime 的作用

把 babel 配置文件改成这样:(将 useBuiltIns 设置为 false,只开启语法转换)

js
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": false
      }
    ]
  ]
}

main.js 中写入这段代码

js
class Person {
  say() {
    console.log(123);
  }
}

打包,查看编译后的代码:

js
"use strict";

function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
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, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
var Person = /*#__PURE__*/function () {
  function Person() {
    _classCallCheck(this, Person);
  }
  _createClass(Person, [{
    key: "say",
    value: function say() {
      console.log(123);
    }
  }]);
  return Person;
}();

可以看到在这个文件中定义了许多辅助函数。@babel/preset-env 在做语法转换的时候,注入了这些函数声明,以便语法转换后使用。

如果只有一个文件还好,但是在工程中,如果每个文件都使用了 class,那么就会在这个文件顶部定义这些相同的辅助函数,造成了重复和冗余。

如何进行复用?可以使用 @babel/runtime 包,里面专门定义了做语法转换时需要用到的辅助函数。

上面用到的辅助函数有 _typeof、_classCallCheck、_defineProperties、_createClass、_toPropertyKey、_toPrimitive,这些函数你能在 node_modules 里的 @babel/runtime 包里进行查看,会发现函数体是一模一样的。

以 _classCallCheck 举例,它的位置在

image.png 内容如下:

js
function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}
module.exports = _classCallCheck, module.exports.__esModule = true, module.exports["default"] = module.exports;

可以发现与在文件里定义的 _classCallCheck 函数一模一样。

@babel/plugin-transform-runtime 的作用一

上面说到了 @babel/runtime 的作用,可以从里面引入辅助函数,替换做语法转换时生成的内联辅助函数

那么然后呢?我们手动一个一个的把在文件里定义的辅助函数改成从 @babel/runtime 里面引入吗?当然不可能。这里需要使用到一个插件 @babel/plugin-transform-runtime 。

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

使用它:

1.下载

shell
npm install --save-dev @babel/plugin-transform-runtime

2.配置 babel

diff
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": false
      }
    ]
  ],
+ "plugins": ["@babel/plugin-transform-runtime"]
}

重新编译,查看编译后的文件:

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: "say",
    value: function say() {
      console.log(123);
    }
  }]);
  return Person;
}();

可以看到已经帮我们自动引入辅助函数了。

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

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

@babel/plugin-transform-runtime 作用二、三

@babel/plugin-transform-runtime 有三大作用。

第一个是自动移除语法转换后内联的辅助函数。那另外两个呢?

第二个,当代码里有 API 需要 polyfill 兼容,自动引入 @babel/runtime-corejs3 或者 @babel/runtime-corejs3,来进行兼容。(需要开启配置)

第三个,当代码里使用了 generator/async 函数,自动引入 @babel/runtime-corejs3 或者@babel/runtime-corejs3 下的 regenerator(此配置默认开启)

并且二、三引入 polyfill 的时候,不会修改原型链或者在 window 上挂载对象。可以做到不污染运行环境。

怎么才能使用二、三两个功能呢?

对 @babel/plugin-transform-runtime 进行配置。下面是默认配置

js
{
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "absoluteRuntime": false,
        "corejs": false,
        "helpers": true,
        "regenerator": true,
        "version": "7.0.0-beta.0"
      }
    ]
  ]
}

corejs 可以设置为 false, 2, 3。

设置为 false,只会将语法转化时用到的内联辅助函数变为从 @babel/runtime 里引入。

设置为 2,将从 @babel/runtime-corejs2 包里引入 polyfill(辅助函数也将会从这个包里引入)。

设置为 3,将从 @babel/runtime-corejs3 包里引入 polyfill(辅助函数也将会从这个包里引入)。

@babel/runtime、@babel/runtime-corejs2、@babel/runtime-corejs3 只需要下载其中一个即可。取决于你设置的 corejs 值。

@babel/runtime-corejs2 包只支持全局变量(比如 Promise)和静态属性(比如 Array.from),不支持实例方法(例如 [].inclues)。@babel/runtime-corejs3 全都支持。

将 corejs 设置为 2

先来试试将 corejs 设置为 2。

配置 babel

diff
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": false
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "absoluteRuntime": false,
-       "corejs": false
+       "corejs": 2,
        "helpers": true,
        "regenerator": true,
        "version": "7.0.0-beta.0"
      }
    ]
  ]
}

下载 @babel/runtime-corejs2。(其实不下也可以,因为我们只是测试而已,想看看编译后有没有引入这个包,并不会运行编译后的文件)

js
npm install --save @babel/runtime-corejs2

main.js 打包前

js
class Person {
  say() {
    console.log(123);
  }
}

main.js 打包后。可以看到辅助函数是从 @babel/runtime-corejs2 包里引入的。

js
"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));
var Person = /*#__PURE__*/function () {
  function Person() {
    (0, _classCallCheck2["default"])(this, Person);
  }
  (0, _createClass2["default"])(Person, [{
    key: "say",
    value: function say() {
      console.log(123);
    }
  }]);
  return Person;
}();

改变一下 main.js

js
class Person {
  say() {
    const p = Promise.resolve('ok')
    console.log(p);
  }
}

再执行打包,可以看到,Promise 是使用从包里面引入的,而不是挂载到全局对象 window 上。

js
"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));
var Person = /*#__PURE__*/function () {
  function Person() {
    (0, _classCallCheck2["default"])(this, Person);
  }
  (0, _createClass2["default"])(Person, [{
    key: "say",
    value: function say() {
      var p = _promise["default"].resolve('ok');
      console.log(p);
    }
  }]);
  return Person;
}();

再改变 main.js

js
class Person {
  say() {
    const arr = [1,2,3,4]
    console.log(arr.includes(1));
  }
}

编译后发现数组的 inclues 实例方法并没有比 polyfill 修复。

image.png

将 corejs 设置为 3

这时我们重新来过,将 corejs 设置为 3。

配置 babel

diff
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": false
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "absoluteRuntime": false,
-       "corejs": 2
+       "corejs": 3
        "helpers": true,
        "regenerator": true,
        "version": "7.0.0-beta.0"
      }
    ]
  ]
}

下载 @babel/runtime-corejs3,并卸载 @babel/runtime-corejs2

shell
npm install --save @babel/runtime-corejs3

main.js

js
class Person {
  say() {
    const arr = [1,2,3,4]
    console.log(arr.includes(1));
  }
}

编译打包后,发现数组的实例方法 includes 被 polyfill 修复了。

这也验证了@babel/runtime-corejs2 包只支持全局变量(比如 Promise)和静态属性(比如 Array.from),不支持实例方法(例如 [].inclues)。@babel/runtime-corejs3 全都支持的说法。

image.png

测试 async

当我们使用 async 函数时,@babel/plugin-transform-runtime 会自动引入 @babel/runtime-corejs2 或者 @babel/runtime-corejs3 下的 regenerator 包吗?

结论是的。。

配置 babel.config.json

diff
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": false
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "absoluteRuntime": false,
        "corejs": 3,
        "helpers": true,
-       "regenerator": true,
+      "regenerator": false,
        "version": "7.0.0-beta.0"
      }
    ]
  ]
}

main.js

js
test()

async function test() {
  await console.log(123);
}

编译后

js
"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/asyncToGenerator"));
test();
function test() {
  return _test.apply(this, arguments);
}
function _test() {
  _test = (0, _asyncToGenerator2["default"])( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) switch (_context.prev = _context.next) {
        case 0:
          _context.next = 2;
          return console.log(123);
        case 2:
        case "end":
          return _context.stop();
      }
    }, _callee);
  }));
  return _test.apply(this, arguments);
}

这之后我们再设置 "regenerator": true,执行编译后的文件

diff
"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
+ var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/asyncToGenerator"));
test();
function test() {
  return _test.apply(this, arguments);
}
function _test() {
  _test = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee() {
    return _regenerator["default"].wrap(function _callee$(_context) {
      while (1) switch (_context.prev = _context.next) {
        case 0:
          _context.next = 2;
          return console.log(123);
        case 2:
        case "end":
          return _context.stop();
      }
    }, _callee);
  }));
  return _test.apply(this, arguments);
}

参考链接

https://babeljs.io/ babel 官网

https://github.com/zloirock/core-js polyfill 核心包

https://www.jiangruitao.com/babel/ babel 教程

https://caniuse.com/ can i use

掘金的一些文章

Released under the MIT License.