The "Dependency Hell" of Image Processing
If you've ever deployed a Node.js app that uses sharp to AWS Lambda or Alpine Linux, you know the pain.
"Error: 'vips/vips8' not found"
"Error: libvips.so.42: cannot open shared object file"
sharp is an incredible library—it's fast and the industry standard. But it relies on libvips, a C++ library that often requires complex OS-level dependencies (apt-get install, dynamic linking, etc.). This makes "Write once, run anywhere" feel like a lie when you move from macOS to Linux production environments.
So, I decided to fix it using Rust.
Meet lazy-image 🦀
I built lazy-image, a next-generation image processing engine for Node.js.
It is powered by Rust (via NAPI-RS) and uses statically linked binaries. This means:
-
Zero System Dependencies: No
libvipsinstallation needed. Noapt-get. NoLD_LIBRARY_PATH. Justnpm install. - Memory Safety: The core logic is written in Rust, protecting your server from memory corruption bugs common in C/C++ libraries when processing user uploads.
- Smaller Binaries: Instead of downloading a massive dependency chain, it uses platform-specific packages (~6-9MB).
Performance: Is it actually fast?
Yes. In fact, for many web-optimization tasks, it beats the competition.
Here is a benchmark result comparing lazy-image vs sharp (using mozjpeg + libvips):
| Format | lazy-image | sharp | Difference |
|---|---|---|---|
| JPEG Size | 15,790 bytes | 17,495 bytes | -9.7% Smaller ✅ |
| Pipeline Speed | 193ms | 273ms | 1.41x Faster ⚡ |
| PNG → AVIF | 4,773ms | 11,652ms | 2.44x Faster ⚡ |
(Tested with 66MB PNG input, resized to 800px)
Why is it smaller?
I integrated mozjpeg by default, which uses advanced trellis quantization and scan optimization to shave off file size without losing visual quality.
Why is it faster?
For format conversions (like PNG to WebP) without resizing, lazy-image uses a Copy-on-Write (CoW) architecture. It avoids intermediate buffer allocations, making it significantly more memory-efficient and faster for batch processing pipelines.
How to use it
It's designed to be a drop-in replacement for your current workflow.
Installation
npm install @alberteinshutoin/lazy-image
Basic Usage
const { ImageEngine } = require('@alberteinshutoin/lazy-image');
// Memory-Efficient: Reads directly from file (bypassing Node.js heap)
await ImageEngine.fromPath('input.png')
.resize(800) // Resize to 800px width (auto height)
.rotate(90) // Rotate
.grayscale() // Apply filter
.toFile('output.jpg', 'jpeg', 85);
Batch Processing
You can even process multiple images in parallel with a single engine definition:
const engine = ImageEngine.fromPath('template.jpg')
.resize(800)
.webp();
// Uses all CPU cores by default
await engine.processBatch(['img1.jpg', 'img2.jpg'], './output', 'webp');
Give it a try!
I built lazy-image to solve my own deployment headaches, but I think it can help many of you who are tired of wrestling with C++ dependencies in Node.js.
It supports JPEG, PNG, WebP, and AVIF.
Check it out on GitHub, and let me know what you think! I'm looking for feedback on the Rust/NAPI implementation.
👉 GitHub: https://github.com/albert-einshutoin/lazy-image
📦 npm: https://www.npmjs.com/package/@alberteinshutoin/lazy-image
Top comments (0)