• 作者:老汪软件技巧
  • 发表时间:2024-09-15 04:01
  • 浏览量:

Babel 插件Babel插件的分类

根据文章内容,Babel插件主要分为三类:

transform插件:

proposal插件:

syntax插件:

插件的基本结构

一个典型的Babel插件结构如下:

export default function() {
  return {
    visitor: {
      Identifier(path) {
        // 转换逻辑
      }
    }
  };
}

syntax插件示例

syntax插件通常在parser选项中添加一个标志,让Babel知道如何解析特定语法。例如:

import { declare } from "@babel/helper-plugin-utils";
export default declare(api => {
  api.assertVersion(7);
  return {
    name: "syntax-function-bind",
    manipulateOptions(opts, parserOpts) {
      parserOpts.plugins.push("functionBind");
    },
  };
});

这个插件让Babel能够解析函数绑定操作符(::)。

transform插件示例

让我们创建一个简单的transform插件,将所有的let声明转换为var:

export default function() {
  return {
    visitor: {
      VariableDeclaration(path) {
        if (path.node.kind === 'let') {
          path.node.kind = 'var';
        }
      }
    }
  };
}

使用这个插件:

const babel = require('@babel/core');
const myPlugin = require('./myPlugin');
const code = `
let x = 1;
let y = 2;
`;
const output = babel.transform(code, {
  plugins: [myPlugin]
});
console.log(output.code);
// 输出:
// var x = 1;
// var y = 2;

proposal插件示例

proposal插件用于转换还在提案阶段的语法。例如,optional chaining插件:

export default function({ types: t }) {
  return {
    visitor: {
      OptionalMemberExpression(path) {
        let { object, property, computed } = path.node;
        let ref = path.scope.generateUidIdentifierBasedOnNode(object);
        
        path.replaceWith(
          t.logicalExpression(
            "&&",
            t.assignmentExpression("=", ref, object),
            computed 
              ? t.memberExpression(ref, property, true)
              : t.memberExpression(ref, property)
          )
        );
      }
    }
  };
}

这个插件将可选链操作符(?.)转换为逻辑与(&&)操作。

使用helper的插件示例

Babel提供了许多helper函数来简化插件开发。例如:

import { declare } from "@babel/helper-plugin-utils";
import { addDefault } from "@babel/helper-module-imports";
export default declare((api, options) => {
  api.assertVersion(7);
  return {
    visitor: {
      Program(path) {
        const reactIdentifier = addDefault(path, 'react', {
          nameHint: '_react'
        });
        path.scope.rename('React', reactIdentifier.name);
      }
    }
  };
});

这个插件使用@babel/helper-module-imports来添加React的默认导入。

插件的执行顺序

插件的执行顺序是从左到右,并且插件会在preset之前执行。例如:

{
  "plugins": ["plugin-1", "plugin-2", "plugin-3"]
}

执行顺序为: plugin-1 -> plugin-2 -> plugin-3 -> presets

插件的选项

插件可以接受选项。例如:

{
  "plugins": [
    ["@babel/plugin-proposal-class-properties", { "loose": true }]
  ]
}

在插件中可以这样访问选项:

export default function({ types: t }) {
  return {
    visitor: {
      ClassProperty(path, state) {
        if (state.opts.loose === true) {
          // 使用loose模式的转换逻辑
        } else {
          // 使用默认模式的转换逻辑
        }
      }
    }
  };
}

自定义插件的开发流程

开发自定义插件的一般流程是:

分析要转换的代码的AST结构确定要访问的AST节点类型在visitor对象中实现相应的转换逻辑使用@babel/types创建新的AST节点使用path.replaceWith()等方法替换或修改原有节点

例如,开发一个将console.log转换为自定义logger的插件:

export default function({ types: t }) {
  return {
    visitor: {
      CallExpression(path) {
        if (t.isMemberExpression(path.node.callee) &&
            t.isIdentifier(path.node.callee.object, { name: "console" }) &&
            t.isIdentifier(path.node.callee.property, { name: "log" })) {
          path.node.callee = t.memberExpression(
            t.identifier("customLogger"),
            t.identifier("log")
          );
        }
      }
    }
  };
}

这个插件将console.log()调用转换为customLogger.log()。

Babel 的 preset:Preset 的概念和作用

Preset 是一组预设的 Babel 插件和/或其他 preset。它的主要作用是简化 Babel 的配置过程。使用 preset 可以避免手动配置大量单独的插件,从而更容易地设置常见的转换任务。

主要的 Preset

根据文章内容,主要的 preset 包括:

Preset 的演变

Babel 6 时期的 preset:

Babel 7 的变化:

@babel/preset-env

这是最重要的 preset,它的主要特点包括:

a) 根据目标环境自动引入需要的插件b) 使用 browserslist 来配置目标环境c) 可以通过 useBuiltIns 选项配置 polyfill 的引入方式d) 可以通过 modules 选项配置模块转换

配置示例:

{
  "presets": [
    ["@babel/preset-env", {
      "targets": "> 0.25%, not dead",
      "useBuiltIns": "usage",
      "corejs": 3,
      "modules": false
    }]
  ]
}

Preset 的配置选项

以 preset-env 为例,主要的配置选项包括:

Preset 的执行顺序

Preset 的执行顺序是从右到左,这与插件的从左到右相反。例如:

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

执行顺序是:preset-react -> preset-env

自定义 Preset

开发者可以创建自己的 preset,将常用的插件和配置打包在一起。例如:

module.exports = function() {
  return {
    presets: [
      require("@babel/preset-env"),
    ],
    plugins: [
      [require("@babel/plugin-proposal-class-properties"), { loose: true }],
      require("@babel/plugin-proposal-object-rest-spread"),
    ],
  };
};

Preset 与 Polyfill

preset-env 可以通过配置来自动引入需要的 polyfill:

{
  "presets": [
    ["@babel/preset-env", {
      "useBuiltIns": "usage",
      "corejs": 3
    }]
  ]
}

这样配置后,Babel 会根据代码中使用的特性和目标环境自动引入需要的 polyfill。

@babel/plugin-transform-runtime

@babel/plugin-transform-runtime 主要通过两个步骤工作:

a) 替换 Babel 注入的辅助函数:当 Babel 转换代码时,它会在需要的地方注入一些辅助函数。这个插件会将这些内联的辅助函数替换为对 @babel/runtime 模块的引用。

b) 提供 polyfill:对于一些新的 API 或全局对象,插件会引入 core-js 的特定实现,而不是修改全局原型。

主要用途:

a) 减少代码体积:通过引用 runtime 中的辅助函数,而不是在每个需要的地方都内联这些函数,可以显著减少生成的代码体积。

b) 避免污染全局作用域:特别是在开发库时,你不希望修改全局原型或引入全局变量,这个插件可以帮助你实现这一点。

c) 模块化的 polyfill:只引入需要的 polyfill,而不是全量引入。

配置选项:

插件有几个重要的配置选项:

{
  "plugins": [
    ["@babel/plugin-transform-runtime", {
      "corejs": 3, // 或 2, 或 3
    }]
  ]
}

corejs:指定是否使用 core-js 进行 polyfill。

实际例子:

假设有以下代码:

class MyClass {
  constructor() {
    this.myAsyncMethod();
  }
  
  async myAsyncMethod() {
    await Promise.resolve();
  }
}

不使用插件时,Babel 可能会生成类似这样的代码:

"use strict";
function _classCallCheck(instance, Constructor) { /* ... */ }
function _defineProperty(obj, key, value) { /* ... */ }
// 其他辅助函数...
var MyClass = function MyClass() {
  _classCallCheck(this, MyClass);
  _defineProperty(this, "myAsyncMethod", async function () {
    await Promise.resolve();
  });
  this.myAsyncMethod();
};

使用插件后,代码可能变成:

"use strict";
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
var _defineProperty = require("@babel/runtime/helpers/defineProperty");
// 其他辅助函数的引入...
var MyClass = function MyClass() {
  _classCallCheck(this, MyClass);
  _defineProperty(this, "myAsyncMethod", async function () {
    await Promise.resolve();
  });
  this.myAsyncMethod();
};

Babel 配置文件如下:

{
  "presets": [
    ["@babel/preset-env", {
      "targets": "> 0.25%, not dead",
      "useBuiltIns": "usage",
      "corejs": 3
    }]
  ],
  "plugins": [
    ["@babel/plugin-transform-runtime", {
      "corejs": 3
    }]
  ]
}

这样,辅助函数就从内联变成了模块引用,可以有效减少代码体积,特别是在多个文件都使用这些辅助函数的情况下。


上一条查看详情 +索引:数据库查询性能提升的利器
下一条 查看详情 +没有了