实现插件
¥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 ofchain!(
. You can replace allchain!(
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 nowvisit_mut_pass
and returnsimpl VisitMut + Pass
instead ofimpl VisitMut + Fold
. -
使用
Program.apply
和Program.mutate
将 Pass 应用于程序:¥Use
Program.apply
andProgram.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()
withnoop_pass()
. The new function lives inswc_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
。例如 foo
和 bar
的 visit_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