mrkeyoor.com_
Wed 23 Sept 00:35 UTC
npmUtilsupdated 22 Sept 2026

yazl review

yazl 3.3.1 writes ZIP archives from Node filesystem paths, Buffers, streams, and explicit empty-directory entries. A ZipFile exposes a readable outputStream, so file data can move to disk or an HTTP response under backpressure without collecting the finished archive in memory. It validates stored paths, compresses through Node zlib, calculates CRC values, and selects ZIP64 when classic limits are exceeded. Version 3.3.1 fixes a race that could call an addReadStreamLazy provider too early or more than once. It does not read or extract archives.

Verdict

yazl 3.3.1 installed two packages and 1 MB in 0.8 seconds with 0 audit findings in our sandbox, and its failed browser build confirms it belongs in Node stream pipelines. Pick it for bounded-memory ZIP writing; pick a higher-level tool for recursion, progress, extraction, encryption, or browser output.

We installed it

Lab card: what happened when we installed yazlScreenshot of yazl documentation
Install✓ · 0.8s2 packages on disk · 1 MB
ImportESM import works · require() works · CommonJS package
Browsern/acould not be bundled for the browser (Node-only code, most likely)
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does yazl install cleanly?

Yes. In a fresh container with an empty cache, npm install yazl finished in 0.8s, leaving 2 packages and 1 MB on disk. npm audit reported no known vulnerabilities.

Can yazl run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does yazl work with both ESM and CommonJS?

Yes. Both import 'yazl' and require('yazl') worked in Node 22 in our run. The package is published as CommonJS.

Does yazl include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

yazl or archiver: which should you use?

archiver: Choose it for directory recursion, globbing, progress events, and TAR alongside ZIP. yazl 3.3.1 installed two packages and 1 MB in 0.8 seconds with 0 audit findings in our sandbox, and its failed browser build confirms it belongs in Node stream pipelines.

When should you not use yazl?

You need extraction, listing, append, or edits to an existing ZIP: yazl only creates new archives

API stability4/5yazl has kept ZipFile, addFile, addBuffer, addReadStream, addEmptyDirectory, outputStream, and end recognizable across its 2.x and 3.x lines. Version 3.1 added addReadStreamLazy while retaining the older eager stream method, and 3.3 extended timestamp metadata without changing the entry calls. One future break is already documented: dateToDosDateTime is deprecated and may disappear in 4.0. The change log also admits that some 3.1 error-handling changes lacked strong tests.
Docs5/5The README specifies every method, default option, path rule, timestamp format, compression mode, buffer ceiling, comment encoding, and ZIP64 threshold. It explains why addReadStreamLazy conserves resources, when calculatedTotalSizeCallback can return an exact byte count, how output backpressure works, and which old readers mishandle data descriptors or ZIP64. A detailed change history names the 3.3.1 lazy-provider race. Complete Promise-based error-handling examples and TypeScript setup are the main omissions.
Maintenance4/5npm published 3.3.1 on 2024-11-23 after several 2024 releases added lazy streams, compression levels, UTC Info-ZIP timestamps, and bug fixes. GitHub shows a repository push on 2026-03-14 for a development dependency update, and currently reports 20 issues and pull requests combined. The package has a narrow stable mission and recent correctness work. Long-running feature requests, callback-era packaging, and the absence of bundled types keep it below the top score.
Ecosystem4/5npm counted 3,589,744 yazl downloads in the latest measured week, and GitHub reports 383 stars. It pairs naturally with yauzl for extraction, has a separately maintained @types/yazl package, and composes with standard Node readable and writable streams. That is a healthy base for server ZIP generation. The package offers fewer convenience layers than archiver, no browser path like JSZip, and no built-in directory discovery, encryption, progress reporting, or Promise facade.

Use it if

  • A Node service must stream a ZIP response without buffering the completed archive
  • A large file set should keep approximately one source file open at a time
  • ZIP64 must turn on automatically for large entries or more than 65,534 archive members
  • Your code can own directory walking, stream errors, destination completion, and callback-style lazy sources
Skip it if

Setup reality

Our install of yazl 3.3.1 finished in 0.8 seconds and left two packages using 1 MB on disk. npm audit reported 0 known vulnerabilities. The package is 72 KB unpacked, declares one direct dependency and no peers, and uses Node's built-in zlib rather than a native add-on build.

There are no credentials or config files. yazl is CommonJS without an exports map; require() and ESM import both worked in our Node 22 sandbox. No TypeScript declarations were bundled. esbuild could not produce our browser bundle, matching the package's direct use of Node files, streams, buffers, and zlib. Use a browser ZIP library for client-side archives.

Archive creation is a stream lifecycle. Connect outputStream, add every entry, call end(), and wait for the destination to finish. end() does not return a completion Promise. addFile() stats immediately but opens content later. addReadStreamLazy() delays acquiring sockets or descriptors until yazl is ready; version 3.3.1 fixed its provider timing. Handle errors from the ZipFile and destination.

Stored file names cannot be blank, absolute, drive-prefixed, contain .. segments, or end in slash. yazl does not walk directories. Compression defaults to DEFLATE level 6, while already compressed media often belongs in store mode. Version 3.3 adds a UTC Info-ZIP timestamp field costing 9 bytes per entry. ZIP64 is automatic when required, but the README warns that some readers still mishandle it.

Patterns

Write two files into one ZIP zip-files

const fs = require('node:fs');
const yazl = require('yazl');

const zip = new yazl.ZipFile();
zip.outputStream.pipe(fs.createWriteStream('reports.zip'));
zip.addFile('reports/january.csv', 'reports/january.csv');
zip.addFile('reports/february.csv', 'reports/february.csv');
zip.end();

addFile() accepts file paths only; its second argument is the slash-separated name stored in the archive.

Await the destination pipeline await-zip-completion

const fs = require('node:fs');
const {pipeline} = require('node:stream/promises');
const yazl = require('yazl');

const zip = new yazl.ZipFile();
const finished = pipeline(zip.outputStream, fs.createWriteStream('out.zip'));
zip.addFile('input.txt', 'input.txt');
zip.end();
await finished;

end() only closes entry input; the pipeline Promise resolves when the writable destination has finished.

Add generated JSON from memory add-buffer

zip.addBuffer(
  Buffer.from(JSON.stringify(manifest, null, 2)),
  'manifest.json',
  {mtime: new Date('2024-01-01T00:00:00Z')}
);

addBuffer() retains the entire entry in memory, so a lazy stream is safer for large generated content.

Open a source only when yazl requests it add-lazy-stream

zip.on('error', handleArchiveError);
zip.addReadStreamLazy(
  'exports/orders.ndjson',
  {size: expectedBytes},
  (callback) => callback(null, createOrdersStream())
);

Version 3.3.1 fixes lazy-provider timing; pass stream creation failures as callback(error) so ZipFile emits them.

Pipe a ZIP into an HTTP response stream-http-response

res.setHeader('Content-Type', 'application/zip');
res.setHeader('Content-Disposition', 'attachment; filename=export.zip');

const zip = new yazl.ZipFile();
zip.on('error', (error) => res.destroy(error));
zip.outputStream.pipe(res);
zip.addBuffer(Buffer.from(csv), 'orders.csv');
zip.end();

After response bytes begin, an archive error cannot be replaced by a clean JSON response, so destroy the stream and log the failure.

Keep an empty folder in the archive add-empty-directory

zip.addEmptyDirectory('uploads/', {
  mtime: new Date('2024-01-01T00:00:00Z'),
  mode: 0o40775,
});

Ordinary file paths imply their parent folders; addEmptyDirectory() is needed only when an empty directory must survive extraction.

Enumerate a directory tree yourself walk-directory

const fs = require('node:fs/promises');
const path = require('node:path');

async function addTree(zip, root, dir = root) {
  for (const entry of await fs.readdir(dir, {withFileTypes: true})) {
    const realPath = path.join(dir, entry.name);
    const storedPath = path.relative(root, realPath).split(path.sep).join('/');
    if (entry.isDirectory()) await addTree(zip, root, realPath);
    else if (entry.isFile()) zip.addFile(realPath, storedPath);
  }
}

This walker skips symlinks and empty directories; choose explicit policies for both before traversing user-controlled trees.

Fix timestamps and modes make-reproducible-zip

const stable = {
  mtime: new Date('2024-01-01T00:00:00Z'),
  mode: 0o100644,
};

zip.addFile('build/app.js', 'app.js', stable);
zip.addBuffer(Buffer.from(version), 'VERSION', stable);

Stable mtime and mode remove two byte-level differences, while entry order and content must also stay deterministic.

Store an already compressed asset skip-useless-compression

zip.addFile('video.mp4', 'media/video.mp4', {
  compress: false,
});

MP4, JPEG, PNG, and existing archives often shrink little under DEFLATE, so store mode saves CPU.

Set the zlib level for text tune-compression-level

zip.addFile('database.sql', 'backup/database.sql', {
  compressionLevel: 9,
});

The default compression level is 6; level 0 means no compression, and conflicting compress options are rejected.

Calculate a streamable Content-Length calculate-content-length

zip.addReadStreamLazy(
  'payload.bin',
  {size: payloadSize, compress: false},
  (callback) => callback(null, openPayload())
);

zip.end({}, (totalSize) => {
  if (totalSize !== -1) res.setHeader('Content-Length', totalSize);
  zip.outputStream.pipe(res);
});

A predictable total generally requires compression disabled plus a declared size for every stream entry; otherwise the callback can return -1.

Force ZIP64 in a compatibility test force-zip64

zip.addFile('sample.bin', 'sample.bin', {forceZip64Format: true});
zip.end({forceZip64Format: true});

Production code should let yazl choose ZIP64 when required because the README warns that some readers mishandle the format.

Alternatives

PackageRegistryPick it when
archivernpmChoose it for directory recursion, globbing, progress events, and TAR alongside ZIP.
jszipnpmChoose it for browser use or an in-memory archive object when inputs are small enough to buffer.
zip-streamnpmChoose it for a low-level sequential entry API built directly around a ZIP output stream.
adm-zipnpmChoose it for simple synchronous reading and writing of small archives where buffering is acceptable.

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.