Skip to content

Architecture ​

Three language layers ​

text
packages/imagemin
  imagemin-compatible interface, file I/O, JS plugins, error context
          │ private NativePluginDescriptor
          ▼
napi/imagemin
  Buffer conversion, AsyncTask scheduling, Rust error mapping
          │ Vec<u8> + typed options
          ▼
crates/imagemin (facade)
  descriptor registry and stable re-exports
          │
          ├─ imagemin-core: assets, formats, errors, plugin trait, pipeline/stats
          ├─ imagemin-codec-png: Oxipng and OptiPNG-compatible paths
          ├─ imagemin-codec-gif: analysis, encoding, metadata, resource policy
          └─ imagemin-codec-svg: XML policy and SVGM

Rust modules do not know about globs, paths, or Node.js. JavaScript modules do not know about codec FFI or thread details. The N-API adapter remains a thin language seam.

Node.js and browser adapters ​

The codec crates have two thin runtime adapters:

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

The N-API adapter schedules CPU work with AsyncTask. The WASM adapter exposes the same serializable plugin descriptors through wasm-bindgen, while the TypeScript package handles initialization, custom browser plugins, and Uint8Array conversion. Codecs that require executable sidecars remain Node-only.

Why AsyncTask ​

Image compression is CPU-intensive. JavaScript async changes the return shape but cannot stop a synchronous native function from blocking the event loop. Native plugins therefore execute in AsyncTask::compute and construct JavaScript results during resolve.

Plugin fusion ​

Native factories still return ordinary imagemin-compatible functions. A private WeakMap stores their descriptors:

text
[native A, native B, JS C, native D]
        │                    │
        └─ AsyncTask #1      └─ AsyncTask #2
                  JS C runs strictly between them

Failure to recognize a descriptor only loses fusion performance; it does not change correctness. Full SVGO configuration can contain JavaScript functions and cannot be serialized, so svgo() stays at the JavaScript compatibility seam while the closed svgm() profile participates in native fusion.

Crate boundaries ​

The Rust workspace is divided by dependency, license, resource-policy, and build boundaries rather than one crate per function. A thin facade re-exports the codec-independent core and grouped PNG, GIF, and SVG codec crates.

See ADR 0001 and ADR 0008 for the complete decisions.

Released under the MIT License.