Compression in the browser
Compression runs in the browser, before a byte crosses the network. A raw phone photo typically shrinks 5–10×, which is bandwidth the user does not spend and storage you do not buy.
It runs in a worker on OffscreenCanvas, so the main thread never blocks —
scrolling stays smooth while a dozen photos encode.
What it costs your bundle
Section titled “What it costs your bundle”Measured with bun build --minify --splitting, which is what a real bundler
does. (Without --splitting the number is 1.38 MB and it is a lie — everything
lands in one chunk whether or not you ever reach it.)
| gzipped | who pays | |
|---|---|---|
| the compressor, eager | 4,363 B | everyone |
compressorjs, lazy |
4,994 B | Safari < 16.4, or a failed decode |
heic2any, lazy |
341,352 B | each HEIC — and only if the engine cannot decode it natively |
hash-wasm |
7,223 B | everyone. Tree-shakes cleanly; there is no alternative, since WebCrypto has no incremental digest |
That last column is the whole design. Only 4 KB is unconditional; the expensive pieces load when something actually needs them.
HEIC: native first
Section titled “HEIC: native first”Engines that can decode HEIC do it in about 136 ms. The WASM fallback takes roughly 1,700 ms — and costs 341 KB to fetch first. So the native path is tried first, and the fallback only runs where it must.
That is a 12× difference on the exact file type phones produce by default, so it is worth confirming on a real device rather than in a simulator.
The silent PNG fallback
Section titled “The silent PNG fallback”canvas.convertToBlob({ type: "image/webp" }) is allowed to ignore you.
WebKit 26.5 returns image/png when asked for WebP; Chromium does not. Nothing
throws, and the result still looks like an image.
Measured on an 11.9 MP photo: PNG 21.8 MB vs JPEG 3.8 MB — a 5.7× penalty,
and 24× against a well-tuned WebP. Worse, a file labelled .webp that is
actually a PNG breaks anything downstream that trusts the extension.
The rule this produced, and it generalises well beyond this SDK:
When it declines
Section titled “When it declines”Non-image inputs — video, PDF — skip compression entirely and upload raw. An image the engine cannot decode also uploads raw rather than failing the upload: the original arriving intact beats a clever error.