Skip to content
文档
插件
ECMAScript
快速开始

开发一个插件

配置环境

安装需要的工具链

因为插件使用 rust 编写并且要构建为 .wasm 文件,所以你需要安装 rust 工具链和 wasm target。

安装 rust

你可以跟随 官方 rust 网站的 “安装 Rust” 页面 (opens in a new tab) 中的介绍

将 wasm target 添加到 rust

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

  • wasm32-wasi
  • wasm32-unknown-unknown

在这个指引中,我们将会使用 wasm-wasi 作为一个 target。

安装 swc_cli

你可以通过以下指令安装一个基于 rust 的 SWC CLI

cargo install swc_cli

配置 IDE

如果你打算使用 vscode,推荐安装 rust-analyzer 扩展。 rust-analyzer 是为 rust 编程语言开发的 language server (opens in a new tab),可以为代码补全、导航和分析提供很好的支持。

实现简单的插件

创建一个项目

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

执行以下命令

swc plugin new --target-type wasm32-wasi my-first-plugin
# 你应该运行该命令
rustup target add wasm32-wasi

创建好一个 plugin 之后,用你喜欢的 rust IDE 打开 my-first-plugin

实现一个 visitor

生成的代码有以下内容

impl VisitMut for TransformVisitor {
    // 为自定义转换实现一个必要的 visit_mut_* 方法。
    // 可用的 visitor 方法列表可以在这里查看:
    // https://rustdoc.swc.rs/swc_ecma_visit/trait.VisitMut.html
}

以上内容用来转换代码。 VisitMut (opens in a new tab) 支持修改 AST 节点,因为它支持所有 AST 类型,所以它有很多方法。


我们将会使用以下代码作为输入。

foo === bar;

SWC 演练场 (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); 调用的。

让我们用二元运算符来缩小范围。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("kdy1".into(), e.left.span()).into());
        }
    }
}

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

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(),
    |_| as_folder(TransformVisitor),
    boo,
    r#"foo === bar;"#,
    r#"kdy1 === bar;"#
);

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| chain!(tr(), properties(t, true)),
        &input,
        &output,
    );
}
 

Things to note:

  • The glob provided to testing::fixture is relative to the cargo project directory.
  • The output file is output.js, and it's stored in a same directory as the input file.
  • test_fixture drives the test.
  • You can determine the syntax of the input file by passing the syntax to 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 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.

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

Last updated on August 21, 2022