Compress anything.
Everywhere.
One typed API over nine codecs — gzip, deflate, zlib, zstd, lz4, snappy, brotli, lzma, bzip2 — plus a ZIP container, all from a single WebAssembly engine. Native speed where it wins, libdeflate density where it matters. Node 18+, Bun, and the browser.
import { gzip, zstd, compress, decompress } from 'zipkit'; const gz = await gzip(bytes); // balanced default const small = await zstd(bytes, { mode: 'ratio' }); // auto-detects the format on the way back const original = await decompress(small);
Every codec you'd reach for,
one tiny API
Best-in-class C libraries compiled into a single Wasm engine, wrapped in a small, typed, tree-shakeable TypeScript surface.
gzip, zstd, brotli — or compress() / decompress() for generic dispatch with auto-detect.mode: 'speed' | 'balanced' | 'ratio'. No tuning maze — each codec picks a sensible level. level is still there when you need exact control.mode: 'ratio' uses libdeflate for denser gzip than native zlib..wasm. Consumers never need Emscripten — it just loads.store, deflate, or denser zstd (method 93). ZIP64 past 4 GB / 65,535 entries, filtered extraction, per-entry metadata.TransformStreams that drop into any pipeThrough(). gzip/zlib/deflate ride the platform's native CompressionStream — true incremental streaming, zero Wasm.ZKP1 container. On 34 MB of logs, parallel gzip is 3.4× faster than native and denser too.zipkit/workers — one worker per core, an AbortSignal you can honor, and an inline fallback where threads aren't available.Accept-Encoding and serves the best the client supports — brotli → zstd → gzip → deflate.encodeImage (QOI) beats brotli on raw pixels, and encodeFrames (frame-delta + zstd) halves plain zstd on temporal frames.pack() tries brotli, lzma, bzip2, and zstd-ultra, keeps the densest, and tags it so unpack() can reverse it.sideEffects: false, and byte-only codecs (Uint8Array). Same import on Node 18+, Bun, and the browser.Nine codecs,
one set of patterns
Named when you know, generic when you don't
Import the codec by name for tree-shaking, or dispatch generically with compress(). On the way back, decompress() sniffs the header and routes itself.
- ✓ Every codec is a
compress/decompresspair - ✓ Async helpers lazy-load the engine on first use
- ✓
decompress()auto-detects gzip, zlib, and zstd - ✓ Bytes in, bytes out — strings convert via
strToU8/strFromU8
import { brotli, unbrotli, compress, decompress } from 'zipkit'; // Named — tree-shakeable, async, lazy engine const c = await brotli(bytes, { mode: 'ratio' }); const back = await unbrotli(c); // Generic dispatch + auto-detect const z = await compress(bytes, 'zstd', { level: 19 }); const orig = await decompress(z); // sniffs zstd
One knob, not a tuning maze
Pick an intent — speed, balanced, or ratio — and ZipKit chooses a sensible level and path for each codec. Reach for level only when you want exact control.
- ✓
speed— lower levels and native runtime paths - ✓
balanced— the default; practical levels, adaptive dispatch - ✓
ratio— denser paths, libdeflate for gzip/deflate/zlib - ✓ Out-of-range
levelvalues are clamped, never rejected
// Fast: lowest latency, native paths where they win await gzip(bytes, { mode: 'speed' }); // Balanced: the default — good speed, good ratio await zstd(bytes); // Ratio: smallest output, libdeflate density await zstd(bytes, { mode: 'ratio' }); // Or pin an exact level await brotli(bytes, { level: 11 });
ZIP archives, optionally with zstd
Build and read standard ZIPs in memory. Mix methods per entry, attach metadata, and filter on the way out — or opt into zstd for much denser archives between ZipKit-aware peers.
- ✓
store/deflateinteroperate with every ZIP tool - ✓
filterruns on metadata, before any decompression - ✓ ZIP64 kicks in automatically past 4 GB / 65,535 entries
- ✓ Per-entry
mtime,unixPermissions, and comments
import { zip, unzip } from 'zipkit'; const archive = await zip([ { name: 'index.html', data: html }, { name: 'app.js', data: js, method: 'zstd' }, { name: 'logo.png', data: png, method: 'store' }, ]); // Extract only what you need const files = await unzip(archive, { filter: (e) => e.name.endsWith('.js'), });
Drops into any stream
Web-standard TransformStreams compose with fetch bodies, files, and sockets — anything that speaks Web Streams. gzip/zlib/deflate stream incrementally on the platform's native engine.
- ✓ Native
CompressionStreamfor gzip/zlib/deflate - ✓ Constant memory — no buffering for unbounded input
- ✓ Other codecs buffer and compress on flush, still composable
- ✓ Standard output any conformant decompressor can read
import { compressionStream } from 'zipkit/streams'; const res = await fetch('/big.json'); await res.body .pipeThrough(compressionStream('gzip')) .pipeTo(dest); // true incremental streaming, zero Wasm
See every codec on your data
The CLI ships with the library. zipkit bench runs every codec on a real file and prints ratio and timings, so you can pick the right trade-off for your payload — not a synthetic one.
- ✓
compress/decompresswith--modeand--codec - ✓
zip/unzipandinfoto inspect any file - ✓ Auto-detects the format on decompress
- ✓ Runs on both Node and Bun
$ zipkit bench big.log bench big.log (97.0 KB) codec ratio size comp decomp gzip 5.1% 5.0 KB 0.3ms 0.1ms zstd 5.6% 5.5 KB 0.1ms 0.1ms brotli 3.4% 3.3 KB 160ms 0.1ms lz4 12.9% 12.6 KB 0.1ms 0.0ms lzma 3.8% 3.7 KB 5.7ms 0.3ms ✓ all roundtrips byte-identical
Install & start compressing
Requires Node.js v18+ or Bun. The Wasm engine ships with the package — no Emscripten, no native build step.
Prefer the terminal? npm install -g zipkit · then zipkit compress data.json --mode ratio
Best-in-class C libraries,
one Wasm engine
Each codec is the reference implementation, statically linked into a single ~1.4 MB module — loaded once, cached for the page or process lifetime.
Ready to compress
everything?
Nine codecs, a ZIP container, streams, workers, and middleware — in one tiny typed API that runs the same on Node, Bun, and the browser.