Migrate ZIP library from JSZip to fflate
fflate-vs-jszip-benchmark.html
fflate-vs-jszip-benchmark-v2.html
Summary
Replace jszip with fflate for all ZIP generation. fflate is significantly faster, smaller, and actively maintained.
Motivation
We benchmarked both libraries in-browser under identical conditions (same input data, DEFLATE level 6, fixed UTC timestamp, 16 files across 8 directories):
- Speed — fflate's synchronous
zipSync consistently outperforms JSZip's generateAsync by a significant margin across all payload types (text, binary, repetitive, mixed) and file sizes (10 KB – 1 MB per file)
- Bundle size — fflate is ~8 KB minified+gzipped vs JSZip's ~100 KB
- Output correctness — both libraries produce valid, extractable ZIP files with identical compressed data blocks at the same compression level. The only structural difference is that fflate writes an extended-timestamp extra field (
0x5455, 9 bytes per entry) which is a strictly additive and standards-compliant improvement
Migration notes
API difference to be aware of — timestamps:
JSZip's date option is always interpreted as UTC. fflate's mtime behaves the same way. Pass new Date(Date.UTC(...)) explicitly rather than new Date(...) (local time) to ensure the encoded DOS timestamp is identical regardless of the user's timezone.
// ❌ Before (JSZip)
const zip = new JSZip();
zip.file('foo.txt', data, {
compression: 'DEFLATE',
compressionOptions: { level: 6 },
date: new Date(2000, 0, 1), // ⚠️ local time — shifts by timezone in JSZip
});
const out = await zip.generateAsync({ type: 'uint8array' });
// ✅ After (fflate)
const out = fflate.zipSync({
'foo.txt': [data, {
level: 6, // must be set per-entry; top-level option is ignored
mtime: new Date(Date.UTC(2000, 0, 1)), // explicit UTC
}],
});
Other gotchas:
level must be specified per entry in fflate — passing it to zipSync at the top level is silently ignored
crypto.getRandomValues() is capped at 65 536 bytes per call; if generating random binary test data, chunk calls accordingly
- fflate's
zipSync is synchronous and blocks the main thread for large inputs — use zip (async/worker-based) for files above a few MB if responsiveness matters
Acceptance criteria
Migrate ZIP library from JSZip to fflate
fflate-vs-jszip-benchmark.html
fflate-vs-jszip-benchmark-v2.html
Summary
Replace
jszipwithfflatefor all ZIP generation. fflate is significantly faster, smaller, and actively maintained.Motivation
We benchmarked both libraries in-browser under identical conditions (same input data, DEFLATE level 6, fixed UTC timestamp, 16 files across 8 directories):
zipSyncconsistently outperforms JSZip'sgenerateAsyncby a significant margin across all payload types (text, binary, repetitive, mixed) and file sizes (10 KB – 1 MB per file)0x5455, 9 bytes per entry) which is a strictly additive and standards-compliant improvementMigration notes
API difference to be aware of — timestamps:
JSZip's
dateoption is always interpreted as UTC. fflate'smtimebehaves the same way. Passnew Date(Date.UTC(...))explicitly rather thannew Date(...)(local time) to ensure the encoded DOS timestamp is identical regardless of the user's timezone.Other gotchas:
levelmust be specified per entry in fflate — passing it tozipSyncat the top level is silently ignoredcrypto.getRandomValues()is capped at 65 536 bytes per call; if generating random binary test data, chunk calls accordinglyzipSyncis synchronous and blocks the main thread for large inputs — usezip(async/worker-based) for files above a few MB if responsiveness mattersAcceptance criteria
jszipremoved from dependenciesfflateadded to dependenciesfflate.zipSync(orfflate.zipfor large async cases)Date.UTC(...)throughout