Skip to content
文档
插件
ECMAScript
入门

Implementing a plugin

Important API Changes

⚠️

最近的 API 更改可能会影响你的插件开发:

  • chain! 宏替换为元组:使用 ( 而不是 chain!(。您可以使用 IDE 功能将所有 chain!( 替换为 (
  • 对于具有 13 个以上参数的 chain!:对第 13 个元素之后的项使用嵌套元组。
  • 通常将 -> impl Fold 替换为 -> impl Pass
  • as_folder 现在为 visit_mut_pass,并返回 impl VisitMut + Pass 而不是 impl VisitMut + Fold
  • 使用 Program.applyProgram.mutate 将 Pass 应用于程序:
  • fn apply(self, impl Pass) -> Self
  • fn mutate(&mut self, impl Pass)
  • noop() 替换为 noop_pass()。新函数位于 swc_ecma_ast 中,是一个真正的无操作函数。

设置环境

安装所需的工具链

由于插件是用 Rust 编程语言编写的,并构建为 .wasm 文件,因此你需要安装 Rust 工具链和 wasm 目标。

安装 Rust

你可以按照 Rust 官方网站的“安装 Rust”页面 (opens in a new tab) 上的说明进行操作。

为 Rust 添加 wasm 目标

SWC 支持两种类型的 .wasm 文件。 它们是

  • wasm32-wasip1
  • wasm32-unknown-unknown

在本指南中,我们将使用 wasm-wasip1 作为目标。

安装 swc_cli

你可以通过以下命令安装基于 Rust 的 SWC CLI:

cargo install swc_cli

配置 IDE

如果你打算使用 vscode,建议安装 rust-analyzer 扩展。 rust-analyzer 是 Rust 编程语言的 语言服务器 (opens in a new tab),它提供了代码补全、代码导航和代码分析的良好功能。

实现简单的插件

创建项目

SWC CLI 支持创建一个新的插件项目。

运行

swc plugin new --target-type wasm32-wasip1 my-first-plugin
# 你需要运行这个
rustup target add wasm32-wasip1

来创建一个新的插件,并使用你喜欢的 Rust IDE 打开 my-first-plugin

实现访问者

生成的代码包含

impl VisitMut for TransformVisitor {
    // 为实际的自定义转换实现必要的 visit_mut_* 方法。
    // 可以在以下位置找到可能的访问者方法的完整列表:
    // https://rustdoc.swc.rs/swc_ecma_visit/trait.VisitMut.html
}

用于转换代码。 VisitMut trait (opens in a new tab) 支持对 AST 节点进行变异,并且由于它支持所有 AST 类型,因此它有很多方法。


我们将使用

foo === bar;

作为输入。从 SWC Playground (opens in a new tab) 中,你可以获取此代码的实际表现。

{
  "type": "Module",
  "span": {
    "start": 0,
    "end": 12,
    "ctxt": 0
  },
  "body": [
    {
      "type": "ExpressionStatement",
      "span": {
        "start": 0,
        "end": 12,
        "ctxt": 0
      },
      "expression": {
        "type": "BinaryExpression",
        "span": {
          "start": 0,
          "end": 11,
          "ctxt": 0
        },
        "operator": "===",
        "left": {
          "type": "Identifier",
          "span": {
            "start": 0,
            "end": 3,
            "ctxt": 0
          },
          "value": "foo",
          "optional": false
        },
        "right": {
          "type": "Identifier",
          "span": {
            "start": 8,
            "end": 11,
            "ctxt": 0
          },
          "value": "bar",
          "optional": false
        }
      }
    }
  ],
  "interpreter": null
}

让我们为 BinExpr 实现一个方法。 你可以这样做

use swc_core::{
    ast::*,
    visit::{VisitMut, VisitMutWith},
};
 
impl VisitMut for TransformVisitor {
    fn visit_mut_bin_expr(&mut self, e: &mut BinExpr) {
        e.visit_mut_children_with(self);
    }
}

请注意,如果你想为子节点调用方法处理程序,则需要 visit_mut_children_with。 例如,foobarvisit_mut_ident 将通过上面的 e.visit_mut_children_with(self); 调用。

让我们使用二元运算符来缩小范围。

use swc_core::{
    ast::*,
    visit::{VisitMut, VisitMutWith},
    common::Spanned,
};
 
impl VisitMut for TransformVisitor {
    fn visit_mut_bin_expr(&mut self, e: &mut BinExpr) {
        e.visit_mut_children_with(self);
 
        if e.op == op!("===") {
            e.left = Box::new(Ident::new_no_ctxt("kdy1".into(), e.left.span()).into());
        }
    }
}

op!("===") 是一个宏调用,它返回各种类型的运算符。 在这种情况下,它返回 BinaryOp (opens in a new tab),因为我们提供了 "===",这是一个二元运算符。 有关更多详细信息,请参见 op! 宏的 rustdoc (opens in a new tab)

如果我们运行这个插件,我们将得到

kdy1 === bar;

测试你的转换

你可以简单地运行 cargo test 来测试你的插件。 SWC 还提供了一个实用程序来简化夹具测试。

你可以轻松验证转换的输入和输出。

test!(
    Default::default(),
    |_| visit_mut_pass(TransformVisitor), // 注意:更新为使用 visit_mut_pass 而不是 as_folder
    boo,
    r#"foo === bar;"#
);

然后,一旦你运行 UPDATE=1 cargo test,快照将被更新。

你可以查看 typescript 类型剥离器的真实夹具测试 (opens in a new tab)

#[testing::fixture("tests/fixture/**/input.ts")]
#[testing::fixture("tests/fixture/**/input.tsx")]
fn fixture(input: PathBuf) {
    let output = input.with_file_name("output.js");
    test_fixture(
        Syntax::Typescript(TsConfig {
            tsx: input.to_string_lossy().ends_with(".tsx"),
            ..Default::default()
        }),
        &|t| (tr(), properties(t, true)), // 注意:更新为使用元组语法而不是链式调用!
        &input,
        &output,
    );
}

需要注意的事项:

  • 提供给 testing::fixture 的 glob 是相对于 cargo 项目目录的。
  • 输出文件是 output.js,它与输入文件存储在同一目录中。
  • test_fixture 驱动测试。
  • 你可以通过将语法传递给 test_fixture 来确定输入文件的语法。
  • 然后你提供你的访问者实现作为 test_fixture 的第二个参数。
  • 然后你提供输入文件路径和输出文件路径。

日志记录

SWC 使用 tracing 进行日志记录。 默认情况下,SWC 测试库将日志级别配置为 debug,这可以通过使用名为 RUST_LOG 的环境变量来控制。 例如,RUST_LOG=trace cargo test 将打印所有日志,包括 trace 日志。

如果你愿意,你可以通过使用 tracing 的 cargo 功能来删除插件的日志记录。 参见 其文档 (opens in a new tab)

发布你的插件

请参阅 插件发布指南