How Create ZIP is built
These are the engineering notes for Create ZIP: the technologies it is built on, what each one is, and how it is used in the tool.
Tech used
The format: ZIP
A ZIP file is a sequence of entries followed by a central directory — an index at the end of the file listing every entry’s name, sizes, timestamp, and flags. Each entry is normally deflate-compressed (the LZ77 + Huffman scheme also used by gzip) or stored (kept verbatim). One per-entry detail matters more than it looks: the general-purpose bit 11, the “language encoding flag,” which declares that the entry’s filename is UTF-8.
@zip.js/zip.js: ZipWriter + BlobWriter
The archive is built with @zip.js/zip.js, a client-side ZIP library. A ZipWriter wrapped around a BlobWriter('application/zip') collects entries; each input File is fed in with writer.add(name, new BlobReader(file)) — BlobReader streams the file’s bytes into the writer — and writer.close() resolves to the finished archive as a single Blob. No compression level is set, so zip.js’s default deflate applies.
UTF-8 filenames (general-purpose bit 11)
new ZipWriter(…, { useUnicodeFileNames: true }) sets bit 11 on every entry, so non-ASCII names — Japanese, emoji — extract correctly in Windows Explorer instead of turning into mojibake. (So many archives in the wild don’t set this flag that there’s a sibling tool, Fix ZIP Filenames, just to repair them.)
zip.js’s internal Web Workers
A Web Worker is a script that runs on its own thread so heavy work doesn’t freeze the page. zip.js runs deflate compression in its own Web Workers, spun up from blob: URLs — invisible to the calling code, but visible in the Content-Security-Policy, which must allow worker-src blob: and script-src blob: for compression to work.
Shell: Astro, Preact, Service Worker
The static Astro + Preact island shell and the Service-Worker PWA are the same across these tools (introduced in the HEIC notes); the archiving runs in that one island.
Implementation & operational notes
Built in memory, saved with a Blob URL. The BlobWriter accumulates the whole archive in memory before close() resolves; the result is downloaded by creating an object URL and clicking a synthetic <a download="archive.zip">. There’s no File System Access API (showSaveFilePicker) and no streaming to disk, so the practical ceiling on archive size is available memory.
deflate regardless of input. With the default settings every entry is deflated, including already-compressed inputs like JPEG or MP4 — which won’t shrink, and cost CPU to try. There’s no store-vs-deflate toggle.
Folder structure and duplicate names. When a folder is dropped, each file’s webkitRelativePath is used as the entry name, so the directory tree is preserved inside the archive; duplicate names are disambiguated as a.txt, a (1).txt, a (2).txt.
Ingestion. Files arrive by file picker, whole-page drag-and-drop, or clipboard paste — a page-level drop zone dispatches a filesDropped event that the island listens for.
Try it / source
- Tool: Create ZIP
- Source: github.com/GeppettoAndRomero