mrkeyoor.com_
Tue 22 Sept 22:36 UTC
npmUtilsupdated 22 Sept 2026

@zip.js/zip.js review

@zip.js/zip.js 2.8.60 reads and writes ZIP archives in browsers, Node, Deno, Bun, and React Native through paired Reader and Writer adapters. It supports streams, workers, native compression streams, ZIP64 archives beyond 4 GB, split archives, Deflate64 decoding, and AES-encrypted entries. Our measured 2.8.56 full import was 178.3 KB minified and 78.7 KB gzipped, so advanced capability carries a real browser cost. Version 2.8.60 adds a `VERSION` constant, registered-codec inspection, supported-compression-method inspection, full core exports from the ZIP filesystem core build, and uncompressed sizes for custom codecs.

Verdict

@zip.js/zip.js 2.8.56 installed as one 8 MB package and its full import measured 78.7 KB gzipped with 0 audit findings in our sandbox. Choose the current 2.8.60 line when streaming, ZIP64, workers, encryption, or cross-runtime adapters justify that weight; use JSZip for small in-memory archives and enforce extraction limits with either library.

We installed it

Lab card: what happened when we installed @zip.js/zip.jsScreenshot of @zip.js/zip.js documentation
Install✓ · 1.7s1 package on disk · 8 MB
ImportESM import works · require() works · ESM package with exports map
Browser78.7 KBgzipped (178.3 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does @zip.js/zip.js install cleanly?

Yes. In a fresh container with an empty cache, npm install @zip.js/zip.js finished in 2 seconds, leaving 1 package and 8 MB on disk. npm audit reported no known vulnerabilities.

How much does @zip.js/zip.js add to a browser bundle?

78.7 KB gzipped (178.3 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does @zip.js/zip.js work with both ESM and CommonJS?

Yes. Both import '@zip.js/zip.js' and require('@zip.js/zip.js') worked in Node 22 in our run. The package is published as ESM with an exports map.

Does @zip.js/zip.js include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

@zip.js/zip.js or jszip: which should you use?

jszip: Use it for small in-memory archives and a simpler object API. @zip.js/zip.js 2.8.56 installed as one 8 MB package and its full import measured 78.7 KB gzipped with 0 audit findings in our sandbox.

When should you not use @zip.js/zip.js?

One response body only needs gzip. CompressionStream or Node zlib avoids the ZIP container API and 78.7 KB gzipped full import.

API stability4/5The 2.x line keeps `ZipReader`, `ZipWriter`, and paired source and destination adapters at its center while adding options, codecs, and entry points. Version 2.8.60 extends inspection and custom-codec APIs without replacing basic archive calls. The export map now spans ESM, CommonJS, native, worker, WASM, filesystem, core, and external builds, so entry choice is a compatibility surface of its own.
Docs4/5The README includes complete Blob and Web Stream read-write flows, concurrent entries, custom workers, and links to generated class and option references. The API site covers the many Reader, Writer, codec, strictness, encryption, and progress settings. A secure extraction policy still requires joining details from several option pages with application rules for paths, byte ceilings, and destinations.
Maintenance5/5npm published versions 2.8.56 through 2.8.60 between August 22 and August 25, 2026, and GitHub records a push on August 25. The unarchived repository has 3,878 stars and 1 open issue or pull request. Recent commits added codec metadata and core exports, while the package publishes provenance plus explicit Node 18, Deno 1, and Bun 0.7 engine floors.
Ecosystem4/5npm counted 5,350,697 downloads in the latest completed week. Browser, Node, Deno, Bun, and React Native entry points sit beside Blob, text, typed-array, HTTP, and Web Stream adapters. Worker, WASM, custom-codec, ZIP64, split-archive, encryption, and Deflate64 support cover unusual jobs, though this breadth makes the API harder to learn than JSZip's in-memory model.

Use it if

  • Users must create or inspect ZIP files in a browser without uploading them.
  • Archives need streaming, ZIP64, split output, concurrent compression, or custom codecs.
  • One archive layer must cover browsers, Node 18+, Deno, Bun, and React Native.
  • AES encryption, Deflate64 decoding, progress reporting, or abort signals are requirements.
Skip it if

Setup reality

We installed @zip.js/zip.js 2.8.56 in 1.7 seconds in a fresh Node 22 Bookworm sandbox. It left 1 package and 8 MB on disk. That measured package had 0 direct dependencies, 0 peer dependencies, 7,224 KB unpacked, a BSD-3-Clause license, and engine floors of Node 18, Deno 1, and Bun 0.7. npm audit found 0 known vulnerabilities.

The package is ESM with an exports map and CommonJS path. Both require() and ESM import worked on our box, and declarations are bundled. No credentials or native compilation are required. Browser deployments may need worker and WASM URLs configured for their CSP, CDN, or bundler. React Native has a separate native export, while Deno examples use the JSR name.

Our full 2.8.56 import bundled to 178.3 KB minified and 78.7 KB gzipped. Prefer named or narrower core imports and measure the production chunk. BlobWriter retains finished output in memory; large archives belong on writable streams or filesystem-backed destinations. Concurrent adds may allocate temporary buffers, and the readable side of a stream must consume data to release backpressure.

Always await close() because it writes the central directory. For uploads, reject absolute and parent-traversal paths, keep output inside a chosen root, cap entry count and uncompressed bytes, and enable strictness, signature, and overlap checks where appropriate. AES hides contents, not filenames. Forward slashes delimit ZIP paths; backslashes are literal characters.

Patterns

Write one text file into a Blob create-text-archive

import { BlobWriter, TextReader, ZipWriter } from '@zip.js/zip.js';

const writer = new ZipWriter(new BlobWriter('application/zip'));
await writer.add('hello.txt', new TextReader('Hello world'));
const zipBlob = await writer.close();

`close()` appends the central directory and resolves to the finished Blob; output used before that point is incomplete.

Inspect entry metadata first list-archive-entries

import { BlobReader, ZipReader } from '@zip.js/zip.js';

const reader = new ZipReader(new BlobReader(file));
try {
  const entries = await reader.getEntries();
  for (const entry of entries) {
    console.log(entry.filename, entry.directory, entry.uncompressedSize);
  }
} finally {
  await reader.close();
}

`getEntries()` reads metadata only. Reject excessive entry counts or declared output sizes before calling `getData`.

Decode config.json as text extract-entry-text

import { BlobReader, TextWriter, ZipReader } from '@zip.js/zip.js';

const reader = new ZipReader(new BlobReader(file));
try {
  const entries = await reader.getEntries();
  const config = entries.find((entry) => entry.filename === 'config.json');
  if (!config || config.directory) throw new Error('config.json is missing');
  const text = await config.getData(new TextWriter());
  console.log(JSON.parse(text));
} finally {
  await reader.close();
}

`TextWriter` holds the decoded entry as one string; set a size ceiling before using it on uploaded archives.

Preview an archived PNG extract-entry-blob

import { BlobReader, BlobWriter, ZipReader } from '@zip.js/zip.js';

const reader = new ZipReader(new BlobReader(file));
try {
  const entry = (await reader.getEntries()).find((item) => item.filename === 'photo.png');
  if (!entry || entry.directory) throw new Error('photo.png is missing');
  const blob = await entry.getData(new BlobWriter('image/png'));
  const url = URL.createObjectURL(blob);
  image.src = url;
} finally {
  await reader.close();
}

Revoke the object URL after the preview closes, and verify file content instead of trusting the `.png` suffix.

Archive browser-selected files add-browser-files

import { BlobReader, BlobWriter, ZipWriter } from '@zip.js/zip.js';

const writer = new ZipWriter(new BlobWriter('application/zip'));
for (const file of fileInput.files) {
  const safeName = file.name.replaceAll('\\', '_').replaceAll('/', '_');
  await writer.add(safeName, new BlobReader(file), { lastModDate: file.lastModified ? new Date(file.lastModified) : new Date() });
}
const archive = await writer.close();

This example flattens both slash styles; preserve directories only after validating every path segment.

Stream a large ZIP response stream-large-archive

import { BlobReader, ZipWriter } from '@zip.js/zip.js';

const stream = new TransformStream();
const response = new Response(stream.readable, {
  headers: { 'Content-Type': 'application/zip' },
});
const writer = new ZipWriter(stream.writable);
const writing = (async () => {
  await writer.add('video.mp4', new BlobReader(videoFile));
  await writer.close();
})();
await Promise.all([writing, consumeResponse(response)]);

Consume `stream.readable` while adding data, or backpressure can block writes; a `BlobWriter` would retain the whole output.

Report byte progress track-progress

await zipWriter.add('data.bin', sourceReader, {
  onprogress: (loaded, total) => {
    if (total) progress.value = loaded / total;
  },
});

await entry.getData(destinationWriter, {
  onprogress: (loaded, total) => {
    console.log(`${loaded} of ${total} bytes`);
  },
});

A streaming source may not expose a total, so the UI needs an indeterminate state when `total` is 0.

Abort a long entry write cancel-operation

const controller = new AbortController();
cancelButton.addEventListener('click', () => controller.abort());

try {
  await zipWriter.add('large.bin', sourceReader, { signal: controller.signal });
} catch (error) {
  if (error.name !== 'AbortError') throw error;
}

After an aborted `add`, discard the target unless your tested writer path can still close a valid archive.

Encrypt one entry with AES-256 encrypt-entry-aes

const writer = new ZipWriter(new BlobWriter('application/zip'));
await writer.add('private.txt', new TextReader(secret), {
  password,
  encryptionStrength: 3,
});
const encryptedZip = await writer.close();

`encryptionStrength: 3` selects AES-256 for contents; filenames remain visible and older unzip tools may reject the method.

Decrypt and verify one entry decrypt-entry

const reader = new ZipReader(new BlobReader(encryptedFile));
try {
  const entry = (await reader.getEntries()).find((item) => !item.directory);
  if (!entry) throw new Error('archive has no file entry');
  const value = await entry.getData(new TextWriter(), {
    password,
    checkSignature: true,
  });
  console.log(value);
} finally {
  await reader.close();
}

Wrong credentials reject during `getData`; rate-limit attempts and keep the password out of logs.

Screen uploaded archive paths inspect-untrusted-archive

const reader = new ZipReader(new BlobReader(upload), { strictness: 'strict' });
try {
  for (const entry of await reader.getEntries()) {
    const name = entry.filename.replaceAll('\\', '/');
    if (name.startsWith('/') || name.split('/').includes('..')) {
      throw new Error(`unsafe entry path: ${name}`);
    }
    if (!entry.directory) {
      await entry.getData(destinationFor(name), {
        strictness: 'strict',
        checkSignature: true,
        checkOverlappingEntry: true,
      });
    }
  }
} finally {
  await reader.close();
}

Strict parsing does not cap expanded bytes or prove destination containment; enforce both before writing any entry.

Point workers and WASM at built assets configure-worker-assets

import { configure } from '@zip.js/zip.js';

configure({
  workerURI: new URL('./zip-web-worker.js', import.meta.url).href,
  wasmURI: new URL('./zip-module.wasm', import.meta.url).href,
  maxWorkers: 2,
});

Copy matching distribution assets during the build and allow both URLs in the site's CSP.

Alternatives

PackageRegistryPick it when
jszipnpmUse it for small in-memory archives and a simpler object API.
fflatenpmUse it for compact lower-level compression with sync, async, and streaming choices.
client-zipnpmUse it only to generate browser ZIP downloads from blobs, streams, or responses.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.