Skip to content

项目架构 ​

跨语言三层与 Rust 内部分层 ​

text
packages/imagemin
  imagemin 兼容 Interface、文件 I/O、JS 插件、错误上下文
          │ 私有 NativePluginDescriptor
          ▼
napi/imagemin
  Buffer 转换、AsyncTask 调度、Rust 错误映射
          │ Vec<u8> + typed options
          ▼
crates/imagemin (facade)
  descriptor registry 与稳定 re-export
          │
          ├─ imagemin-core:asset、格式、错误、开放 plugin trait、pipeline/统计
          ├─ imagemin-codec-png:Oxipng 与 OptiPNG-compatible path
          ├─ imagemin-codec-gif:GIF analysis、encode、metadata 与资源策略
          └─ imagemin-codec-svg:XML policy 与 SVGM

Rust Module 不知道 glob、路径和 Node.js。JS Module 不知道 codec FFI 和线程细节。N-API Adapter 保持薄,只负责语言 seam。

Node.js 与浏览器 Adapter ​

codec crate 现在有两个薄 runtime adapter:

text
packages/imagemin ── napi/imagemin ──┐
                                     ├─ crates/imagemin 与 codec crates
@imagemin-rs/wasm ─ wasm/imagemin-core┘

N-API Adapter 使用 AsyncTask 调度 CPU 工作;WASM Adapter 通过 wasm-bindgen 暴露 相同的可序列化 plugin descriptor,TypeScript 包负责初始化、浏览器自定义插件与 Uint8Array 转换。依赖外部可执行 sidecar 的 codec 仍只支持 Node.js。

为什么使用 AsyncTask ​

图片压缩是 CPU 密集任务。JavaScript async 只能改变返回形式,不能防止同步 native 函数阻塞事件循环。原生插件因此在 AsyncTask::compute 中执行,并在 resolve 阶段创建 JavaScript 结果。

插件融合 ​

原生插件工厂返回普通函数,因此和 imagemin 插件兼容。内部 WeakMap 保存 descriptor:

text
[native A, native B, JS C, native D]
        │                    │
        └─ AsyncTask #1      └─ AsyncTask #2
                  JS C 在中间严格执行

无法识别 descriptor 时只损失融合性能,不影响正确性。

完整 SVGO 配置允许 JavaScript 函数,不能序列化成 descriptor,因此 svgo() 保持 JS 兼容 seam;边界清楚的 svgm() 才进入原生融合。这个区别防止为了 fast-path 静默改变 plugin 顺序或丢弃参数。

crate 粒度 ​

Phase 0 先用单一 Rust crate 验证 seam;在 codec 依赖和测试边界稳定后,workspace 已拆为薄 facade、无 codec 依赖的 imagemin-core,以及按 PNG/GIF/SVG 聚合的 codec crates。粒度按依赖、 许可证、资源策略和构建边界决定,而不是机械地为每个函数建 crate。PNG 的两个入口共享同一 engine,所以仍在一个深 crate;GIF crate 内再按 analysis/encode/metadata 拆 Module。

完整决策见仓库内的 ADR 0001 与 ADR 0008。

Released under the MIT License.