adm-zip、archiver、node-zip、yauzl、yazl 和 zip-lib 都是 Node.js 生态中用于处理 ZIP 压缩文件的流行库,但它们的架构理念、性能特征和适用场景截然不同。adm-zip 倾向于将整个文件加载到内存中进行操作,适合处理小文件的快速读写;archiver 采用流式架构,专为处理大文件或实时数据传输设计;node-zip 是一个较旧的封装库,目前已不再推荐用于新项目;yauzl 和 yazl 分别提供了极致的流式解压和压缩能力,强调底层控制和内存效率;zip-lib 则试图在易用性和流式支持之间寻找平衡。理解这些库的核心差异对于构建高性能、低内存占用的后端服务或构建工具至关重要。
在 Node.js 后端开发中,处理 ZIP 文件是构建工具、数据导出服务和文件管理系统的常见需求。然而,不同的库在底层实现上有着本质的区别:有的将整个文件吞入内存,有的则像流水线一样逐字节处理。选错库可能导致服务器内存飙升甚至崩溃。本文将深入对比 adm-zip、archiver、node-zip、yauzl、yazl 和 zip-lib,帮助你做出正确的架构决策。
这是选择 ZIP 库时最关键的分水岭。你的应用是需要处理几 KB 的配置文件,还是几百 MB 的数据备份?
adm-zip 采用全量内存模式。它在读取时会一次性将整个 ZIP 文件加载到 RAM 中,写入时也是如此。这使得 API 非常简单,可以直接随机访问文件列表,但代价是内存消耗与文件大小成正比。
// adm-zip: 整个文件被加载到内存
const AdmZip = require('adm-zip');
const zip = new AdmZip('./large-archive.zip');
// 此时整个 large-archive.zip 已进入内存
const entries = zip.getEntries();
zip.extractAllTo('./output', true);
archiver 采用流式架构。它不会等待所有文件准备就绪,而是边读取边压缩,数据像水流一样通过管道。这使得它可以处理远超可用内存大小的文件。
// archiver: 数据流式处理,内存占用恒定
const archiver = require('archiver');
const output = fs.createWriteStream('./archive.zip');
const archive = archiver('zip');
archive.pipe(output);
// 文件被分块读取并压缩,不会一次性加载
archive.file('./large-file.log', { name: 'log.txt' });
archive.finalize();
node-zip 同样倾向于全量处理,且基于较旧的接口设计。它通常需要将数据作为字符串或 Buffer 整体传入,缺乏现代流式处理的灵活性。
// node-zip: 整体数据处理
const zip = require('node-zip')();
zip.file('test.txt', 'hello');
// 生成整个 ZIP 的 Buffer
const data = zip.generate({ base64: false, compression: 'DEFLATE' });
yauzl 是纯粹的流式读取器。它专为解压设计,允许你逐个条目(entry)地打开流,极大降低了内存峰值。
// yauzl: 流式打开每个条目
const yauzl = require('yauzl');
yauzl.open('./large-archive.zip', { lazyEntries: true }, (err, zipfile) => {
zipfile.on('entry', (entry) => {
// 只有需要时才创建读取流
zipfile.openReadStream(entry, (err, readStream) => {
readStream.pipe(fs.createWriteStream(entry.fileName));
});
});
zipfile.readEntry();
});
yazl 是纯粹的流式写入器。它不关心文件是否存在于磁盘,你可以通过流直接喂给它数据。
// yazl: 流式添加数据
const yazl = require('yazl');
const zipfile = new yazl.ZipFile();
zipfile.outputStream.pipe(fs.createWriteStream('output.zip'));
// 直接添加流,无需中间文件
zipfile.addReadStream(fs.createReadStream('source.txt'), 'dest.txt');
zipfile.end();
zip-lib 试图结合两者,提供基于 Promise 的流式支持。它既支持类似 adm-zip 的简单调用,也支持流式管道,但在极端大文件场景下的优化略逊于 archiver。
// zip-lib: 支持流式打包
const { pack } = require('zip-lib');
// 支持传入文件列表,内部处理流
await pack(['file1.txt', 'file2.txt'], 'output.zip');
// 也支持更底层的流操作
在实际工程中,我们往往需要压缩动态生成的内容(如数据库导出)而非仅仅是磁盘上的静态文件。
adm-zip 主要依赖文件系统路径。虽然它支持通过 Buffer 添加文件,但这意味着你需要先在内存中生成整个文件内容。
// adm-zip: 添加文件路径或 Buffer
const zip = new AdmZip();
zip.addLocalFile('./config.json'); // 从磁盘读取
zip.addFile('dynamic.txt', Buffer.from('Generated content')); // 需完整 Buffer
archiver 极其灵活,支持路径、流、Buffer 甚至子归档。它的 append 方法可以统一处理多种数据源。
// archiver: 统一接口处理多种源
const archive = archiver('zip');
archive.file('./static.txt', { name: 'static.txt' }); // 文件路径
archive.append(fs.createReadStream('./stream.txt'), { name: 'stream.txt' }); // 流
archive.append('string content', { name: 'text.txt' }); // 字符串
node-zip 接口较为单一,主要接受字符串或 Buffer。处理文件路径时需要配合 fs 模块手动读取,增加了样板代码。
// node-zip: 手动读取后添加
const content = fs.readFileSync('./file.txt');
zip.file('file.txt', content);
yauzl 仅负责解压,不涉及添加文件的逻辑。
yazl 专为流和 Buffer 设计。它没有“添加本地文件”的快捷方法,强制你显式地创建读取流,这反而保证了性能的可控性。
// yazl: 显式添加流或 Buffer
const zipfile = new yazl.ZipFile();
zipfile.addFile('./local.txt', 'remote.txt'); // 内部会创建流
zipfile.addBuffer(Buffer.from('data'), 'buffer.txt');
zipfile.addReadStream(myCustomStream, 'stream.txt');
zip-lib 提供了高层抽象,适合批量添加文件,但对于极度动态的流式数据,其 API 不如 archiver 直观。
// zip-lib: 批量添加
await pack(['file1', 'file2'], 'out.zip');
// 单个文件添加需使用 unzip/zip 对象实例
如果你需要快速读取 ZIP 包中的某个特定文件而不解压全部内容,库的选择至关重要。
adm-zip 擅长随机访问。由于文件已在内存中,你可以瞬间获取任何条目的内容,非常适合读取小型配置包或资源包。
// adm-zip: 瞬间读取特定文件
const zip = new AdmZip('./archive.zip');
const content = zip.readAsText('config/settings.json');
// 无需遍历,直接访问
archiver 不支持解压。它是一个单向的压缩工具。如果需要解压,必须配合 unzipper 或 yauzl 使用。
// archiver: 无解压功能
// 必须引入其他库如 unzipper 来处理解压任务
node-zip 支持解压,但通常也是全量加载后提取,缺乏流式提取的精细控制。
// node-zip: 全量解析
zip.load(data);
const fileContent = zip.files['test.txt'].asText();
yauzl 是流式解压的王者。它支持 lazyEntries 模式,允许你在遍历过程中决定是否读取某个文件,甚至可以根据文件名跳过不需要的文件,极大节省 I/O。
// yauzl: 条件式流式解压
yauzl.open('./archive.zip', { lazyEntries: true }, (err, zipfile) => {
zipfile.on('entry', (entry) => {
if (entry.fileName.endsWith('.log')) {
// 只解压日志文件
zipfile.openReadStream(entry, (err, stream) => stream.pipe(...));
} else {
// 跳过其他文件,不消耗 I/O
zipfile.readEntry();
}
});
zipfile.readEntry();
});
yazl 不支持解压。它是专用的压缩库。
zip-lib 提供了解压功能,支持流式提取,API 比 yauzl 更现代化(基于 Promise),但在极端定制化解压逻辑上略逊一筹。
// zip-lib: Promise 风格的解压
const { unpack } = require('zip-lib');
await unpack('./archive.zip', './output-dir');
// 支持更细粒度的控制但文档相对较少
在 production 环境中,库的健壮性决定了系统的稳定性。
adm-zip 在处理损坏的 ZIP 文件时可能会抛出异常或行为不可预测。由于它是同步操作为主,错误处理需要使用 try-catch 块包裹,且在处理超大文件时容易触发 heap out of memory 错误。
// adm-zip: 需手动捕获同步错误
try {
const zip = new AdmZip('./corrupted.zip');
} catch (e) {
console.error('Failed to load zip', e);
}
archiver 拥有成熟的事件驱动错误处理。作为流,它通过 error 事件通知问题,能够优雅地处理管道中断或写入失败。
// archiver: 事件驱动的错误处理
archive.on('error', (err) => {
throw err;
});
output.on('close', () => {
console.log('Done');
});
node-zip 由于缺乏维护,对现代 Node.js 版本的兼容性较差,且在处理非标准 ZIP 头或特殊字符文件名时容易静默失败或崩溃。
yauzl 和 yazl 由同一作者维护,代码质量极高,严格遵循 ZIP 规范。它们对损坏文件的处理非常明确,会通过回调或事件报告具体错误,非常适合对数据完整性要求高的场景。
// yauzl: 明确的错误回调
yauzl.open('./bad.zip', (err) => {
if (err) {
// 明确知道是打开失败
console.error('Open failed:', err);
}
});
zip-lib 提供了基于 Promise 的错误处理,符合现代 async/await 习惯,但在面对极度畸形的 ZIP 文件时,其容错机制不如 yauzl 透明。
| 特性 | adm-zip | archiver | node-zip | yauzl | yazl | zip-lib |
|---|---|---|---|---|---|---|
| 核心模式 | 内存全量 | 流式管道 | 内存全量 | 流式读取 | 流式写入 | 混合/流式 |
| 内存占用 | 高 (与文件大小成正比) | 低 (恒定) | 高 | 极低 | 极低 | 低 |
| 压缩支持 | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| 解压支持 | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ |
| 随机访问 | ✅ 优秀 | ❌ | ⚠️ 一般 | ⚠️ 需遍历 | ❌ | ⚠️ 一般 |
| 维护状态 | 活跃 | 活跃 | ⚠️ 停止维护 | 活跃 | 活跃 | 活跃 |
| 最佳场景 | 小文件、脚本工具 | 大文件、HTTP 流 | 无 (遗留代码) | 专业解压工具 | 动态内容压缩 | 中等规模任务 |
node-zip:如果你的项目中还在使用它,请制定计划迁移。它已不再是安全或可靠的选择。archiver:如果你正在构建一个文件下载 API、备份服务或需要处理用户上传的大型压缩包,archiver 是唯一的生产级选择。它的流式特性能保护你的服务器内存。adm-zip:对于 CLI 工具、单元测试中的 Mock 数据生成或处理几 MB 以内的配置文件,adm-zip 的同步 API 能极大简化代码逻辑。yauzl/yazl:如果你需要构建一个专业的解压软件,或者需要将动态数据流(如日志、数据库dump)直接压缩上传且对依赖体积敏感,这两个库提供了最纯净、高效的底层能力。zip-lib:如果你需要 Promise 风格的 API 且处理的文件规模中等,zip-lib 是一个现代化的替代方案,但在生态丰富度上略逊于 archiver。在架构设计中,永远不要低估数据量增长带来的影响。今天能轻松放入内存的 50MB 文件,明天可能会变成 5GB 的日志归档。选择流式库(archiver, yauzl, yazl)通常是为未来扩展性留下的最佳保险。
如果你需要处理较小的文件(例如配置文件、小型构建产物)且追求 API 的简单直观,adm-zip 是一个不错的选择。它的同步 API 非常适合脚本任务或命令行工具,但由于它将整个 ZIP 文件加载到内存中,因此严禁用于处理大文件或高并发场景,以免导致内存溢出。
当你的应用场景涉及大文件处理、实时数据流(如直接管道传输到 HTTP 响应或云存储)或需要动态添加大量文件时,必须选择 archiver。它的流式架构确保了极低的内存占用,是构建文件下载服务、备份系统或大型构建流水线的行业标准。
不建议在任何新项目中选择 node-zip。该库已长期缺乏维护,存在已知的设计局限性和潜在的安全风险,且其功能完全可以被更现代、更活跃的替代品(如 adm-zip 或 archiver)所覆盖。请将其视为遗留代码库中的存在,并在重构时优先替换。
如果你需要极高的内存效率来解压大型 ZIP 文件,或者需要精细控制解压过程(例如只提取特定文件、处理加密条目或逐条读取元数据),yauzl 是最佳选择。它专注于“只读”场景,提供了比 adm-zip 更底层的流式访问能力,适合构建专业的解压工具或分析器。
当你需要一个轻量级、无依赖且完全基于流的 ZIP 创建工具时,应选择 yazl。它特别适合那些需要将动态生成的内容(如数据库查询结果、日志流)直接压缩并发送的场景。与 archiver 相比,它的 API 更底层,但换来了更小的体积和更少的抽象开销。
如果你希望获得比 adm-zip 更好的流式支持,同时又觉得 archiver 的插件系统过于复杂,zip-lib 提供了一个折中方案。它支持流式输入输出,API 设计相对现代,适合中等规模的文件处理任务,但在社区生态和高级功能(如复杂的加密支持)上可能不如 archiver 丰富。
ADM-ZIP is a pure JavaScript implementation for zip data compression for NodeJS.
With npm do:
$ npm install adm-zip
Electron file system support described below.
The library allows you to:
There are no other nodeJS libraries that ADM-ZIP is dependent of
var AdmZip = require("adm-zip");
// reading archives
var zip = new AdmZip("./my_file.zip");
var password = "1234567890";
var zipEntries = zip.getEntries(); // an array of ZipEntry records - add password parameter if entries are password protected
zipEntries.forEach(function (zipEntry) {
console.log(zipEntry.toString()); // outputs zip entries information
if (zipEntry.entryName == "my_file.txt") {
console.log(zipEntry.getData().toString("utf8"));
}
});
// outputs the content of some_folder/my_file.txt
console.log(zip.readAsText("some_folder/my_file.txt"));
// extracts the specified file to the specified location
zip.extractEntryTo(/*entry name*/ "some_folder/my_file.txt", /*target path*/ "/home/me/tempfolder", /*maintainEntryPath*/ false, /*overwrite*/ true);
// extracts everything
zip.extractAllTo(/*target path*/ "/home/me/zipcontent/", /*overwrite*/ true);
// creating archives
var zip = new AdmZip();
// add file directly
var content = "inner content of the file";
zip.addFile("test.txt", Buffer.from(content, "utf8"), "entry comment goes here");
// add local file
zip.addLocalFile("/home/me/some_picture.png");
// get everything as a buffer
var willSendthis = zip.toBuffer();
// or write everything to disk
zip.writeZip(/*target file name*/ "/home/me/files.zip");
// ... more examples in the wiki
For more detailed information please check out the wiki.
ADM-ZIP has supported electron original-fs for years without any user interractions but it causes problem with bundlers like rollup etc. For continuing support original-fs or any other custom file system module. There is possible specify your module by fs option in ADM-ZIP constructor.
Example:
const AdmZip = require("adm-zip");
const OriginalFs = require("original-fs");
// reading archives
const zip = new AdmZip("./my_file.zip", { fs: OriginalFs });
.
.
.
Please report security vulnerabilities privately. See SECURITY.md.