This library depends on the Promise, the TypedArray, the Streams APIs and these ones optionally:
This library works fully with the latest versions of Chrome, Firefox, Safari, Microsoft Edge, Node.js, Deno, and Bun. The minimum supported versions depend on the imported build, cf. the Library variants and Bundle size sections:
| Build | Chrome, Edge | Firefox | Safari | Node.js | Deno | Bun |
|---|---|---|---|---|---|---|
WebAssembly builds: index.js (default), lib/zip-core-wasm.js,
dist/zip.min.js, dist/zip-fs.min.js |
76 | 102 | 15.1 | 18 | 1.0 | 0.7 |
"native" JavaScript builds: index-native.js, lib/zip-core-native.js,
dist/zip-native.min.js, dist/zip-fs-native.min.js |
76 | 102 | 14.1 | 18 | 1.0 | 0.7 |
"core" builds: lib/zip-core.js, lib/zip-core-reader.js,
lib/zip-core-writer.js, lib/zip-core-custom.js,
dist/zip-core.min.js, dist/zip-fs-core.min.js |
80 | 113 | 16.4 | 18 | 1.19 | 1.3.3 |
"deflate-raw" format is unavailable. When neither is usable, e.g. with the
"core" builds on Chrome 80 to 102, zip.js wraps raw deflate data in the "gzip" format
as a last resort, and stores entries uncompressed when writing data without any usable
implementation. The embedded WebAssembly module uses bulk memory operations, hence the higher
Safari requirement of the WebAssembly builds compared to the "native" JavaScript builds.Blob reading methods, i.e.
Chrome 76, Firefox 102, Safari 14.1, and Node.js 18, with any build. A worker
script embedding the codecs, passed via workerURI or created via
createWorker, also lifts the requirements of the "core" builds.TransformStream, the WebAssembly builds work down to
Firefox 79, and the "native" JavaScript builds down to Firefox 72. It only patches the scope
where it is loaded, so running the codecs in the web workers requires a worker script importing
the polyfill before the worker script of zip.js, cf. createWorker. Otherwise the
worker script throws when it is evaluated and the codecs run in the main scope.signal option of pipeTo(): aborting operations
in progress has no effect on these versions, the data read or written remains correct.level values require an embedded deflate
implementation, they are not available with the "core" builds.crypto.getRandomValues, exposed globally since version 19 in Node.js. Reading
encrypted entries does not require the Web Crypto API.'wasm-unsafe-eval' source ('wasm-eval' in extensions on Chromium 102 and
older). Without it, zip.js falls back on the Compression Streams API. Use the "native" builds
otherwise.
zip.js validates untrusted input by default. The filenames returned by getEntries() are
checked against path traversal: names containing .. segments, absolute paths, drive letters
or UNC prefixes are rejected with an ERR_UNSAFE_FILENAME error. The data of an entry cannot
be read outside the bounds of the zip file. Optional checks harden reading further: the
checkOverlappingEntry option detects entries sharing bytes with other entries, the
checkCrc32 option verifies the CRC-32 checksum of the extracted data, and
strictness: "strict" detects duplicate filenames, ambiguous end of central directory records,
and local file headers contradicting the central directory. The filename validation can be relaxed or
disabled with the filenameValidation option; an application resolving paths from
entry.filename, e.g. to write extracted files to disk, relies on it staying enabled.
Some checks remain the responsibility of the application:
uncompressedSize property of the
entries and enforce a budget before getting the data.getEntriesGenerator() to enumerate the entries
with an upper bound.normalizeFilename option.filename is validated. The rawFilename and
rawComment properties expose the stored bytes as-is, and the global
comment of the zip file is raw bytes too.symlink property, is never
validated: it can be absolute or escape the extraction directory with .. segments.
It must be checked before being used to resolve a file.Warning: Make sure that the code is parsed in UTF-8, e.g. served with the HTTP header
"Content-Type: application/javascript; charset=utf-8", otherwise filenames might be corrupted
when reading zip files.
You can get the code via:
import * as zip from "jsr:@zip-js/zip-js";
$ npm install @zip.js/zip.js
import * as zip from "@zip.js/zip.js";
const zip = require("@zip.js/zip.js");
Note: import * as zip imports the whole library. See the
Bundle size section below to reduce the footprint of zip.js in your
application.
You can also install manually the library by adding zip.min.js from the
/dist
directory in your project. Then, include zip.min.jsin your HTML page:
require(["/library_path/zip.min.js"], zip => {
// ...
});
<meta charset="utf-8"> <!-- make sure the script is parsed in UTF-8 -->
...
<script type="text/javascript" src="/library_path/zip.min.js"></script>
<script>
// the zip API is in the `zip` global variable
// ...
</script>
zip.js is tree-shakeable: import named identifiers, e.g.
import { ZipReader, BlobReader, TextWriter } from "@zip.js/zip.js";, and your bundler
removes what you do not use, e.g. the whole write path if you only read zip files.
Smaller entry points are also available, e.g. @zip.js/zip.js/lib/zip-core.js embeds
neither the Web Worker code nor the WebAssembly module, pass workerURI and
wasmURI to configure() instead.
@zip.js/zip.js/lib/zip-core-custom.js goes further and embeds no deflate implementation
at all, cf. Custom web workers and compression engines. The prebuilt
bundles in
/dist
are standalone files for script tags and AMD loaders, they are not tree-shaken.
The @zip.js/zip.js/external entry point offers the full API but references the Web
Worker code and the WebAssembly module as external files instead of embedding them. Bundlers like
webpack and Vite detect these references, emit zip-web-worker.js and
zip-module.wasm as separate assets, and remove approximately 45KB of embedded
payloads from the main bundle. The worker also runs from a real file URL. This avoids
blob: restrictions on pages and browser extensions with a strict Content Security
Policy. With Vite, add @zip.js/zip.js to optimizeDeps.exclude to
resolve the assets during development. With bundlers which do not rewrite
new URL(..., import.meta.url) expressions, copy the two files next to the output
bundle. The prebuilt bundle zip-fs-external.min.js offers the same behavior without
a bundler, it resolves the two files from its own directory: deploy the three files together.
Smaller compositions of this entry point are also available:
@zip.js/zip.js/lib/zip-fs-core-external.js excludes the MIME type table
(approximately 23KB), getMimeType() then returns
"application/octet-stream", and @zip.js/zip.js/lib/zip-core-external.js
also excludes the filesystem API. Both come with prebuilt ES module bundles in
/dist.
The full MIME type table remains available from any entry point via
@zip.js/zip.js/mime-types.
The library comes in two variants. The default variant embeds a WebAssembly build of zlib, while the
"native" variant embeds a JavaScript port of zlib instead. This embedded code is used when compressing
data with a non-default level, when decompressing Deflate64 entries, when the
useCompressionStream option is set to false, or when the Compression Streams
API is unavailable. Otherwise, both variants rely on the Compression Streams API and behave identically.
zip.js uses WebAssembly by default for more flexibility: the embedded zlib module works inside a web
worker or in the main thread, and the same binary is shared between both contexts without duplicating
any code. This also
makes the default variant smaller. The "native" variant does not depend on WebAssembly, e.g. for React
Native or pages with a Content Security Policy which does not allow wasm-unsafe-eval. React
Native resolves the main entry point of the package to the "native" variant automatically.
You can import the "native" variant:
import * as zip from "@zip.js/zip.js/index-native.js";
const zip = require("@zip.js/zip.js/index-native.js");
zip-native.min.js from the
/dist
directory in your project, as an AMD module or a regular script exposing the zip global
variable.
The smaller entry points also have a "native" twin, e.g. @zip.js/zip.js/lib/zip-core-native.js
instead of @zip.js/zip.js/lib/zip-core.js. The "native" variant is not available on JSR.
The createWorker option of configure() lets you create the web workers
yourself, with the standard pattern below that bundlers like webpack and Vite detect statically to
compile the worker script and its imports into a separate asset. It takes precedence over
workerURI.
import { configure } from "@zip.js/zip.js";
configure({
createWorker: () => new Worker(new URL("./zip-worker.js", import.meta.url), { type: "module" })
});
A custom worker script calls initWorker(), exposed by the
@zip.js/zip.js/worker entry point. It can register an alternative deflate
implementation: classes implementing the CompressionStream and
DecompressionStream interfaces with the "deflate-raw" format, e.g. based on
fflate. See the complete example in the
API documentation of initWorker().
import { initWorker } from "@zip.js/zip.js/worker";
import { CompressionStreamFallback, DecompressionStreamFallback } from "./fflate-streams.js";
initWorker({ CompressionStreamFallback, DecompressionStreamFallback });
The @zip.js/zip.js/lib/zip-core-custom.js entry point is designed for this use case: it
offers the full API but embeds neither the web worker code nor any deflate implementation. Combined
with createWorker, the compression engine of your choice then ships only once, in the
worker script. In a test with fflate, the application bundle and the worker script weighed
approximately 115KB in total, 43KB gzipped, versus approximately 158KB, 72KB gzipped, for the default
entry point.
import { configure } from "@zip.js/zip.js/lib/zip-core-custom.js";
configure({
createWorker: () => new Worker(new URL("./zip-worker.js", import.meta.url), { type: "module" })
});
The same classes can also be registered in the main thread via the
CompressionStreamFallback and DecompressionStreamFallback options of
configure(), e.g. when useWebWorkers is set to false. Like the
embedded implementation they replace, they are used when useCompressionStream is set to
false or when the Compression Streams API is unavailable. These options were previously
named CompressionStreamZlib and DecompressionStreamZlib, the old names are
deprecated but remain supported. The registered implementation also determines the support of custom
compression level values and, via the "deflate64-raw" format, of Deflate64
decompression.
The online builder generates a customized standalone build of zip.js directly in the browser, from the latest version published on npm. You can restrict the API to reading or writing zip files, include the web workers, choose the embedded compression implementation (WebAssembly, JavaScript, or none to rely on the Compression Streams API only), include the filesystem API, and download the result as a minified UMD or ES module file.
You can also build the library from the source code:
$ git clone https://github.com/gildas-lormeau/zip.js.git $ cd zip.js $ npm ci $ npm run build
The bundles are generated in the
/dist
directory. npm test runs the test suite with Node.js, Deno and Bun, and
npm run test-browsers runs it in Chrome, Firefox and Safari. See the
/tests
directory for more details.