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.apply
和Program.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
。
例如,foo
和 bar
的 visit_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)。
发布你的插件
请参阅 插件发布指南