SWC插件开发:高性能代码注入与按需编译优化
首先,开发 SWC 插件必须明确核心逻辑的编写语言。所有自定义转换操作(如代码注入、条件编译等)均需使用 Rust 编译为动态库。JavaScript 或 TypeScript 仅承担配置与接口调用的辅助角色,无法实现核心逻辑。 当执行 swc 命令出现 unknown plugin "my-inj
首先,开发 SWC 插件必须明确核心逻辑的编写语言。所有自定义转换操作(如代码注入、条件编译等)均需使用 Rust 编译为动态库。JavaScript 或 TypeScript 仅承担配置与接口调用的辅助角色,无法实现核心逻辑。
当执行 swc 命令出现 unknown plugin "my-inject" 或 plugin not found 报错时,常见原因有两种:一是 Rust 代码未被编译为 .so、.dylib 或 .dll 文件;二是 SWC 配置中的路径未指向该二进制文件。
在决定编写自定义插件前,建议评估是否存在框架无法覆盖的定制需求。社区现有插件(如 @swc/plugin-transform-react-jsx)足以满足大部分常见场景,例如为 React 组件添加 console.log 或注入样式。自定义插件通常用于处理环境变量驱动的调试代码剔除、特定函数自动包装、或团队内部 DSL 编译流程等场景。
动手前需确认以下三个前提:
- 编译:使用
cargo build --release将 Rust 代码编译为动态库。 - 配置:
.swcrc文件的plugins字段必须指定动态库的绝对路径,相对路径或包名均无效。 - 签名:插件入口函数必须采用
#[no_mangle] pub extern "C" fn transform(...)格式,以对齐 SWC 官方 ABI。
按需编译的核心:在 AST 节点上做文章
SWC 插件的“按需”编译并非仅通过文件名或目录路径过滤,而是深入到抽象语法树(AST)层面的实时决策。例如,需要仅对带有 @env("prod") 特殊注释的函数执行死代码删除时,VisitMut 实现必须检查 FnDecl AST 节点上是否挂载了对应 Comment。在插件外层使用 if (filePath.includes("prod")) 这种粗糙判断无法满足要求。
性能方面需注意:SWC 默认启用增量编译,但若插件在 visit_mut_program 中设置全局状态缓存(如 HashMap)且未处理好 Span 的跨文件唯一性,可能导致缓存污染,二次构建时误删应保留的代码。
实操经验建议:
- 缓存 key 优先组合
Span和FileName,避免仅依赖Span。 - 避免在插件内直接读取外部文件(如
fs.readFileSync),否则会阻塞并行编译线程。 - 注入 JSX 元素时注意区分
JsxElement和JsxFragment:后者无opening和closing结构,容易遗漏。
代码注入:这两个坑一旦踩进去,损失不小
简单地在 Program 顶层 unshift 一个 ExprStmt 会导致 source map 错位、HMR 失效、Tree Shaking 破坏等后果。SWC 的注入逻辑必须遵循 JavaScript 作用域和执行时序。常见错误是在 visit_mut_module_decl 中插入 ImportDecl,导致 import 语句出现在 export default 之后,违反 ES 模块规范,浏览器抛出 Unexpected token 'export' 错误。
正确做法如下:
- 注入 import 语句应在
visit_mut_module_items阶段进行,并确保插入所有ModuleItem::ModuleDecl的最前面。 - 注入运行时代码(如初始化逻辑)应封装为 IIFE,并插入
Program.body中第一个Stmt之前,而非末尾。 - 若注入内容包含模板字符串或 JSX,务必使用
ast::Expr::Tpl或ast::Expr::JsxElement构造 AST 节点,避免拼接字符串后二次解析,否则原始Span信息丢失将严重影响调试。
本地调试:少走弯路的一些经验
SWC 插件运行于独立进程或 WASM 环境,标准输出默认被重定向或丢弃,因此 console.log 无法直接输出调试信息。有效调试方法只有两种:写日志文件,或使用 gdb 等调试器 attach 到 swc 进程。
有时插件在 CI 正常,本地使用 swc src -d dist 却无响应,大概率是 .swcrc 未被正确加载。SWC 默认仅在执行命令的当前工作目录下查找 .swcrc,不会向上遍历父目录。但在 Vite 或 Rspack 集成场景中,配置通常通过 API 显式传入,绕过了文件查找逻辑。因此本地未生效不代表插件问题。
本地调试实用技巧:
- 加上
--config <.swcrc>参数强制指定配置路径,可确认配置加载是否正常。 - 在插件入口函数第一行加入
std::fs::write("/tmp/swc-plugin-debug.log", "start").ok();,若文件生成则证明插件已被调用。 - 先使用
swc --dump-ast input.ts查看原始 AST 结构,再决定实现哪个 visitor 方法,比盲目猜测高效。
最后需注意:Rust 插件编译产物与 Node.js 运行时的 ABI 版本强绑定。使用 swc_core v0.87 编译的插件可能无法加载到 @swc/core@1.4.0 环境中,此类兼容性问题 SWC 不会明确报错,仅静默失败。版本管理需格外谨慎。
