Babel 常用插件、预设详解

发布于 2023-07-22 21:03:20 字数 17557 浏览 36 评论 0

常用插件

@babel/plugin-transform-runtime

此插件可以复用 Babel 注入的辅助代码以减少代码体积;另一个作用可以避免全局注入 polyfill,从而避免污染全局。

Babel 会使用很多辅助函数来帮助实现代码转换,比如下面示例中的 _extends ,这些函数统称为 helpers 。默认情况下,每个文件顶部会添加当前文件中所需要的 helpers ,如果存在多个文件,就可能重复添加相同的 helper ,编译输出中就会重复打包这些函数导致包体积增大。

// 源码:
<Component {...props} prop="a"/>;

// 转换后:
require("core-js/modules/es.object.assign.js");

// helper
function _extends() { _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i]; for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key]; } } } return target;
};
return _extends.apply(this, arguments);
} React.createElement(Component, _extends({}, props, { prop: "a" }));

@babel/plugin-transform-runtime 的作用就是将所有 helpers 都从 @babel/runtime 模块导入,从而避免在编译输出中出现重复。因此使用此插件还需要安装 @babel/runtime 作为生产依赖被编译到构建中。

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

使用 @babel/plugin-transform-runtime 后:

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

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

React.createElement(Component, (0, _extends2.default)({}, props, {
  prop: "a"
}));

@babel/plugin-transform-runtime 的另一个作用是避免 polyfill 全局污染。无论是导入 core-js@babel/polyfill (Babel 7.4.0 开始已废弃),polyfill 的行为如全局添加内置对象(如 Promise )、内置对象新增静态属性(如 Array.from )或实例属性(如 [].includes ),这些都会导致全局污染。虽然这对于应用程序或命令行工具来说可能没问题,但如果作为一个库供别人使用,或者你无法完全控制其运行的环境,这就会带来问题。

// 源码:
var a = new Promise();

// 转换后:
require("core-js/modules/es.promise.js"); // Promise polyfill 导致全局污染
var a = new Promise();

@babel/plugin-transform-runtime 后(插件参数选项 "corejs": 3 ,详见下面):

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

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

@babel/runtime-corejs3core-js3 的运行时版本,转换器为其添加别名进行调用,从而无需使用 polyfill。

插件参数选项:

{
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        // 指定 core-js 的 runtime 版本。false 表示 @babel/runtime(只包含 helper),指定数字表示 core-js 的 runtime 版本(还包含 polyfill,如 @babel/runtime-corejs2)。详见下面
        "corejs": false, // false 表示 @babel/runtime
        // 指定 @babel/runtime 或对应 corejs 版本 的绝对路径。默认情况下 `@babel/runtime` 需要位于正在编译的文件的 node_modules 中,其他情况下需要指定绝对路径以保证运行时模块的位置的正确解析。
        "absoluteRuntime": false,
        // 是否复用 helpers。将内联的 helpers(如 extends)替换为对模块的调用,即复用 helpers。
        "helpers": true,
        // generator 函数是否转换为使用不污染全局范围的 regenerator runtime,详见下面 
        "regenerator": true,
        // 运行时模块的版本号,默认 @babel/runtime@7.0.0
        // 例如 { corejs: 2,version: '^7.7.4' } 表示依赖 @babel/runtime-corejs2@7.7.4
        "version": "7.0.0-beta.0",
        // 是否支持提案。此插件默认不填充提案,如果 `corejs: 3`,此选项为 true 可开启
        "proposals": false
      }
    ]
  ]
}

corejs

指定 core-js 的 runtime 版本。Babel v7 中已经把 helper 与它在运行时的 polyfilling 行为分开了。
false 表示 @babel/runtime (仅包含 helper),指定数字( 23 )表示 core-js 的 runtime 版本(包含 helper + polyfill,如 @babel/runtime-corejs2 )。

// false => @babel/runtime
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
require("core-js/modules/es.promise.js");

// 2 => @babel/runtime-corejs2
var _extends2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/extends"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));

// 3 => @babel/runtime-corejs3
var _extends2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/extends"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));

此选项需要更改用于提供 runtime helpers 的依赖项:

  • false :默认,即 @babel/runtime ,仅包含 helpers。
  • 2 :即 @babel/runtime-corejs2 ,包含 helpers + polyfill(对应 core-js v2 )。仅支持全局变量(如 Promise )和静态属性(如 Array.from )。
  • 3 :即 @babel/runtime-corejs3 ,包含 helpers + polyfill(对应 core-js v3 )。支持全局变量和静态属性,还支持实例属性(如 [].includes )。

更改此选项需要安装对应的生产依赖( @babel/runtime | @babel/runtime-corejs2 | @babel/runtime-corejs3 ),例如 npm install --save @babel/runtime-corejs3
@babel/plugin-transform-runtime 作为开发环境依赖始终都是需要安装的。

regenerator

generator 函数是否转换为使用不污染全局范围的 regenerator runtime

// 源码:
function* foo() {}

// generator=false 时:
var _marked = /*#__PURE__*/regeneratorRuntime.mark(foo);
// 依赖了 regeneratorRuntime,会污染全局
function foo() {
  return regeneratorRuntime.wrap(function foo$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}

// generator=true 时:
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var _marked = /*#__PURE__*/_regenerator.default.mark(foo);

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

常用预设( presets

@babel/preset-env

@babel/preset-env 是一个智能预设,指定目标环境后即可使用最新的 JavaScript 特性,它会去管理需要哪些插件(以及 polyfill)。

它利用 browserslistcompat-table (ECMAScript 兼容性表)数据,维护了目标环境中各版本与 JavaScript 语法或浏览器功能支持的映射表,以及这些语法和功能需要哪些 Babel 转换插件和 core-js polyfill 的映射表。所以只需要你设置目标环境, @babel/preset-env 就能找到所需的插件和 polyfill。

它同时替代了 preset-es2015/es2016/es2017/latest 这几个预设,对于开发者而言,直接关注目标环境而无需关注特性属于哪一套标准更加简单。

预设参数选项:

{
  "presets": {
    // 项目支持的环境或目标环境。详见下面
    "targets": "> 0.25%, not dead",
    "bugfixes": false,
    // 如何优化 polyfill 引入,"usage"| "entry"| false, 详见下面
    "useBuiltIns": false,
    // 是否开启对处于提案中、且浏览器已经实现的内置对象/特性的支持。详见下面
    "shippedProposals": false,
    // 指定 core-js (polyfill)版本。@babel/preset-env 的 polyfill 来自于 core-js。详见下面
    "corejs": "2.0", // 其他如 "3.8"或"2.0", "3" 将解析为 "3.0"
    // 是否允许预设中插件启用松散模式转换。对比详见下面。
    "loose": false, // 默认 fasle,即 'normal'
    // 始终需包含的插件
    "include": [],
    // 始终排除/删除的插件
    "exclude": [],
    // 启用 ES 模块语法到另一种模块类型的转换。"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false
    "modules": "auto"
  }
}

主要选项说明:

targets

项目支持的环境或目标环境。

可以设置为 browserslist-compatible 查询,或者是一个描述支持的最低环境版本对象。

{
  // browserslist
  "targets": "> 0.25%, not dead",
  // 或者
  "targets": { "chrome": "58", "ie": "11" }, // 环境可选:chrome, opera, edge, firefox, safari, ie, ios, android, node, electron
}

推荐使用 browserslist 配置(使用 .browserslistrc 文件或 package.json#browserslist 字段),因为这样可与其他工具共享配置,如 Autoprefixerpostcss-preset-env

但与 browserslist 行为不同的是,当在 Babel 或 browserslist 配置中找不到目标配置时,它不使用 defaults 查询。而会将所有 ES2015+ 代码转换为 ES5(类似 preset-latest ),这种方式不推荐使用,因为没有使用其针对特定环境/版本的能力,为了避免可以显示的声明 defaults

{
  "presets": [["@babel/preset-env", { "targets": "defaults" }]]
}

corejs

指定 core-js 版本。

core-js 是 JavaScript 的模块化标准库。包括 ECMAScript 到 2021 年的所有 polyfill(如 promises、symbols、ECMAScript 提案、一些跨平台的 WHATWG/W3C 特性和提案等), @babel/preset-env 依赖它来注入 polyfill。
此选项仅在与 useBuiltIns: usageuseBuiltIns: entry 一起使用时才有效。

默认情况下,只注入稳定 ECMAScript 功能的 polyfill,如果你想注入提案阶段的 polyfill,有三个不同的选项:

  • 使用 useBuiltIns: "entry" 时,可以直接导入一个 proposal polyfill: import "core-js/proposals/string-replace-all"

  • 使用 useBuiltIns: "usage" 时,有两种选择:

    • shippingProposals 选项设置为 true 。这将为浏览器已支持的提案启用 polyfill 和转换。
    • 使用 corejs: { version: "3.8", proposal: true } 。这将启用 core-js@3.8 支持的每个提案的 polyfill。

useBuiltIns

配置 @babel/preset-env 如何优化注入 polyfill。

如果全量注入 polyfill ( import "core-js"; )将增加包体积, useBuiltIns 选项的作用就是对其进行优化。

useBuiltIns: 'entry' 时:

@babel/preset-envimport "core-js";import "regenerator-runtime/runtime" (提供 generatorasyncawait 的 polyfill) 语句替换为只引入目标环境可能用到的 polyfill。

import "core-js";
// 转换后 `import "core-js"` 替换为(因目标环境而异):
import "core-js/modules/es.string.pad-start";
import "core-js/modules/es.string.pad-end";

如果你只需要知道只需要其中的一部分 polyfill 怎么办?

使用 core-js@3 时, @babel/preset-env 能够优化每个 core-js 入口点及其组合。例如你只想填充数组方法和 Math 新提案:

import "core-js/es/array";
import "core-js/proposals/math-extensions";
// 转换后:
import "core-js/modules/es.array.unscopables.flat";
import "core-js/modules/es.array.unscopables.flat-map";
import "core-js/modules/esnext.math.clamp";
import "core-js/modules/esnext.math.deg-per-rad";
import "core-js/modules/esnext.math.degrees";
import "core-js/modules/esnext.math.fscale";
import "core-js/modules/esnext.math.rad-per-deg";
import "core-js/modules/esnext.math.radians";
import "core-js/modules/esnext.math.scale";

useBuiltIns: 'usage' 时:

polyfill 会在需要时(目标环境不支持、当前文件中使用到的)自动导入。
@babel/preset-env 会在每个文件的开头引入需要的(目标环境不支持,当前文件中使用到)polyfill。

// a.js
var a = new Promise();
// b.js
var b = new Map();

// 转换后:(如果目标环境不支持)
// a.js
import "core-js/modules/es.promise";
var a = new Promise();
// b.js
import "core-js/modules/es.map";
var b = new Map();

// 转换后:(如果目标环境支持)
// a.js
var a = new Promise();
// b.js
var b = new Map();

shippedProposals

是否开启对处于提案中的且浏览器已经实现的内置对象/特性的支持。

你的目标环境已经有了对某提案特性的原生支持,则会开启对应的语法插件以支持解析器完成解析,而不会执行任何代码转换操作。注意这不会开启与 @babel/preset-stage-3 相同的转换,因为提案在登陆浏览器之前还会继续更改。

loose

是否允许预设中包含的插件使用松散模式转换代码。

默认 false ,即使用正常模式( normal )。Babel 中的很多插件都支持两种模式。松散模式产生更简单的 ES5 代码,而正常模式会尽可能遵循 ES6 语义。

建议不要使用松散模式,松散模式优缺点:

  • 优点:生成的代码更简洁,因为缺少了尽可能去遵循 ES6 语义而添加的繁杂逻辑,运行速度可能更快,与旧引擎兼容性更好。
  • 缺点:以后直接使用原生 ES6 时可能会遇到问题,这风险很少情况下值得冒。

下面是一段源码两种模式下的转换结果对比:

class Animal {
  constractor(name) {
      this.name = name;
  }
};

正常模式(normal mode):

"use strict";

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

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 Animal = /*#__PURE__*/function () { function Animal() { _classCallCheck(this, Animal); } _createClass(Animal, [{ key: "constractor", value: function constractor(name) { this.name = name; } }]); return Animal; }();

松散模式(loose mode):

"use strict";

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

var Animal = /*#__PURE__*/function () {
  function Animal() {}

  var _proto = Animal.prototype;

  _proto.constractor = function constractor(name) {
    this.name = name;
  };

  return Animal;
}();

其他选项查看 这里

@babel/preset-react

解析、转换 React JSX。

此预设包含的插件:

  • @babel/plugin-syntax-jsx :解析 JSX。
  • @babel/plugin-transform-react-jsx :转换 JSX。
  • @babel/plugin-transform-react-display-name :添加 displayName 。如 var bar = createReactClass({}); 转换为 var bar = createReactClass({ displayName: "bar" });

启用 development 选项后,如果预设选项 runtime 设置为 classic 还将添加以下两个插件:

  • @babel/plugin-transform-react-jsx-self :在 JSX 元素添加 __self prop,React 将使用它来生成一些运行时警告。
<sometag />
// =>
<sometag __self={this} />
  • @babel/plugin-transform-react-jsx-source :将源文件、行号和列号添加到 JSX 元素。
<sometag />
// =>
<sometag __source={ { fileName: 'this/file.js', lineNumber: 10, columnNumber: 1 } } />

预设参数选项:

{
  "presets": [
    [
      "@babel/preset-react",
      {
        // 使用哪种运行时转换 jsx。'classic' 将 jsx 转换为 `React.createElement` 调用,'automatic' 将引入 `react/jsx-runtime`,详见下面对比
        "runtime": "classic", // 默认 'classic'
        // 开启特定于开发环境的某些行为。例如此选项为 true 且 runtime='classic'时,将增加两个插件用于在 jsx 元素上添加 `__source` 和 `__self` prop。
        "development": false, // 默认 false
        // 如果使用 XML 命名空间标记名称(如 `<f:image />`)是否抛出错误。虽然 JSX 规范允许,但是默认禁止,因为 React 所实现的 JSX 目前并不支持这种方式。
        "throwIfNamespace": false, // 默认 true

// 以下选项仅在 'automatic' runtime 生效:
        // 替换导入源。详见下面对比
        "importSource": "custom-jsx-library", // 默认 `react`

// 以下选项都仅在 'classic' runtime 生效:
        // 替换编译 JSX 表达式时使用的函数(仅 'classic' runtime 生效)。
        "pragma": "dom", // 默认 `React.createElement`
        // 替换编译 JSX 片段时使用的组件(仅 'classic' runtime 生效)
        "pragmaFrag": "DomFrag", // 默认 `React.Fragment`
        
        // todo
        // Will use the native built-in instead of trying to polyfill behavior for any plugins that require one.
        "useBuiltIns": false, // 默认 false

        // 处理 props 合并时(例如 <Component {...props} prop="a"/>),直接使用带有扩展元素的内联对象(`{ ...props, prop: "a" }`),
// 还是使用 Babel 的 `extend` helper 或 `Object.assign`(`_extends({}, props, { prop : "a" })`)。详见下面对比 "useSpread": false // 默认 false } ] ] }

主要参数选项说明:

runtime

classic 是旧的转换 jsx 的方式,存在一些缺陷,如必须 React 环境, React.createElement 有一些无法做到的 性能优化和简化

下面是 classicautomatic 转换 JSX 对比:

<div></div>

// classic
React.createElement('div', null);

// automatic
import { jsx as _jsx } from "react/jsx-runtime";
_jsx("div", {});
  • importSource :替换默认的导入源。默认使用 react/jsx-runtime ,若设置为 "custom-jsx-library" ,则替换为 custom-jsx-library/jsx-runtime
// react
import { jsx as _jsx } from "react/jsx-runtime";
import { Fragment as _Fragment } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";

// custom-jsx-library
import { jsx as _jsx } from "custom-jsx-library/jsx-runtime";
import { Fragment as _Fragment } from "custom-jsx-library/jsx-runtime";
import { jsxs as _jsxs } from "custom-jsx-library/jsx-runtime";

useSpread

<Component {...props} prop="a"/>;

// false
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

React.createElement(Component, _extends({}, props, {
  prop: "a"
}));

// true
React.createElement(Component, { ...props,
  prop: "a"
});

参考

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

恍梦境°

暂无简介

0 文章
0 评论
22 人气
更多

推荐作者

13886483628

文章 0 评论 0

流年已逝

文章 0 评论 0

℡寂寞咖啡

文章 0 评论 0

笑看君怀她人

文章 0 评论 0

wkeithbarry

文章 0 评论 0

素手挽清风

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文