首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一文聊完前端项目中的Babel配置

一文聊完前端项目中的Babel配置

作者头像
19组清风
发布于 2022-10-08 02:40:12
发布于 2022-10-08 02:40:12
2.1K09
举报
文章被收录于专栏:Web Front EndWeb Front End
运行总次数:9

写在前边

大家好,今天来和大家聊聊 Babel Runtime 相关知识。

As a Front-end engineer,浏览器兼容性对于每个人来讲都是必不可少的话题。

在现代大多数项目中,我们都无需手动在代码中处理不同浏览器的兼容性写法,这正是 babel 的作用。

接下来,这篇文章我会着重和大家聊聊有关 @babel/plugin-transform-runtime 的详细用法,希望可以帮助到大家。

不出意外的话,这应该是我的Babel 专栏中关于配置项讲解的最后一篇文章。

我要说的话

关于 Babel 的用法、插件编写以及不同项目下的适用场景我在前几篇文章中或多或少都有一些介绍:

上述三篇文章中从浅到深依次讲述了 Babel 配置指南、Babel 插件开发者手册以及不同项目场景下的 Babel 最佳实践心得。

如果你对 Babel 还不是很了解,强烈建议优先阅读上述三篇文章。本文更多的是针对于 @babel/plugin-transform-runtime 各项配置的解释补充。

@babel/runtime

什么是 @babel/runtime

在开始 @babel/plugin-transform-runtime 的讲述前,我们先来看看 @babel/runtime 是什么东西。

@babel/runtime is a library that contains Babel modular runtime helpers.

可以看到官方文档对于 @babel/runtime 的介绍非常简单: 一个包含 Babel 模块化运行时助手的库。

那么,怎么理解这个所谓的运行时呢?此时就要拉出来另一个概念:@babel/preset-env

简单来说 @babel/preset-env 是一系列 babel-plugin 的预设集合,默认情况下它允许我们在代码中使用一系列高版本 ECMAScript 语法,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Source Code
const a = () => {
  // dosomething
};
// compile core
"use strict";

var a = function a() {
    // dosomething
};

当然,preset-env 也提供了 polyfill 等可选配置,稍后我们会详细讨论它。

那么它和 @babel/runtime 有什么关系呢,我们来看看另一段代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// source code
class a {}
// compile by preset-env
"use strict";

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); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }

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

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

上述代码我们可以看到,当我们在代码中使用 class 语法时。使用 babel-preset-env 进行处理时,会为文件内部注入一系列判断代码来实现 class 的低版本兼容性效果。

所谓 @babel/runtime 正是为了解决这个问题出现的,

@babel/runtime 针对于代码中这些重复注入的辅助语句可以达到运行时引入的效果,从而缩小代码体积。比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// source code
class a {}
// compile code by @babel/runtime
"use strict";

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

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

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

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

我们可以清晰的看到原本通过 preset-env 对于 class 的注入的辅助代码变成了运行时的模块引入的代码了。

这也就是 @babel/runtime 的作用: 将转译的辅助代码从文件中硬编码方式变为运行时的模块注入,从而(在某些条件下,比如重复代码过多时)缩小编译后的代码体积。

误区

@babel/runtime 并不包含任何 polyfill 的注入

首先 @babel/runtime 仅仅是一个包含运行时 helpers 的库,它会将 Babel 编译输出中的跨文件重复辅助代码变为运行时引入,仅此而已。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// source code
const promise = () => new Promise();

const map = new Map();
// compile code with babel/runtime
"use strict";

var promise = function promise() {
  return new Promise();
};

var map = new Map();

可以看到代码中的 promisemap 没有任何变化,@babel/runtime 库并不会帮我们注入任何 polyfill 代码。

@babel/runtime@babel/preset-env 的关系

@babel/runtime 是一个运行时的模块化库,当我们使用 @babel/preset-env 转译代码时。

如果我们使用了 @babel/runtime 的话,针对于重复的硬编码 helper 方法会变为模块化的方式在运行时引入。

反之,则亦然。

换句话说,如果你仅仅使用 @babel/runtime 的话并不使用 preset-env,那么其实是没有任何效果的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// source code
class a {
  // do something
}
// compile code with @babel/runtime without @babel/preset-env
class a {
    // do something
}

你可以将 @babel/runtime 理解成为 @babel/preset-env 的扩展工具库,虽然这只是针对 ECMA 语法部分的转译来说。

如何开启 @babel/runtime

这个问题其实并不是什么误区,其实它非常简单。

我们会在稍后提到它。

@babel/transform-runtime

接下来我们来聊一聊 @babel/transform-runtime

基础

相信了解过 babel 的小伙伴,或多或少都听过一句话。

类库项目的构建如果需要注入 polyfill 的话,最好使用 @babel/transform-runtime**,因为它提供了一种不污染全局作用域的方式**。

而业务项目中最好使用 preset-env useBuintIns 配置来注入 polyfill**,这种方式会污染全局作用域。**

如果你对上述几句话不是特别清楚的话,推荐你去详细阅读下我的这篇 「前端基建」探索不同项目场景下Babel最佳实践方案,我就不在这里详细展开了。

所谓 @babel/plugin-transform-runtime 插件主要为我们提供了以下三个功能:

  • 首当其冲的一定是当我们需要一种不污染全局环境的 polyfill 时,我们可以通过 @babel/plugin-transform-runtime 来帮我们提供,这对于类库的打包起到了非常 Nice 的作用(通过 corejs 选项开启)。
  • 其次,它提供了自动删除内敛 Babel helper 并使用 @babel/runtime/helpers 来进行运行时注入(可使用 helpers 选项切换)。
  • 最后,当我们在代码中使用 generators/async 函数时,它会自动根据 @babel/runtime/regenerator进行运行时注入(可通过 regenerator 选项切换)。

接下来,就让我们深入 @babel/plugin-transform-runtime 插件来一探究竟。

配置项解读

corejs

讲解

所谓的 corejs 正是 @babel/plugin-transform-runtime 来实现 @polyfill 的核心库。

简单点来说,只有指定了 corejs 版本的话,@babel/plugin-transform-runtime 才会根据指定的 corejs 版本对于我们的源代码动态添加 polyfill

默认值为 false 这表示默认不需要注入任何 polyfill

同时,**@babel/plugin-transform-runtime** 中提供的 core-js 库是一种不污染全局变量方式的 polyfill 方式注入。

我们来看看 runtime-corejs3 中的文件内容:

可以看到这里边会包含非常多的 ECMAScript 新版内置模块(Promise、Map 等)、静态方法(Arrar.from、Array.of)以及一些实例方法(instance 文件夹中)。

当我们这样使用时:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// config file
module.exports = (api) => {
  api.cache.never();
  return {
    plugins: [
      [
        '@babel/plugin-transform-runtime',
        {
          corejs: 3, // 使用 corejs 3版本
        },
      ],
    ],
  };
};

// source code
const promise = new Promise();
// compile code
import _Promise from "@babel/runtime-corejs3/core-js-stable/promise";
const promise = new _Promise();

我们可以清楚的看到当我们使用 corejs:3 时针对于代码中的 promise 会增加一层 polyfill 垫片的作用。

同时它会从 @babel/runtime-corejs3/core-js-stable/promise 引入 Promise 同时返回给 _Promise 内部变量使用,并不会污染全局作用域。

关于 corejs 存在以下的版本:

corejs选项

安装命令

false

npm install --save @babel/runtime

2

npm install --save @babel/runtime-corejs2

3

npm install --save @babel/runtime-corejs3

  • 当为 false 时,表示仅仅包含 @babel/runtime@babel/runtime 的作用我们刚刚已经详细讲过了。(注意 false 配置并不代表开启 @babel/runtime
  • corejs 2 版本,目前已经不再维护了,所以这里不推荐大家使用。
  • corejs 3 版本,是目前最常用的版本,对比 2 版本。3 版本会额外增加一些实例方法的 polyfill 比如 "foobar".includes("foo") 中的 includes 方法 2 中是不存在的。

所以说,所谓的 corejs 配置就是针对于运行时的 polyfill 配置。如果开启了 corejs(不为false) 那么 @babel/plugin-transform-runtime 会在代码运行时为我们动态注入 polyfill 内容。

误区

需要特别留意的是 @babel/plugin-transform-runtimecorejs 选项和 preset-envcorejs 配置看起来虽然是相似的。

但是他们引入的包内容是完全不同的,**preset-env 中的 corejs 配置依赖的是 core-js(大版本 2 or 打版本 3) 这个包,这个包中的 polyfill 会污染全局作用域。**

@babel/plugin-transform-runtime 中的 corejs 选项依赖的是 runtime-corejs3/runtime-corejs2 这两个包,这两个包内提供的则是一种不污染全局作用域的 polyfill 方式。

当然,@babel/plugin-transform-runtime 的 corejs 配置默认为 false,而当使用 preset-env 设置 useBuintIns: usage or entry 时,corejs 默认为 2。

helpers

接下来的 helpers 配置就会比较简单了。

因为我们在上述说过正常情况下 preset-env 会将一些多余的语法转椅硬编码编译在源代码文件中,而我们可以利用 @babel/runtime 将重复的语法做成运行时的注入。

那么,如果开启 @babel/runtime 呢? 当然是使用 helpers 属性,它的默认值是 true

当我们开启 helpers: true 时,结合 preset-env 选项。它会将我们一些重复的转译语法变成运行时注入。比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// configfile
module.exports = (api) => {
  api.cache.never();
  return {
    presets: ['@babel/preset-env'],
    plugins: [
      [
        '@babel/plugin-transform-runtime',
        {
          corejs: false,
        },
      ],
    ],
  };
};

// source code 
class a {}
// compile code
"use strict";

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

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

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

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

可以看到针对于编译 class 时重复的语法全部变成了运行时注入。

需要留意的是如果你使用 corejs:false 的话 helpers 模块会从 @babel/runtime/helpers 中引入。

而如果你使用了 corejs 那么对应的 helpers 会从对于的 corejs 包中引入,比如假如你使用了 core-js:3 针对于 class 的引入:@babel/runtime-corejs3corejs:2 同理。

不过这些对于使用者来说是无关紧要的,也许你永远不用关心它从哪里引入。只要当你使用对应 corejs 版本时记得安装对应的包即可。

regenerator

讲解

regenerator 配置的值默认为 true,它代表当我们在代码中使用 async/await 或者 generator 函数时,切换是否从全局作用域中获取。

默认为 true,表示生成运行时的 async/awaitgenerator 模块注入并不从全局作用域获取。

怎么理解这个从全局作用域中获取呢?我们稍微来看看当我将它设置 false 时的编译结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// source Code
function* sayHello() {}
// compile Code 
"use strict";

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

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

function sayHello() {
  return _sayHello.apply(this, arguments);
}

function _sayHello() {
  _sayHello = (0, _asyncToGenerator2["default"])( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));
  return _sayHello.apply(this, arguments);
}

上述编译后的代码引用了两个 helpers ,不过这无关紧要。

细心的小伙伴可能会发现了,编译后的 generator 函数依赖了全局的 regeneratorRuntime 这个对象。

这也就意味着当 regenerator: false 时针对于 async/generator 函数的转译 babel 需要依赖于全局作用域的 regeneratorRuntime 这个对象。

反之,再来看看相同的代码当我们设置为 regenerator: true 时它的体现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"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"));

function sayHello() {
  return _sayHello.apply(this, arguments);
}

function _sayHello() {
  _sayHello = (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:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));
  return _sayHello.apply(this, arguments);
}

相同的源代码,我们可以清晰的看到当设置 regenerator: true 时明显生成函数的 polyfill 已经不依赖于全局作用域了。

而是会在运行时从模块导入,这也就意味着 regenerator: true 可以让我们不依赖于全局污染的生成器模块来使用 async/awiat 或者 generator 模块。

注意点

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.

这是官方文档在 @babel/polyfill 中的描述。在旧版本的 babel 中默认是不会将 generator 的帮助模块(也就是全局的 regenerator 对象注入)。

所以当我们在使用低版本的 @babel/core 或者 @babel/plugin-transform-regenerator 时,如果需要依赖全局作用域的 regenerator 对象时,需要额外在项目中引入 regenerator-runtime 这个库。

当然在 7.18.0 全局的 regenerator 已经变成了类似于 helpers 模块的注入了,当我们在项目中使用到 async/await/generator 时,preset-env 会自动帮我们注入对应的全局 regenerator 函数声明。

这也许就是有时你会碰到的 regeneratorRuntime.mark is not a function,当然,我更推荐你使用新版本,它会避免这个问题。详情你可以参考这个Issue

需要额外注意的是,当你使用 @babel/plugin-transform-regenerator 时,是否需要注入全局的 regenerator 是依据 helpers regenerator 的。

我说点人话,针对于新版本(7.18.0)以上如果我的代码是这么写的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function* sayhello() {}

配置文件是这样的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
module.exports = (api) => {
  api.cache.never();
  return {
    presets: ['@babel/preset-env']
  };
};

它会生成:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"use strict";

function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol &amp;&amp; "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj &amp;&amp; "function" == typeof Symbol &amp;&amp; obj.constructor === Symbol &amp;&amp; obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }

function _regeneratorRuntime() { 
    // 省略无数个代码
}

var _marked = /*#__PURE__*/_regeneratorRuntime().mark(sayhello);

function sayhello() {
  return _regeneratorRuntime().wrap(function sayhello$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}

可以看到,当我单独使用 preset-env 时代码中使用到了 generator 函数,它会自动注入对应的 _regeneratorRuntime 函数。

之后,同一段源代码当我这样配置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
module.exports = (api) => {
  api.cache.never();
  return {
    presets: ['@babel/preset-env'],
    plugins: [
      [
        '@babel/plugin-transform-runtime',
        {
          helpers: true, // 其实默认就是 true
          regenerator: true,
        },
      ],
    ],
  };
};

编译后的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"use strict";

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

var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));

var _marked = /*#__PURE__*/_regenerator["default"].mark(sayhello);

function sayhello() {
  return _regenerator["default"].wrap(function sayhello$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}

不出意外 _regenerator 变成了 runtime 的模块注入,并不依赖全局作用域。

当我再次修改配置文件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
module.exports = (api) => {
  api.cache.never();
  return {
    presets: ['@babel/preset-env'],
    plugins: [
      [
        '@babel/plugin-transform-runtime',
        {
          helpers: true, // 其实默认就是 true
          regenerator: false,
        },
      ],
    ],
  };
};

编译后的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"use strict";

var _marked = /*#__PURE__*/regeneratorRuntime.mark(sayhello);

function sayhello() {
  return regeneratorRuntime.wrap(function sayhello$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}

可以看到,当我关闭 regenerator 同时开启 helpers:true 时,并不会注入任何 regeneratorRuntime 相关的全局 helpers 注入。

这是因为我们设置了 regenerator: false 表示依赖全局的 regenerator ,同时我们使用了 helpers: true 表示所有 helpers 需要 runtime 模块注入。

那么,当然对于 regenerator 并不会生成对于的硬编码 _regeneratorRuntime 注入了。

再来,当我再次修改配置文件后:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
module.exports = (api) => {
  api.cache.never();
  return {
    presets: ['@babel/preset-env'],
    plugins: [
      [
        '@babel/plugin-transform-runtime',
        {
          helpers: false, // 其实默认就是 true
          regenerator: false,
        },
      ],
    ],
  };
};

同样的源代码,编译后为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"use strict";

function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol &amp;&amp; "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj &amp;&amp; "function" == typeof Symbol &amp;&amp; obj.constructor === Symbol &amp;&amp; obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }

function _regeneratorRuntime() {
    // ... 
}

var _marked = /*#__PURE__*/_regeneratorRuntime().mark(sayhello);

function sayhello() {
  return _regeneratorRuntime().wrap(function sayhello$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}

可以看到此时会硬编码 _regeneratorRuntime 全局函数,会污染全局作用域。

稍微总结一下上边的体现:

  • 不使用 @babel/plugin-transform-runtime,单独使用 preset-env 时。针对于 async/await/generator 会在代码中硬编码 _regeneratorRuntime 污染全局作用域。
  • preset-env 配合 @babel/plugin-transform-runtime
代码语言:txt
AI代码解释
复制
- `helpers: false &amp;&amp; regenerator: false` 生成 `_regeneratorRuntime` 污染全局作用域。
- `helpers: true &amp;&amp; regenerator: false`  不生成 `_regeneratorRuntime` 污染全局作用域,但是需要依赖全局的 `_regeneratorRuntime`,否则会报错。
- `helpers: true &amp;&amp; regenerator: true` (插件默认值),生成运行时的模块注入不依赖全局作用域,同时也不污染全局作用域。
- `helpers: false &amp;&amp; regenerator: true` :生成 `_regeneratorRuntime` 污染全局作用域,和第一种情况一致。

只要 helpers: false 其实无论你的 regenerator 如何设置都是无效的,都会交给 preset-env 来处理你的 async/await/generator 函数从而生成污染全局作用域的硬编码。

helpers: true 时,才会根据 regenerator 来决定后续行为。如果 regenerator:true 那么生成 runtime 不依赖全局同时不污染作用域的 regeneratorRuntime

反之,设置 regenerator: false 时,需要依赖全局的 regeneratorRuntime 这也就意味着需要额外依赖 regenerator runtime这个库。

useESModules

用法

之后我们再来看看所谓的 useESModules 配置。

当设置 useESModules: true 时,当使用 @babel/plugin-transfrom-runtime 转译代码时,会启动 @babel/plugin-transform-modules-commonjs 插件将注入的 helpers 模块转化为 CJS 模块导出语句。

简单点来说 useESModules: true 表示注入的 helpers 模块为 ESM 导出,而设置为 false 时表示使用 CJS 导出。

我们来看一个 Demo 就一目了然:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// source code
class Hello {}

// plugin config 
module.exports = (api) => {
  api.cache.never();
  return {
    presets: ['@babel/preset-env'],
    plugins: [
      [
        '@babel/plugin-transform-runtime',
        {
          helpers: true, 
          useESModules: false,
        },
      ],
    ],
  };
};

// compile code
"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
// 从 esm 中导入
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/createClass"));

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

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

可以看到上述的 helpers 引入变成了 @babel/runtime 中的 esm 这个模块。

同样的源代码,当我们设置为 useESModules: false 时:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// compile code
"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
// 从正常的 CJS 包中导入模块
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

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

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

比如,classCallCheck 在开启 useESModules 的表现下分别为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// useESModules: false
exports.__esModule = true;

exports.default = function (instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError('Cannot call a class as a function');
  }
};
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// useESModules: true
export default function (instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError('Cannot call a class as a function');
  }
}

通常在前端项目中,我们都会利用一些构建工具,比如 webpack/rollup 之类。

合理的利用 useESModules 配置会让构建出更小的 bundler ,原因也很简单因为它不需要保留任何 commonjs 语义的代码。

注意点

This option has been deprecated: starting from version 7.13.0@babel/runtime's package.json uses "exports" option to automatically choose between CJS and ESM helpers.

当然这个配置项在 7.13.0 之后被废弃掉了,我们可以看到了针对于 7.13.0 之后 @babel/runtime 这个包会根据 package.json 中的 exports 字段来决定以何种模块规范自动导出。

如果你不是特别了解 exports 关键字的话可以查看这篇 从 package.json 来聊聊如何管理一款优秀的 Npm 包

其次,useESModules 配置仅仅针对于引入的 helpers 函数有效,反而言之如果你设置了 helpers: false 那么自然 useESModules 是完全没有任何效果的。

absoluteRuntime

absoluteRuntime 接受一个 string 或者 boolean 的值,默认为 false。它可以让我们更加自由的使用 transform-runtime

默认情况下,transform-runtime 会从当前项目的 node_mouldes 文件夹中寻找 @babel/runtime 模块从而引入对应的 helpers 模块。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

这也就意味着,当前项目中的 node_modules 中必须存在对应的 @babel/runtime 这个包。

在某些情况下,比如 monorepo 项目、npm link 的包或者一些用户外部调用的 CLI 使用默认的 helpers 查找规则是会产生问题的(寻找不到对应的 @babel/runtime 模块)。

absoluteRuntime 就是为了解决上述问题而产生的配置,它允许我们在 Babel 开始编译前预先解析一次 runtime 所在位置,从而将指定的绝对路径拼接到输出的代码之前。

当我们设置 absoluteRuntime: true 时,我们在来看看编译后的引入模块:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 这里为使用了 pnpm ,所以扫描到的 runtime 目录是我磁盘目录上的绝对路径地址
var _createClass2 = _interopRequireDefault(require("/Users/wanghaoyu/Desktop/babel/node_modules/.pnpm/@babel+runtime@7.19.0/node_modules/@babel/runtime/helpers/createClass.js"));

需要额外留意的是,如果你的项目中编译后依赖了 @babel/runtime 对应的包(简单来说并没有将 runtime 编译进入而是作为 dependency),那么对于编译后的绝对路径是不可取的。

version

接下来聊聊 version 配置项,这是一个非常简单的配置项。

默认情况下,transform-runtime 会认为 @babel/runtime@7.0.0 已经安装了,当我们安装了更高版本的 @babel/runtime (或者对应的 corejs 版本)时,此时我们可以通过 version 字段来制定对应 runtime 的版本从而使用更多先进的 feature。

比如,当我们依赖 @babel/runtime-corejs2@7.7.4 编译我们的代码时:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "absoluteRuntime": false,
        "corejs": 2,
        "version": "^7.7.4"
      }
    ]
  ]
}

对比 preset-env polyfill 方案

上述我们详细聊了聊 @babel/plugin-transform-runtime 的各个常用配置项的含义,通过 corejs 配置我们明白 @babel/plugin-transform-runtime 可以为我们的代码添加 polyfill 。

那么,在 preset-env 中的 useBuiltIns 也可以为我们的项目添加 polyfill 支持,接下里我们就聊聊这两者有什么区别。

作用域范围

首先,老生常谈 @babel/plugin-transform-runtime 编译后添加的 polyfill 并不会污染全局作用域,而 preset-env 并不会污染全局作用域。

比如同一段代码 const promise = new Promise(),我们先来看看 @babel/plugin-transform-runtime

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// by @babel/plugin-transform-runtime 
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

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

var promise = new _promise["default"]();

可以看到 @babel/plugin-transform-runtime 编译后的 promise 是作为局部变量 _promise 引入的,这也就意味着它并不会污染全局作用域

我们再来看看通过 preset-envuseBuiltIns: usage 编译后的结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"use strict";

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

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

var promise = new Promise();

上述代码中的 Promise 的确添加了 polyfill 但是明显可以看到这是一种污染全局作用域的作用,会为全局添加 Promise

浏览器 Target

当我们使用 preset-env 时,它支持一个额外的配置名为 targets 配置,它表示源代码需要兼容的浏览器列表。

比如一个 const 语法,在新版 Chrome 中已经支持了 const 语法,此时如果我们将目标浏览器设置为 chrome: 101 那么 const 就不需要进行转译,这可以大大减小编译后的代码体积。

同样对于 polyfill 的添加也是如此,我们同样以一段 Promise 源代码来一探究竟:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const promise = new Promise()

当我们设置如下配置时:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// config file 开启 usage ,关闭 plugin-tansform-runtime 
module.exports = (api) => {
  api.cache.never();
  return {
    presets: [
      [
        '@babel/preset-env',
        {
          useBuiltIns: 'usage',
          // 指定目标浏览器为 chrome 101
          targets: {
            chrome: 101,
          },
        },
      ],
    ],
    plugins: [
      [
        '@babel/plugin-transform-runtime',
        {
          helpers: true, // 其实默认就是 true
          corejs: false,
        },
      ],
    ],
  };
};

// compiled code
const promise = new Promise();

因为 chrome 101 版本中已经内置了 Promise ,所以当我们支持目标浏览器为 Chrome 101 时自然并不需要做任何处理。

同样,当我们关闭 preset-env 使用 @babel/plugin-transform-runtime 来添加 polyfill 看看:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// config file
module.exports = (api) => {
  api.cache.never();
  return {
    presets: [
      [
        '@babel/preset-env',
        {
          useBuiltIns: false,
          targets: {
            chrome: 101,
          },
        },
      ],
    ],
    plugins: [
      [
        '@babel/plugin-transform-runtime',
        {
          helpers: true, // 其实默认就是 true
          corejs: 3,
        },
      ],
    ],
  };
};
// compiled code
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

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

const promise = new _promise.default();

结果已经一目了然了,**preset-env** polyfill 方案支持通过 targets 动态调整 polyfill 的支持从而缩小编译后的代码体积,而 @babel/plugin-transform-runtime 并不支持 targets 设置会全量引入 polyfill。

总结

关于 @babel/plugin-transform-runtimepreset-env 的 polyfill 方案在我个人看来并没有任何绝对的好坏,不同的业务场景下这两种方案都会有不一样的效果,具体取舍更多的还是仁者见仁,智者见智。

当然,目前大多数成熟的组件库细心的小伙伴其实已经发现并不会使用 babel 添加任何 polyfill 支持,而是将 polyfill 步骤留给用户自己抉择。

比如 ant-design 官网中明确标名的:

关于 preset-env@babel/plugin-transform-runtime 的不同业务场景下 polyfill 的抉择,有兴趣了解的小伙伴可以参考这篇 「前端基建」探索不同项目场景下Babel最佳实践方案

借题聊聊 SWC

SWC 是一个基于 Rust 编写的可拓展的平台,适用于下一代快速开发工具。

SWC 可以被用作编译和打包,所谓的编译就类似于 Babel 的功能(将高版本 JS/TS 代码编译为主流浏览器皆支持的低版本语法)。

虽然官方表示 SWC 提供了 swcpack 支持 Bundling(打包) 功能,但是笔者目前感觉仍然不是特别成熟。目前社区中对于 swcpack 的热度也一直不温不火,有兴趣的同学可以自行尝试。

当然,为什么要额外提一句 SWC 。这里并不打算详细展开它,更多的在提到代码转译时目前看来 SWC 相较于代码转译虽然没有 Babel 那么成熟但是已经可以满足绝大多数需求了(目前 Next.js, Parcel, and Deno 等等已经投入使用了)。

相较于进行代码编译,如果你是一个没有任何历史包袱的新项目 SWC 倒是一个不错的选择。当项目越来越庞大时代码打包过程中绝大多数耗时其实正是发生在转译过程中,而 SWC 可以带给你的证实飞一般的速度。

结尾

关于 @babel/plugin-transform-runtime 的分享在这里就和大家告一段落了。

当然,如果小伙伴们对于文中有任何疑问也可以在评论区留下你的想法。

之后如果有机会的话我会继续这个专题和大家分享一些实用的 Babel-Plugin 的编写以及 SWC 方向的拓展话题。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-10-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
MySQL函数及用法示例(收藏大全)
1、字符串函数 ascii(str) 返回字符串str的第一个字符的ascii值(str是空串时返回0) mysql> select ascii('2');   -> 50 mysql> select ascii(2);   -> 50 mysql> select ascii('dete');   -> 100
java思维导图
2018/08/16
9440
MySQL函数大全及用法示例(三)
dayofweek(date) 返回日期date是星期几(1=星期天,2=星期一,……7=星期六,odbc标准) mysql> select dayofweek('1998-02-03');   -> 3 weekday(date) 返回日期date是星期几(0=星期一,1=星期二,……6= 星期天)。 mysql> select weekday('1997-10-04 22:23:00');   -> 5 mysql> select weekday('1997-11-05');   -> 2 dayofmonth(date) 返回date是一月中的第几日(在1到31范围内) mysql> select dayofmonth('1998-02-03');   -> 3 dayofyear(date) 返回date是一年中的第几日(在1到366范围内) mysql> select dayofyear('1998-02-03');   -> 34 month(date) 返回date中的月份数值 mysql> select month('1998-02-03');   -> 2 dayname(date) 返回date是星期几(按英文名返回) mysql> select dayname("1998-02-05");   -> 'thursday' monthname(date) 返回date是几月(按英文名返回) mysql> select monthname("1998-02-05");   -> 'february' quarter(date) 返回date是一年的第几个季度 mysql> select quarter('98-04-01');   -> 2 week(date,first) 返回date是一年的第几周(first默认值0,first取值1表示周一是 周的开始,0从周日开始) mysql> select week('1998-02-20');   -> 7 mysql> select week('1998-02-20',0);   -> 7 mysql> select week('1998-02-20',1);   -> 8 year(date) 返回date的年份(范围在1000到9999) mysql> select year('98-02-03');   -> 1998 hour(time) 返回time的小时数(范围是0到23) mysql> select hour('10:05:03');   -> 10 minute(time) 返回time的分钟数(范围是0到59) mysql> select minute('98-02-03 10:05:03');   -> 5 second(time) 返回time的秒数(范围是0到59) mysql> select second('10:05:03');   -> 3 period_add(p,n) 增加n个月到时期p并返回(p的格式yymm或yyyymm) mysql> select period_add(9801,2);   -> 199803 period_diff(p1,p2) 返回在时期p1和p2之间月数(p1和p2的格式yymm或yyyymm) mysql> select period_diff(9802,199703);   -> 11 date_add(date,interval expr type) date_sub(date,interval expr type) adddate(date,interval expr type) subdate(date,interval expr type) 对日期时间进行加减法运算 (adddate()和subdate()是date_add()和date_sub()的同义词,也 可以用运算符+和-而不是函数 date是一个datetime或date值,expr对date进行加减法的一个表 达式字符串type指明表达式expr应该如何被解释  [type值 含义 期望的expr格式]:  second 秒 seconds
哲洛不闹
2018/09/14
1K0
小白博客 MySQL日期时间函数大全
DAYOFWEEK(date) 返回日期date是星期几(1=星期天,2=星期一,……7=星期六,ODBC标准) mysql> select DAYOFWEEK('1998-02-03'); -> 3 WEEKDAY(date) 返回日期date是星期几(0=星期一,1=星期二,……6= 星期天)。 mysql> select WEEKDAY('1997-10-04 22:23:00'); -> 5 mysql> select WEEKDAY('1997-11-05'); -> 2 DAYOFMO
奶糖味的代言
2018/04/11
2K0
[864]mysql日期时间函数
sysdate() 日期时间函数跟 now() 类似,不同之处在于:now() 在执行开始时值就得到了, sysdate() 在函数执行时动态得到值。看下面的例子就明白了:
周小董
2022/04/13
8K0
MySQL 格式化日期函数 DATE_FORMAT(), FROM_UNIXTIME() 和 UNIX_TIMESTAMP() 之间区别
MySQL 中有非常多的日期函数,但是使用到比较多的就是 DATE_FORMAT(), FROM_UNIXTIME() 和 UNIX_TIMESTAMP() 这三个,DATE_FORMAT() 把日期进行格式化,FROM_UNIXTIME() 把时间戳格式化成一个日期,UNIX_TIMESTAMP() 正好想法,把日期格式化成时间戳。下面就介绍下他们之间详细的使用过程:
Denis
2023/04/15
1.1K0
Mysql 中的日期时间函数汇总
MySQL中内置了大量的日期和时间函数,能够灵活、方便地处理日期和时间数据,本节就简单介绍一下MySQL中内置的日期和时间函数。
跟着飞哥学编程
2023/02/10
20.1K0
Mysql 中的日期时间函数汇总
那些年我们的(具有含金量)MySQL测试题目
 请耐心阅读,下面有惊喜! 1.创建数据库 CREATE DATABASE QQDB; 2.创建各表(表结构;约束) /*******************创建********************/ USE QQDB; DROP TABLE IF EXISTS QQUser; CREATE TABLE QQUser ( qqid BIGINT PRIMARY KEY, PASSWORD VARCHAR(20) NOT NULL, lastlogtime DATETIME NOT NULL, onli
房上的猫
2018/03/14
1.3K0
MySQL:日期函数、时间函数总结
获得当前日期+时间(date + time)函数:sysdate() sysdate() 日期时间函数跟 now() 类似,不同之处在于:now() 在执行开始时值就得到了, sysdate() 在函数执行时动态得到值。看下面的例子就明白了:
Java架构师必看
2021/12/19
3.4K0
MySQL单行函数详解
函数在计算机语言的使用中贯穿始终,函数的作用是什么呢?它可以把我们经常使用的代码封装起来,需要的时候直接调用即可。这样既提高了代码效率,又提高了可维护性。在 SQL 中我们也可以使用函数对检索出来的数据进行函数操作。使用这些函数,可以极大地提高用户对数据库的管理效率。
timerring
2023/02/16
1.5K0
MySQL单行函数详解
mysql中关于时间统计的sql语句总结
在之前写VR360时有一个统计页面(https://vr.beifengtz.com/p/statistics.html),在此页面的数据统计时用到了很多mysql中日期函数和时间统计sql语句,当时也是参考了一些资料才写出来的。在平时开发中,涉及到统计数据、报表甚至大数据计算时一定会使用这些日期函数,其他关系数据库也是类似的,我是以mysql为例,比较简单还免费嘛。话不多说,下面直接列出常用的时间统计sql语句,记录下来方便以后学习巩固。
beifengtz
2019/06/03
4.1K0
Mysql-7-mysql函数
1.数学函数   用来处理数值数据方面的运算,主要的数学函数有:绝对值函数,三角函数,对数函数,随机函数。使用数学函数过程中,如果有错误产生,该函数会返回null值。 数学函数 功能介绍 组合键 abs(x) 返回x的绝对值 整数本身 pi() 返回圆周率 返回pa的值,默认显示6位 sqrt(x) 返回非负数x的二次方根 如为负数,返回null mod(x,y) 返回x/y的模,即相除余数
用户1173509
2018/01/17
8.4K0
MySQL中日期和时间函数学习--MySql语法
下面的例子使用了时间函数。以下询问选择了最近的 30天内所有带有date_col 值的记录:
用户1289394
2021/07/09
2.5K0
用于 SELECT 和 WHERE 子句的函数
1 一个 SQL 语句中的 select_expression 或 where_definition 可由任何使用了下面所描述函数的表达式组成。 2 3 包含 NULL 的表达式总是得出一个 NULL 值结果,除非表达式中的操作和函数在文档中有另外的说明。 4 5 注意:在一个函数名和跟随它的括号之间必须不存在空格。这有助于 MySQL 语法分析程序区分函数调用和对恰巧与函数同名表或列的引用。然而,参数左右两边的空格却是允许的。 6 7 你可以强制 My
用户1112962
2018/07/03
5.9K0
MySQL数据库,从入门到精通:第七篇——MySQL单行函数应用
在MySQL数据库中,函数是一种非常强大的功能,可以帮助我们对数据进行各种操作,例如进行数值计算、字符串格式化、日期时间处理等等。本文将全面介绍MySQL中的单行函数,涵盖数值函数、字符串函数、日期时间函数和加密解密函数等多个方面,帮助读者更好地理解和应用MySQL中的单行函数。
默 语
2024/11/20
3100
MySQL数据库,从入门到精通:第七篇——MySQL单行函数应用
MySQL的日期时间计算速查表
最近写个SQL逻辑,涉及到计算各种日期和时间,MySQL提供了很丰富的函数来支持,记录一下,用的时候,有地方可查。
bisal
2023/04/08
2.3K0
盘点MySQL中常用的函数
在平常使用MySQL的过程中,我们常常会使用到其中的函数。有些函数常用,就会非常熟悉,但有些不经常使用就会十分生疏。
半月无霜
2023/03/03
7390
sql server 与mysql的区别_sql server的优缺点
最近在自学jsp,这就少不了和数据库打交道啊,相信大家对SQLserver和MySQL不陌生吧。 在视频上老师用的是sqlserver数据库,但是我用的时候却是mysql数据库,可真的是吃了不少的苦头啊 。 直接上代码吧
全栈程序员站长
2022/11/09
3.4K0
sql server 与mysql的区别_sql server的优缺点
MySQL日期和时间函数汇总
同一个日期时间会有多种不同的表示方式,有的时候需要在不同格式之间相互转换。在MySQL中用的是date_format()函数:
三分恶
2020/12/11
4.4K0
MySql时间函数
同now()函数不同的是,now()在执行开始时得到,sysdate()在函数执行时动态得到。
悠扬前奏
2019/05/28
6.5K0
【重学 MySQL】三十二、日期时间函数
GET_FORMAT函数中date_type和format_type参数取值如下:
用户11332765
2024/10/28
4750
【重学 MySQL】三十二、日期时间函数
相关推荐
MySQL函数及用法示例(收藏大全)
更多 >
交个朋友
加入腾讯云官网粉丝站
双11活动抢先看 更有社群专属礼券掉落
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档