SWC插件开发:高性能代码注入与按需编译优化

作者:技术拾光时间:2026-06-29 08:26:00浏览:237

首先,开发 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 不会明确报错,仅静默失败。版本管理需格外谨慎。