WASM WebP for batches
WebKit cannot encode WebP from a canvas, so on Safari and every iOS browser the
compressor falls back to JPEG. Online converters produce WebP in those same
browsers anyway. They ship their own encoder, compiled to WebAssembly, and
never touch canvas.toBlob.
We could too. Here is the measurement, because the answer changes depending on how many files are coming.
The numbers
Section titled “The numbers”Same 4.9 MP source, same target dimensions (2560 px), same quality (82).
@jsquash/webp — the same libwebp build that would run in the browser:
| size | time | |
|---|---|---|
| JPEG, native encoder — what WebKit gives you today | 621 kB | 109 ms |
| WebP, WASM encoder | 364 kB | 489 ms |
| −41.4 % | 4.5× slower |
And the encoder itself, fetched once and then cached:
| file | size |
|---|---|
webp_enc_simd.wasm |
337 kB |
webp_enc.wasm (no SIMD) |
275 kB |
One photo: not worth it
Section titled “One photo: not worth it”You save 257 kB and download a 337 kB encoder. Net loss on bytes, plus 380 extra milliseconds. For a single upload the JPEG fallback is simply the right answer.
Thirty photos: worth it, and by a lot
Section titled “Thirty photos: worth it, and by a lot”This is the case that matters, and it is the common one — someone picking a camera roll selection.
| saved | 30 × 257 kB = 7.5 MB |
| encoder cost | 337 kB, once |
| net data saved | ≈ 7.2 MB |
| extra encode time | 30 × 380 ms ≈ 11 s on a laptop |
Break-even is the second photo. After two images the encoder has paid for itself in bytes, and everything after that is pure saving.
The time cost mostly disappears, and exactly when it matters
Section titled “The time cost mostly disappears, and exactly when it matters”11 seconds of extra encoding sounds bad until you notice that encoding and uploading can overlap: photo N+1 encodes while photo N is still uploading.
Do the arithmetic on a realistic uplink. A 364 kB WebP on 4G at ~250 kB/s takes 1.45 s to upload, against 489 ms to encode. The encode finishes long before the upload does, so in a pipelined batch the WASM encoder is free — it runs in dead time the network was going to waste anyway.
And the slower the connection, the more true that gets:
| uplink | 7.2 MB saved ≈ | encode overhead | verdict |
|---|---|---|---|
| 3G / congested cellular (~60 kB/s) | 2 min | hidden behind uploads | big win |
| 4G (~250 kB/s) | 29 s | hidden behind uploads | clear win |
| fast wifi (~10 MB/s) | 0.7 s | ~11 s, partly exposed | not worth it |
So the rule is not really “how many files” — it is “is the network slower than the encoder?” For a batch on cellular, always yes. On fast wifi, no, and there the bytes did not matter anyway.
Why the SDK is the right place to decide this
Section titled “Why the SDK is the right place to decide this”The SDK already knows everything the decision needs, before a single byte moves:
- how many files —
compressImages()takes the array; - the total size — it has hashed them;
- whether the engine can encode WebP natively — it already probes this to choose the fallback;
- the connection, via
navigator.connectionwhere available.
That is the whole argument for putting this in an SDK rather than leaving it to each app: the caller says “here are 30 photos”, and the library is the only layer that can see the batch, the engine and the link at the same time.
What it does not change
Section titled “What it does not change”Delivery is WebP either way. The server re-encodes every variant, so this
only ever affects upload bytes and the format of the archived
original. Nobody’s page gets faster — the user’s upload gets cheaper, which
on a metered phone plan is the thing they actually feel.