Skip to content
开发文档
插件
ECMAScript
新手入门

实现插件

¥Implementing a plugin

重要的 API 更改

¥Important API Changes

⚠️

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

¥Recent API changes that may affect your plugin development:

  • 用元组替换 chain! 宏:使用 ( 代替 chain!(。你可以使用 IDE 功能将所有 chain!( 替换为 (

    ¥Replace chain! macro with tuples: Use ( instead of chain!(. You can replace all chain!( with ( using IDE features.

  • 对于具有 13 个以上参数的 chain!:对第 13 个元素之后的项目使用嵌套元组。

    ¥For chain! with 13+ arguments: Use nested tuples for items after the 13th element.

  • 一般用 -> impl Pass 替换 -> impl Fold

    ¥Replace -> impl Fold with -> impl Pass in general.

  • as_folder 现在是 visit_mut_pass,返回 impl VisitMut + Pass 而不是 impl VisitMut + Fold

    ¥as_folder is now visit_mut_pass and returns impl VisitMut + Pass instead of impl VisitMut + Fold.

  • 使用 Program.applyProgram.mutate 将 Pass 应用于程序:

    ¥Use Program.apply and Program.mutate to apply Pass to program:

    • fn apply(self, impl Pass) -> Self

    • fn mutate(&mut self, impl Pass)

  • noop_pass() 替换 noop()。新功能位于 swc_ecma_ast 中,是一个真正的 noop 功能。

    ¥Replace noop() with noop_pass(). The new function lives in swc_ecma_ast and is a real noop function.

设置环境

¥Setup environment

安装所需的工具链

¥Install required toolchain

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

¥As plugin is written in the rust programming language and built as a .wasm file, you need to install rust toolchain and wasm target.

安装 Rust

¥Install rust

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

¥You can follow instructions at 'Install Rust' page from the official rust website (opens in a new tab)

将 wasm 目标添加到 Rust

¥Add wasm target to rust

SWC 支持两种 .wasm 文件。那些是

¥SWC supports two kinds of .wasm files. Those are

  • wasm32-wasi

  • wasm32-未知-未知

    ¥wasm32-unknown-unknown

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

¥In this guide, we will use wasm-wasi as a target.

安装 swc_cli

¥Install swc_cli

你可以通过执行以下操作为 SWC 安装基于 Rust 的 CLI

¥You can install a rust-based CLI for SWC by doing

cargo install swc_cli

配置 IDE

¥Configuring IDE

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

¥If you are going to use vscode, it's recommended to install rust-analyzer extension. rust-analyzer is a language server (opens in a new tab) for the rust programming language, which provides good features for code completion, code navigation, and code analysis.

实现简单的插件

¥Implementing simple plugin

创建一个项目

¥Create a project

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

¥SWC CLI supports creating a new plugin project.

运行

¥Run

swc plugin new --target-type wasm32-wasi my-first-plugin
# You should to run this
rustup target add wasm32-wasi

创建一个新插件,然后使用你首选的 Rust IDE 打开 my-first-plugin

¥to create a new plugin, and open my-first-plugin with your preferred rust IDE.

实现访客

¥Implementing a visitor

生成的代码有

¥The generated code has

impl VisitMut for TransformVisitor {
    // Implement necessary visit_mut_* methods for actual custom transform.
    // A comprehensive list of possible visitor methods can be found here:
    // https://rustdoc.swc.rs/swc_ecma_visit/trait.VisitMut.html
}

用于转换代码。性状 VisitMut (opens in a new tab) 支持修改 AST 节点,并且由于它支持所有 AST 类型,因此它有很多方法。

¥which is used to transform code. The trait VisitMut (opens in a new tab) supports mutating AST nodes, and as it supports all AST types, it has lots of methods.


我们将使用

¥We will use

foo === bar;

作为输入。从 SWC 在线运行 (opens in a new tab) 开始,你可以得到这段代码的实际表示。

¥as the input. From the SWC Playground (opens in a new tab), you can get actual representation of this code.

{
  "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 实现一个方法。你可以这样做

¥Let's implement a method for BinExpr. You can do it like

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); 调用。

¥Note that visit_mut_children_with is required if you want to call the method handler for children. e.g. visit_mut_ident for foo and bar will be called by e.visit_mut_children_with(self); above.

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

¥Let's narrow down it using the binary operator.

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!macro (opens in a new tab)

¥op!("===") is a macro call, and it returns various types of operators. It returns BinaryOp (opens in a new tab) in this case, because we provided "===", which is a binary operator. See the rustdoc for op! macro (opens in a new tab) for more details.

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

¥If we run this plugin, we will get

kdy1 === bar;

测试你的变换

¥Testing your transform

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

¥You can simply run cargo test to test your plugins. SWC also provides a utility to ease fixture testing.

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

¥You can easily verify the input and output of the transform.

test!(
    Default::default(),
    |_| visit_mut_pass(TransformVisitor), // Note: Updated to use visit_mut_pass instead of as_folder
    boo,
    r#"foo === bar;"#
);

然后,一旦运行 UPDATE=1 cargo test,快照就会更新。

¥Then, once you run UPDATE=1 cargo test, the snapshot will be updated.

你可以看看 Typescript Type Stripper 的真实夹具测试 (opens in a new tab)

¥You can take a look at the real fixture test for typescript type stripper (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)), // Note: Updated to use tuple syntax instead of chain!
        &input,
        &output,
    );
}

注意事项:

¥Things to note:

  • 提供给 testing::fixture 的 glob 是相对于 cargo 项目目录的。

    ¥The glob provided to testing::fixture is relative to the cargo project directory.

  • 输出文件是 output.js,它与输入文件存储在同一目录中。

    ¥The output file is output.js, and it's stored in a same directory as the input file.

  • test_fixture 驱动测试。

    ¥test_fixture drives the test.

  • 你可以通过将语法传递给 test_fixture 来确定输入文件的语法。

    ¥You can determine the syntax of the input file by passing the syntax to test_fixture.

  • 然后,你提供访问者实现作为 test_fixture 的第二个参数。

    ¥You then provide your visitor implementation as the second argument to test_fixture.

  • 然后提供输入文件路径和输出文件路径。

    ¥Then you provide the input file path and the output file path.

日志

¥Logging

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

¥SWC uses tracing for logging. By default, SWC testing library configures the log level to debug by default, and this can be controlled by using an environment variable named RUST_LOG. e.g. RUST_LOG=trace cargo test will print all logs, including trace logs.

如果需要,你可以使用 tracing 的 Cargo 功能删除插件的日志记录。参见 它的文档 (opens in a new tab)

¥If you want, you can remove logging for your plugin by using cargo features of tracing. See the documentation for it (opens in a new tab).

发布你的插件

¥Publishing your plugin

请参阅 插件发布指南

¥Please see plugin publishing guide