不谈配置,聊一聊 @babel/plugin-transform-runtime 做了什么

961 阅读3分钟

我正在参加「掘金·启航计划」

说到 @babel/plugin-transform-runtime 真是熟悉又陌生,经常在 Babel 配置里看到,问它做了什么,却又答不上来,今天通过两个例子,一起实践下。

引用下 官方 的描述:

A plugin that enables the re-use of Babel's injected helper code to save on codesize.

译:一个插件,可以复用 Babel 注入的辅助代码,以节省代码大小。

看起来说明白了,但又不那么清楚。下面我们一起动手实践下,看看它到底做了什么。

功能一:自动引入辅助函数

首先创建一个源文件

export class D {
  d = 0;
  constructor() {
    this.d = 111;
  }
}

使用 @babel/preset-env 转换

简单提一下,@babel/preset-env 是一大堆插件的集合,包含了当前浏览器环境下,所有语言特性的插件,可以根据 browserList 的结果,选择合适的插件将新语言特性转译成旧浏览器可以支持的表达方式。

/** ---------------- babel.config.js --------------- */
module.exports = {
  presets: ['@babel/preset-env']
};

/** -------------------- 转换后的文件 ----------------------- */
function _classCallCheck(instance, Constructor) {
  /** xxx */
}
function _defineProperties(target, props) {
  /** xxx */
}
function _createClass(Constructor, protoProps, staticProps) {
  /** xxx */
}
var D = /*#__PURE__*/ _createClass(function D() {
  _classCallCheck(this, D);

  this.d = 0;
  this.d = 111;
});
export { D };

我们可以看到 @babel/preset-env 在处理语法转换时会注入很多辅助函数(_classCallCheck、_createClass 等),如果项目文件很多,会导致体积十分庞大

为了解决这个问题,@babel/runtime 出现了,它将这些辅助函数整合到了一起,我们可以通过包来复用这些代码。

var _classCallCheck = require('@babel/runtime/helpers/classCallCheck');
var _defineProperties = require('@babel/runtime/helpers/defineProperty');

/** xxx */

这样解决了代码复用的问题,但是每次手动引入过于麻烦,于是 @babel/plugin-transform-runtime 登场。

module.exports = {
  presets: ['@babel/preset-env'],
  plugins: ['@babel/plugin-transform-runtime']
};

/** -------------------- 转换后的文件 ----------------------- */
var _interopRequireDefault = require('@babel/runtime/helpers/interopRequireDefault');

Object.defineProperty(exports, '__esModule', {
  value: true
});
exports.D = void 0;
/** ⚠️ 注意看这里 */
var _createClass2 = _interopRequireDefault(
  require('@babel/runtime/helpers/createClass')
);

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

var _defineProperty2 = _interopRequireDefault(
  require('@babel/runtime/helpers/defineProperty')
);

var D = /*#__PURE__*/ (0, _createClass2.default)(function D() {
  (0, _classCallCheck2.default)(this, D);
  (0, _defineProperty2.default)(this, 'd', 0);
  this.d = 111;
});
exports.D = D;

功能二:为类库打包增加 Polyfill

在开发类库的时候,也会用到一些新语法,需要有对应的 Polyfill,但是如果项目中不存在该怎么办呢?我们又不能在组件层注入全局 Polyfill (不想组件影响项目) 因此 @babel/plugin-transform-runtime 提供了局部 Polyfill 的方法。

创建源文件

export const a = new Promise((resolve, reject) => {
  resolve(true);
});
export async function asyncFn() {
  await a;
}

按照默认配置进行代码转换

/** ---------------- babel.config.js --------------- */
module.exports = {
  presets: ['@babel/preset-env'],
  plugins: ['@babel/plugin-transform-runtime']
};

/** -------------------- 转换后的文件 ----------------------- */
var _interopRequireDefault = require('@babel/runtime/helpers/interopRequireDefault');

Object.defineProperty(exports, '__esModule', {
  value: true
});
exports.a = void 0;
exports.asyncFn = asyncFn;

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

var _asyncToGenerator2 = _interopRequireDefault(
  require('@babel/runtime/helpers/asyncToGenerator')
);
/** ⚠️ 主意看这行 */
var a = new Promise(function (resolve, reject) {
  resolve(true);
});
exports.a = a;

function asyncFn() {
  /** xxx */
}

根据转换后的代码可以发现,Promise 仍然用的全局的,也就是项目中必须注入 Polyfill,否则类库会出错。

我们修改下配置,通过设置 core-js 来转换 (core-js 可以配置多种, 我们这里使用 version: 3 详见 官方文档

/** ---------------- babel.config.js --------------- */
module.exports = {
  presets: ['@babel/preset-env'],
  plugins: [['@babel/plugin-transform-runtime', { corejs: 3 }]]
};

/** -------------------- 输出结果 ----------------------- */
var _Object$defineProperty = require('@babel/runtime-corejs3/core-js-stable/object/define-property');
var _interopRequireDefault = require('@babel/runtime-corejs3/helpers/interopRequireDefault');
_Object$defineProperty(exports, '__esModule', {
  value: true
});

exports.a = void 0;
exports.asyncFn = asyncFn;

var _regenerator = _interopRequireDefault(
  require('@babel/runtime-corejs3/regenerator')
);

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

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

var a = new _promise.default(function (resolve, reject) {
  resolve(true);
});
exports.a = a;

function asyncFn() {
  /** xxx */
}

可以看到,现在转换后的 Promise 是一个从 @babel/runtime-corejs3/core-js-stable/promise 导入的局部变量

总结

  1. 删除内联的辅助函数,并自动从 @babel/runtime/helpers 中引入,以节省代码大小。
  2. 以局部变量的方式在 core-js 中引入 Polyfill, @babel/plugin-transform-runtime 引入的 Polyfill 不会对全局造成污染,所以很适合作为类库的打包。

参考