Online file compression and extraction sound like server-side work: upload files, process them remotely, then download the result. But for ZIP archives, modern browsers can handle most everyday workflows directly. Files can stay on the user device while the page reads local data, runs the archive algorithm, and creates download links.
ToolGarden ZIP Compressor and ZIP Extractor follow that model. The compressor turns multiple File objects into one ZIP Blob. The extractor reads ZIP bytes, parses entries, rebuilds a folder tree from paths, and lets users download individual files. No API route receives the file contents.
The Data Flow
Compression:
File input / drag drop
-> File API reads Blob data
-> normalize archive paths
-> fflate zipSync
-> Blob(application/zip)
-> URL.createObjectURL download
Extraction:
ZIP File
-> arrayBuffer
-> inspect central directory for filename encoding
-> fflate unzipSync
-> filter unsafe paths
-> build folder tree
-> create Blob download for each fileThe important boundary is that the page can read files the user explicitly chooses, but it does not need to send those bytes to a server. The server delivers JavaScript, CSS, and static assets; the actual work happens inside the browser process.
Why ZIP, Not RAR or 7z?
ZIP has mature JavaScript implementations for both creation and extraction, and the output works almost everywhere. Windows, macOS, Linux, and mobile operating systems can usually open a .zip without extra software. For a web utility, that makes ZIP the reliable default.
RAR and 7z are different. Extraction can be evaluated separately, but archive creation is not as open, lightweight, or browser-friendly as ZIP creation. Rather than shipping fragile format support, the compressor exports ZIP only and the extractor focuses on ZIP first.
Compression: Multiple Files to One ZIP Blob
The compression page should stay thin: selected files, output filename, compression level, and button state. The real implementation belongs in a utility module such as lib/utils/zip.ts, where a function receives filename + blob entries and returns a discriminated result instead of touching React state.
type ZipCompressionOutcome =
| {
ok: true;
blob: Blob;
filename: string;
fileCount: number;
originalSize: number;
outputSize: number;
durationMs: number;
}
| { ok: false; code: 'empty_selection' | 'empty_file' | 'zip_failed' };Before compression, normalize archive paths. Folder uploads may expose webkitRelativePath, while normal file selection only has file.name. Replace backslashes, drop empty segments, remove . and .., and rename duplicate paths so later files do not silently overwrite earlier ones.
fflate zipSync handles the archive generation. Compression levels range from 0 to 9: 0 is closer to packaging without compression, while 9 may save more space at the cost of time. A default of 6 is a practical speed-to-size balance.
Extraction: Entries First, Folder Tree Second
The extractor should not flatten every file into one long list. ZIP entries contain paths such as docs/readme.txt and docs/assets/logo.png; splitting those paths gives a natural folder tree. Folder rows can expand and collapse, while file rows show size and a download button.
interface ZipExtractedEntry {
path: string;
name: string;
size: number;
blob: Blob;
}
interface TreeNode {
name: string;
path: string;
children: Map<string, TreeNode>;
entry?: ZipExtractedEntry;
}Keep extraction and display concerns separate. The utility function returns entries; the React component builds TreeNode data and owns interaction state such as expanded folders. That keeps the archive parser testable and the UI easier to change.
Why Chinese Filenames Become Garbled
Many ZIP filenames are UTF-8, but not every archive sets the UTF-8 flag. Older archives from Chinese Windows environments often store filename bytes as GBK or GB18030. If the unzip library interprets those bytes as Latin-1 or another fallback, Chinese names become unreadable.
A practical fix is to inspect raw filename bytes in the ZIP central directory. When an entry does not have the UTF-8 filename flag, decode those bytes with GB18030 and map that name back to the Latin-1 name returned by the unzip library. Standard UTF-8 ZIP files remain untouched, while legacy Chinese filenames display correctly.
Downloads Use Blob URLs
Both compression and extraction can download from Blob URLs. The compressed result is an application/zip Blob. Each extracted file is an application/octet-stream Blob. The page calls URL.createObjectURL, assigns it to an anchor href, and sets the download filename.
Blob URLs are temporary browser resources. A long-running tool should revoke them when clearing results or unmounting the component, especially when users process multiple large archives in one session.
Security and Reliability Boundaries
- Zip Slip: archive paths may contain ../ segments, so clean paths before showing or downloading them.
- Zip bombs: a tiny archive may expand into huge output, so warn about large files and add entry-count or total-size limits in stricter deployments.
- Encrypted ZIP: password flows and compatibility are complex; unsupported encrypted archives should fail clearly.
- Memory pressure: hundreds of MB or thousands of files can slow down or crash a browser tab.
- Filename collisions: compression should rename duplicate archive paths instead of overwriting silently.
Where the Code Belongs
In ToolGarden, ZIP tools follow the Harness Engineering pattern: metadata in the registry, copy in messages, archive logic in lib/utils/zip.ts, and page components limited to state, file selection, folder expansion, and download clicks. Home cards, the Other tools menu, breadcrumbs, SEO metadata, sitemap, and llms files all derive from the same metadata.