开发一个插件
配置环境
安装需要的工具链
因为插件使用 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
。
例如,处理 foo
和 bar
的 visit_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