compressing vs adm-zip vs archiver vs node-zip vs yauzl vs yazl vs zip-lib
Node.js 中 ZIP 文件处理方案的技术选型指南
compressingadm-ziparchivernode-zipyauzlyazlzip-lib类似的npm包:

Node.js 中 ZIP 文件处理方案的技术选型指南

adm-zip、archiver、compressing、node-zip、yauzl、yazl 和 zip-lib 都是 Node.js 生态中用于处理 ZIP 压缩文件的工具库,但它们在 API 设计、功能完整度、维护状态和适用场景上有显著差异。archiver 专注于流式压缩,适合处理大文件;yauzl 和 yazl 分别提供低级别的解压和压缩能力;adm-zip 提供同步和异步的完整 ZIP 操作;compressing 是阿里团队维护的多格式压缩工具;node-zip 已停止维护;zip-lib 提供现代化的异步 API。选择哪个库取决于你的具体需求:是需要流式处理、同步操作、还是现代异步 API。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
compressing144,12645659.2 kB472 个月前MIT
adm-zip02,183151 kB571 个月前MIT
archiver02,97839.6 kB1745 个月前MIT
node-zip0217-1911 年前-
yauzl0829110 kB124 个月前MIT
yazl038758.7 kB222 年前MIT
zip-lib04568.8 kB14 个月前MIT

Node.js ZIP 处理库深度对比:从 API 设计到生产实践

在 Node.js 项目中处理 ZIP 文件是常见需求,但选择合适的库并不简单。adm-zip、archiver、compressing、node-zip、yauzl、yazl 和 zip-lib 各有特点,理解它们的差异能帮你避免生产环境中的性能问题和维护陷阱。让我们从实际工程角度深入对比。

📦 核心功能定位:完整方案 vs 单一职责

adm-zip 提供完整的 ZIP 文件操作能力,支持读取、创建、修改现有 ZIP。

const AdmZip = require('adm-zip');
const zip = new AdmZip();
zip.addLocalFile('./file.txt');
zip.writeZip('./output.zip');

archiver 专注于流式压缩,通过管道将数据写入目标。

const archiver = require('archiver');
const output = fs.createWriteStream('./output.zip');
const archive = archiver('zip', { zlib: { level: 9 } });
archive.pipe(output);
archive.file('./file.txt', { name: 'file.txt' });
archive.finalize();

compressing 提供多格式统一 API,支持 ZIP、TAR、GZIP 等。

const compressing = require('compressing');
await compressing.zip.compressFile('./file.txt', './output.zip');

node-zip 提供基础的 ZIP 创建和解压,但已停止维护。

const zip = require('node-zip')();
zip.file('file.txt', 'content');
const data = zip.generate({ type: 'nodebuffer' });

yauzl 只负责解压,提供细粒度的读取控制。

const yauzl = require('yauzl');
yauzl.open('./input.zip', { lazyEntries: true }, (err, zipfile) => {
  zipfile.readEntry();
  zipfile.on('entry', (entry) => {
    zipfile.openReadStream(entry, (err, readStream) => {
      // 处理文件流
    });
  });
});

yazl 只负责创建 ZIP,与 yauzl 配合使用。

const yazl = require('yazl');
const zipFile = new yazl.ZipFile();
zipFile.addFile('./file.txt', 'file.txt');
zipFile.outputStream.pipe(fs.createWriteStream('./output.zip'));
zipFile.end();

zip-lib 提供现代化的异步 API,支持压缩和解压。

const { zip, unzip } = require('zip-lib');
await zip(['./file.txt'], './output.zip');
await unzip('./input.zip', './output-dir');

🔄 执行模式:同步 vs 异步 vs 流式

执行模式直接影响性能和内存使用,这是生产环境的关键考量。

adm-zip 支持同步和异步,但默认将 ZIP 加载到内存。

// 同步方式
const zip = new AdmZip('./input.zip');
const entries = zip.getEntries();

// 异步方式
zip.toAsyncBuffer((data) => {
  fs.writeFileSync('./output.zip', data);
});

archiver 纯流式异步,适合大文件处理。

const archive = archiver('zip');
archive.pipe(fs.createWriteStream('./output.zip'));
archive.directory('./source-dir', false);
archive.on('finish', () => {
  console.log('压缩完成');
});

compressing 同时支持 Promise 和流式。

// Promise 方式
await compressing.zip.compressDir('./source', './output.zip');

// 流式方式
const stream = compressing.zip.createStream();
stream.pipe(fs.createWriteStream('./output.zip'));

node-zip 同步操作,无流式支持。

const zip = require('node-zip')();
zip.file('test.txt', 'hello');
const buffer = zip.generate({ type: 'nodebuffer' });

yauzl 基于事件的异步 API。

yauzl.open('./input.zip', { lazyEntries: true }, (err, zipfile) => {
  zipfile.on('entry', (entry) => {
    // 逐个处理文件
  });
  zipfile.on('end', () => {
    console.log('解压完成');
  });
});

yazl 基于流的异步 API。

const zipFile = new yazl.ZipFile();
zipFile.addFile('./file.txt', 'file.txt');
zipFile.outputStream.on('finish', () => {
  console.log('创建完成');
});

zip-lib 纯 Promise/async-await 风格。

await zip(['./file1.txt', './file2.txt'], './output.zip');
await unzip('./input.zip', './output-dir');

📊 内存与性能考量

处理大文件时,内存使用是关键指标。

库内存模式适合文件大小流式支持
adm-zip全量加载中小文件 (<100MB)❌
archiver流式处理大文件 (无限制)✅
compressing可选中等文件✅
node-zip全量加载小文件❌
yauzl流式读取大文件✅
yazl流式写入大文件✅
zip-lib流式处理中等文件✅

adm-zip 在读取 ZIP 时会加载整个文件到内存,处理大文件可能导致内存溢出。

// 风险:大文件可能耗尽内存
const zip = new AdmZip('./large-file.zip');
const entries = zip.getEntries(); // 全部加载到内存

archiver 使用流式处理,内存占用稳定。

// 安全:流式处理大文件
const archive = archiver('zip');
archive.pipe(fs.createWriteStream('./output.zip'));
archive.directory('./large-dir', false); // 流式读取目录

yauzl 使用 lazyEntries 选项可控制内存使用。

// 逐个处理,控制内存
yauzl.open('./large.zip', { lazyEntries: true }, (err, zipfile) => {
  zipfile.readEntry(); // 手动控制读取节奏
});

🔧 API 设计哲学对比

面向对象 vs 函数式

adm-zip 采用面向对象设计,状态保存在实例中。

const zip = new AdmZip();
zip.addFile('name.txt', Buffer.from('content'));
zip.addLocalFolder('./dir');
zip.writeZip('./output.zip');

archiver 采用链式 API,配置与执行分离。

archiver('zip', { level: 9 })
  .pipe(fs.createWriteStream('./output.zip'))
  .file('./file.txt', { name: 'custom.txt' })
  .directory('./src', 'source')
  .finalize();

compressing 提供简洁的函数式 API。

await compressing.zip.compressFile('./file.txt', './output.zip');
await compressing.zip.unzip('./input.zip', './output-dir');

zip-lib 纯函数式,返回 Promise。

await zip(['./file1.txt'], './output.zip');
await unzip('./input.zip', './output-dir');

yauzl/yazl 基于事件/流,需要手动管理状态。

// yauzl 事件驱动
zipfile.on('entry', (entry) => { /* 处理 */ });
zipfile.on('end', () => { /* 完成 */ });

⚠️ 维护状态与安全性

这是选择库时最重要的考量因素之一。

node-zip 已停止维护,最后更新在 2015 年,存在已知安全漏洞。

// ❌ 不建议在新项目中使用
const zip = require('node-zip')();

adm-zip 仍在维护,但更新频率较低,有报告的安全问题需关注。

// ⚠️ 可用但需关注安全更新
const AdmZip = require('adm-zip');

archiver 活跃维护,社区广泛使用,定期安全更新。

// ✅ 生产环境推荐
const archiver = require('archiver');

compressing 阿里团队维护,企业级支持,更新稳定。

// ✅ 企业项目推荐
const compressing = require('compressing');

yauzl/yazl 稳定维护,专注于单一职责,安全性较好。

// ✅ 需要精细控制时推荐
const yauzl = require('yauzl');
const yazl = require('yazl');

zip-lib 较新,社区较小,但 API 现代化。

// ⚠️ 新项目可考虑,但需评估社区支持
const { zip, unzip } = require('zip-lib');

🎯 实际场景选型建议

场景 1:构建工具打包资源

需要压缩大量小文件,对速度要求高。

// ✅ 推荐:archiver
const archive = archiver('zip', { zlib: { level: 6 } });
archive.pipe(fs.createWriteStream('./dist.zip'));
archive.directory('./dist', false);
archive.finalize();

场景 2:用户上传文件解压处理

需要安全地解压用户上传的 ZIP 文件。

// ✅ 推荐:yauzl (可控制解压路径)
yauzl.open('./upload.zip', { lazyEntries: true }, (err, zipfile) => {
  zipfile.on('entry', (entry) => {
    // 验证路径安全性
    if (entry.fileName.startsWith('..')) {
      return; // 跳过危险路径
    }
    zipfile.openReadStream(entry, (err, readStream) => {
      readStream.pipe(fs.createWriteStream('./safe-path/' + entry.fileName));
    });
  });
});

场景 3:API 返回压缩文件下载

需要流式传输到 HTTP 响应。

// ✅ 推荐:archiver
app.get('/download', (req, res) => {
  const archive = archiver('zip');
  archive.pipe(res);
  archive.directory('./files', false);
  archive.finalize();
});

场景 4:简单压缩需求

只需要基础压缩功能,代码简洁优先。

// ✅ 推荐:compressing 或 zip-lib
await compressing.zip.compressFile('./file.txt', './output.zip');
// 或
await zip(['./file.txt'], './output.zip');

场景 5:修改现有 ZIP 文件

需要添加或删除 ZIP 中的文件。

// ✅ 推荐:adm-zip
const zip = new AdmZip('./existing.zip');
zip.addFile('new.txt', Buffer.from('content'));
zip.deleteFile('old.txt');
zip.writeZip('./updated.zip');

📋 功能对比总表

功能adm-ziparchivercompressingnode-zipyauzlyazlzip-lib
创建 ZIP✅✅✅✅❌✅✅
解压 ZIP✅❌✅✅✅❌✅
修改 ZIP✅❌❌❌❌❌❌
流式处理❌✅✅❌✅✅✅
多格式支持❌❌✅❌❌❌❌
异步 API✅✅✅❌✅✅✅
维护状态⚠️✅✅❌✅✅⚠️
内存友好❌✅✅❌✅✅✅

💡 最终建议

生产环境首选:archiver(压缩)+ yauzl(解压)组合,经过大量项目验证,性能和安全都有保障。

企业级项目:compressing,阿里团队维护,多格式支持,适合需要统一压缩方案的场景。

简单需求:zip-lib,API 简洁现代,适合新项目快速开发。

避免使用:node-zip,已停止维护,存在安全风险。

特殊需求:adm-zip 适合需要修改现有 ZIP 的场景,但注意内存限制。

选择 ZIP 处理库时,优先考虑维护状态和内存模型,其次才是 API 便利性。生产环境中,稳定性和安全性永远比代码简洁更重要。

如何选择: compressing vs adm-zip vs archiver vs node-zip vs yauzl vs yazl vs zip-lib

  • compressing:

    选择 compressing 如果你需要统一处理多种压缩格式(ZIP、TAR、GZIP 等),且希望使用阿里团队维护的稳定库。适合需要多格式支持的企业级应用,API 设计简洁,支持流式和 Promise 两种方式。

  • adm-zip:

    选择 adm-zip 如果你需要同步或异步地读取和修改现有 ZIP 文件,支持添加、删除、更新 ZIP 中的文件。适合需要完整 ZIP 操作能力的场景,如构建工具、文件管理器。但注意它在处理超大文件时可能占用较多内存,因为需要将整个 ZIP 加载到内存中。

  • archiver:

    选择 archiver 如果你需要流式压缩大量文件或目录,支持管道传输到文件、HTTP 响应或其他可写流。适合备份系统、文件下载服务等需要高效处理大文件的场景。它是生产环境中最常用的压缩库之一,社区活跃且文档完善。

  • node-zip:

    不建议在新项目中使用 node-zip,该库已停止维护多年,存在已知安全问题且不再接收更新。如果现有项目依赖它,应尽快迁移到 adm-zip 或 archiver 等活跃维护的替代方案。

  • yauzl:

    选择 yauzl 如果你只需要读取/解压 ZIP 文件,且希望使用轻量级、低级别的 API。适合需要精细控制解压过程的场景,如自定义文件提取逻辑。它只支持解压,不支持创建 ZIP 文件。

  • yazl:

    选择 yazl 如果你只需要创建 ZIP 文件,且希望使用轻量级、低级别的 API。适合需要精细控制压缩过程的场景。它只支持创建 ZIP,不支持解压。常与 yauzl 配合使用。

  • zip-lib:

    选择 zip-lib 如果你需要现代化的异步 API 和流式处理能力,同时支持压缩和解压。适合新项目,特别是需要 Promise/async-await 风格的代码。但社区相对较小,文档不如 archiver 完善。

compressing的README

compressing

NPM version Test coverage npm download Node.js Version PRs Welcome CodeRabbit Pull Request Reviews

The missing compressing and uncompressing lib for node.

Currently supported:

  • tar
  • gzip
  • tgz
  • zip

Install

npm install compressing

Usage

Compress a single file

Use gzip as an example, tar, tgz and zip is same as gzip.

promise style

const compressing = require('compressing');

// compress a file
compressing.gzip.compressFile('file/path/to/compress', 'path/to/destination.gz')
.then(compressDone)
.catch(handleError);

// compress a file buffer
compressing.gzip.compressFile(buffer, 'path/to/destination.gz')
.then(compressDone)
.catch(handleError);

// compress a stream
compressing.gzip.compressFile(stream, 'path/to/destination.gz')
.then(compressDone)
.catch(handleError);

stream style

const compressing = require('compressing');

new compressing.gzip.FileStream({ source: 'file/path/to/compress' })
  .on('error', handleError)
  .pipe(fs.createWriteStream('path/to/destination.gz'))
  .on('error', handleError);

// It's a transform stream, so you can pipe to it
fs.createReadStream('file/path/to/compress')
  .on('error', handleError)
  .pipe(new compressing.gzip.FileStream())
  .on('error', handleError)
  .pipe(fs.createWriteStream('path/to/destination.gz'))
  .on('error', handleError);

// You should take care of stream errors in caution, use pump to handle error in one place
const { pipeline: pump } = require('stream');
const sourceStream = fs.createReadStream('file/path/to/compress');
const gzipStream = new compressing.gzip.FileStream();
const destStream = fs.createWriteStream('path/to/destination.gz');
pump(sourceStream, gzipStream, destStream, handleError);

Compress a dir

Use tar as an example, tgz and zip is same as gzip.

Gzip only support compressing a single file. if you want to compress a dir with gzip, then you may need tgz instead.

promise style

const compressing = require('compressing');
compressing.tar.compressDir('dir/path/to/compress', 'path/to/destination.tar')
.then(compressDone)
.catch(handleError);

stream style

const compressing = require('compressing');

const tarStream = new compressing.tar.Stream();
tarStream.addEntry('dir/path/to/compress');

tarStream
  .on('error', handleError)
  .pipe(fs.createWriteStream('path/to/destination.tar'))
  .on('error', handleError);

// You should take care of stream errors in caution, use pump to handle error in one place
const tarStream = new compressing.tar.Stream();
tarStream.addEntry('dir/path/to/compress');
const destStream = fs.createWriteStream('path/to/destination.tar');
pump(tarStream, destStream, handleError);

Stream is very powerful, you can compress multiple entries in it;

const tarStream = new compressing.tar.Stream();
// dir
tarStream.addEntry('dir/path/to/compress');

// file
tarStream.addEntry('file/path/to/compress');

// buffer
tarStream.addEntry(buffer);

// stream
tarStream.addEntry(stream);

const destStream = fs.createWriteStream('path/to/destination.tar');
pipe(tarStream, destStream, handleError);

Uncompress a file

promise style

const compressing = require('compressing');

// uncompress a file
compressing.tgz.uncompress('file/path/to/uncompress.tgz', 'path/to/destination/dir')
.then(uncompressDone)
.catch(handleError);

// uncompress a file buffer
compressing.tgz.uncompress(buffer, 'path/to/destination/dir')
.then(uncompressDone)
.catch(handleError);

// uncompress a stream
compressing.tgz.uncompress(stream, 'path/to/destination/dir')
.then(uncompressDone)
.catch(handleError);

Note: tar, tgz and zip have the same uncompressing API as above: destination should be a path of a directory, while that of gzip is slightly different: destination must be a file or filestream.

And working with urllib is super easy. Let's download a tgz file and uncompress to a directory:

const urllib = require('urllib');
const targetDir = require('os').tmpdir();
const compressing = require('compressing');

urllib.request('http://registry.npmjs.org/compressing/-/compressing-2.0.0.tgz', {
  streaming: true,
  followRedirect: true,
})
.then(result => compressing.tgz.uncompress(result.res, targetDir))
.then(() => console.log('uncompress done'))
.catch(console.error);

stream style

const compressing = require('compressing');
const mkdirp = require('mkdirp');

function onEntry(header, stream, next) => {
  stream.on('end', next);

  // header.type => file | directory
  // header.name => path name

  if (header.type === 'file') {
    stream.pipe(fs.createWriteStream(path.join(destDir, header.name)));
  } else { // directory
    fs.mkdir(path.join(destDir, header.name), { recursive: true }, err => {
      if (err) return handleError(err);
      stream.resume();
    });
  }
}

new compressing.tgz.UncompressStream({ source: 'file/path/to/uncompress.tgz' })
  .on('error', handleError)
  .on('finish', handleFinish) // uncompressing is done
  .on('entry', onEntry);

// It's a writable stream, so you can pipe to it
fs.createReadStream('file/path/to/uncompress')
  .on('error', handleError)
  .pipe(new compressing.tgz.UncompressStream())
  .on('error', handleError)
  .on('finish', handleFinish) // uncompressing is done
  .on('entry', onEntry);

Note: tar, tgz and zip have the same uncompressing streaming API as above: it's a writable stream, and entries will be emitted while uncompressing one after one another, while that of gzip is slightly different: gzip.UncompressStream is a transform stream, so no entry event will be emitted and you can just pipe to another stream

This constrants is brought by Gzip algorithm itself, it only support compressing one file and uncompress one file.

new compressing.gzip.UncompressStream({ source: 'file/path/to/uncompress.gz' })
  .on('error', handleError)
  .pipe(fs.createWriteStream('path/to/dest/file'))
  .on('error', handleError);

API

compressFile

Use this API to compress a single file. This is a convenient method, which wraps FileStream API below, but you can handle error in one place.

  • gzip.compressFile(source, dest, opts)
  • tar.compressFile(source, dest, opts)
  • tgz.compressFile(source, dest, opts)
  • zip.compressFile(source, dest, opts)

Params

  • source {String|Buffer|Stream} - source to be compressed, could be a file path, buffer, or a readable stream
  • dest {String|Stream} - compressing destination, could be a file path(eg. /path/to/xx.tgz), or a writable stream.
  • opts {Object} - usually you don't need it

Returns a promise object.

compressDir

Use this API to compress a dir. This is a convenient method, which wraps Stream API below, but you can handle error in one place.

Note: gzip do not have a compressDir method, you may need tgz instead.

  • tar.compressDir(source, dest, opts)
  • tgz.compressDir(source, dest, opts)
  • zip.compressDir(source, dest, opts)

Params

  • source {String|Buffer|Stream} - source to be compressed
  • dest {String|Stream} - compressing destination, could be a file path(eg. /path/to/xx.tgz), or a writable stream.
  • opts {Object} - usually you don't need it

uncompress

Use this API to uncompress a file. This is a convenient method, which wraps UncompressStream API below, but you can handle error in one place. RECOMMANDED.

  • tar.uncompress(source, dest, opts)
  • tgz.uncompress(source, dest, opts)
  • zip.uncompress(source, dest, opts)
  • gzip.uncompress(source, dest, opts)

Params

  • source {String|Buffer|Stream} - source to be uncompressed
  • dest {String|Stream} - uncompressing destination. When uncompressing tar, tgz and zip, it should be a directory path (eg. /path/to/xx). When uncompressing gzip, it should be a file path or a writable stream.
  • opts {Object} - usually you don't need it
    • opts.zipFileNameEncoding {String} - Only work on zip format, default is 'utf8'. Major non-UTF8 encodings by languages:

      • Korean: cp949, euc-kr
      • Japanese: sjis (shift_jis), cp932, euc-jp
      • Chinese: gbk, gb18030, gb2312, cp936, hkscs, big5, cp950
    • opts.strip {Number} - Strip leading path segments when extracting (tar/tgz/zip). Default is 0.

FileStream

The transform stream to compress a single file.

Note: If you are not very familiar with streams, just use compressFile() API, error can be handled in one place.

  • new gzip.FileStream(opts)
  • new tar.FileStream(opts)
  • new tgz.FileStream(opts)
  • new zip.FileStream(opts)

Common params:

  • opts.source {String|Buffer|Stream} - source to be compressed, could be a file path, buffer, or a readable stream.

Gzip params:

  • opts.zlib - {Object} gzip.FileStream uses zlib to compress, pass this param to control the behavior of zlib.

Tar params:

  • opts.relativePath {String} - Adds a file from source into the compressed result file as opts.relativePath. Uncompression programs would extract the file from the compressed file as relativePath. If opts.source is a file path, opts.relativePath is optional, otherwise it's required.
  • opts.size {Number} - Tar compression requires the size of file in advance. When opts.source is a stream, the size of it cannot be calculated unless load all content of the stream into memory(the default behavior, but loading all data into memory could be a very bad idea). Pass opts.size to avoid loading all data into memory, or a warning will be shown.
  • opts.suppressSizeWarning {Boolean} - Pass true to suppress the size warning mentioned.

Tgz params:

tgz.FileStream is a combination of tar.FileStream and gzip.FileStream, so the params are the combination of params of tar and gzip.

Zip params:

  • opts.relativePath {String} - Adds a file from source into the compressed result file as opts.relativePath. Uncompression programs would extract the file from the compressed file as relativePath. If opts.source is a file path, opts.relativePath is optional, otherwise it's required.
  • opts.yazl {Object} - zip.FileStream compression uses yazl, pass this param to control the behavior of yazl.

Stream

The readable stream to compress anything as you need.

Note: If you are not very familiar with streams, just use compressFile() and compressDir() API, error can be handled in one place.

Gzip only support compressing a single file. So gzip.Stream is not available.

Constructor

  • new tar.Stream()
  • new tgz.Stream()
  • new zip.Stream()

No options in all constructors.

Instance methods

  • addEntry(entry, opts)

Params

  • entry {String|Buffer|Stream} - entry to compress, cound be a file path, a dir path, a buffer, or a stream.
  • opts.relativePath {String} - uncompression programs would extract the file from the compressed file as opts.relativePath. If entry is a file path or a dir path, opts.relativePath is optional, otherwise it's required.
  • opts.ignoreBase {Boolean} - when entry is a dir path, and opts.ignoreBase is set to true, the compression will contain files relative to the path passed, and not with the path included.

UncompressStream

The writable stream to uncompress anything as you need.

Note: If you are not very familiar with streams, just use uncompress() API, error can be handled in one place.

Gzip only support compressing and uncompressing one single file. So gzip.UncompressStream is a transform stream which is different from others.

Constructor

  • new gzip.UncompressStream(opts)
  • new tar.UncompressStream(opts)
  • new tgz.UncompressStream(opts)
  • new zip.UncompressStream(opts)

Common params:

  • opts.source {String|Buffer|Stream} - source to be uncompressed, could be a file path, buffer, or a readable stream.
  • opts.strip {Number} - Strip leading path segments when extracting (tar/tgz/zip). Default is 0.

CAUTION for zip.UncompressStream

Due to the design of the .zip file format, it's impossible to interpret a .zip file without loading all data into memory.

Although the API is streaming style(try to keep it handy), it still loads all data into memory.

https://github.com/thejoshwolfe/yauzl#no-streaming-unzip-api

Contributors

Contributors

Made with contributors-img.