adm-zip、extract-zip、node-unzip-2 和 unzipper 都是用于在 Node.js 环境中处理 ZIP 压缩文件的 npm 包,但它们在功能范围、API 设计和使用场景上有显著差异。adm-zip 提供完整的 ZIP 读写能力,支持内存操作和文件系统交互;extract-zip 专注于将 ZIP 文件安全地解压到指定目录,常用于工具链(如 Puppeteer);node-unzip-2 是早期 node-unzip 的社区维护分支,提供流式解压能力;unzipper 则是一个现代、流式优先的库,支持异步迭代器和内存高效处理。这些库都面向后端或构建时任务,而非浏览器环境。
在 Node.js 开发中,处理 ZIP 文件是常见需求 —— 无论是解压用户上传的归档、动态生成部署包,还是集成第三方二进制工具。面对 adm-zip、extract-zip、node-unzip-2 和 unzipper 这四个主流选项,如何根据真实工程场景做出合理选型?本文从核心能力、内存模型、API 风格和适用边界出发,提供可落地的技术判断。
node-unzip-2 是原始 node-unzip 包的社区分支,而 node-unzip 早在多年前已被作者标记为 deprecated。尽管 node-unzip-2 在 npm 上仍可安装,但其 GitHub 仓库长期缺乏实质性更新,存在已知的内存泄漏和路径遍历漏洞(如未正确处理 .. 路径)。强烈建议新项目完全避开此包,已有项目应尽快迁移至 unzipper 或 extract-zip。
如果你的任务类似于 “下载 ChromeDriver 并解压到 .local/bin”,那么你只需要一个 安全、可靠的解压器,无需读取或修改 ZIP 内容。
extract-zip 是此类场景的黄金标准。它专为此设计,内部使用 yauzl(一个只读、流式、注重安全的 ZIP 解析器),自动处理路径规范化,防止目录遍历攻击。
// extract-zip:简单安全的全量解压
const extract = require('extract-zip');
await extract('archive.zip', { dir: './output' });
// 所有文件被解压到 ./output,路径已安全校验
其他库也能实现类似功能,但需手动处理安全细节:
// adm-zip:需自行验证路径
const AdmZip = require('adm-zip');
const zip = new AdmZip('archive.zip');
zip.getEntries().forEach(entry => {
if (!entry.isDirectory && !entry.entryName.includes('..')) {
zip.extractEntryTo(entry, './output', false, true);
}
});
// unzipper:需手动遍历并写入
const fs = require('fs');
const path = require('path');
const unzipper = require('unzipper');
await fs.createReadStream('archive.zip')
.pipe(unzipper.Extract({ path: './output' }))
.promise();
💡 结论:若只需“解压到目录”,无脑选
extract-zip—— 它少即是多。
如果你要动态向 ZIP 添加文件、读取特定条目内容,或在内存中构建 ZIP 后返回给客户端,则需要 完整的读写能力。
adm-zip 是唯一提供双向操作的库。它将整个 ZIP 加载到内存,允许增删改查,最后可写回文件或 Buffer。
// adm-zip:创建并修改 ZIP
const AdmZip = require('adm-zip');
const zip = new AdmZip();
zip.addFile('report.txt', Buffer.from('Hello World'));
zip.addLocalFile('./config.json');
// 读取现有条目
const entry = zip.getEntry('report.txt');
console.log(entry.getData().toString()); // 'Hello World'
// 写入磁盘
zip.writeZip('output.zip');
其他库均 不支持写入:
// extract-zip:只读,无 API 可写入
// node-unzip-2 / unzipper:仅支持解压,无法创建 ZIP
💡 结论:若需编辑 ZIP,
adm-zip是唯一选择,但要警惕大文件内存占用。
处理 100MB+ 的 ZIP 文件时,内存模型决定成败。
adm-zip 将整个 ZIP 加载到内存。对于大文件,这可能导致 OOM(内存溢出)。extract-zip、unzipper 基于流式解析,内存占用恒定,适合大文件。node-unzip-2 虽声称流式,但实现有缺陷,易泄漏内存。// unzipper:按需读取条目,内存友好
const unzipper = require('unzipper');
const directory = await unzipper.Open.file('large.zip');
const file = directory.files.find(f => f.path === 'data.csv');
if (file) {
const content = await file.buffer(); // 仅加载该文件到内存
console.log(content.toString());
}
// adm-zip:整个 ZIP 被加载
const AdmZip = require('adm-zip');
const zip = new AdmZip('large.zip'); // 整个文件进内存!
const entry = zip.getEntry('data.csv');
const content = entry.getData(); // 已在内存中
💡 结论:大文件或内存敏感场景,优先
unzipper;小文件且需编辑,用adm-zip。
现代开发偏好 async/await 和流式接口。
extract-zip:仅提供 Promise API,简单直接。unzipper:全面支持 Promise、Stream 和 Async Iterator(ES2018),适合现代流水线。adm-zip:同步 API 为主,部分方法有异步版本(如 writeZipPromise),但整体偏传统。node-unzip-2:仅支持回调(Callback),不符合现代规范。// unzipper:现代异步迭代
const unzipper = require('unzipper');
for await (const entry of fs.createReadStream('archive.zip').pipe(unzipper.Parse())) {
const fileName = entry.path;
if (fileName.endsWith('.json')) {
const content = await entry.buffer();
processJSON(content);
} else {
entry.autodrain(); // 丢弃不需要的文件
}
}
其他库无法如此优雅地实现按需处理:
// adm-zip:必须先加载全部
const zip = new AdmZip('archive.zip');
zip.getEntries().filter(e => e.entryName.endsWith('.json'))...
// extract-zip:无法访问单个条目
💡 结论:若需精细控制解压过程(如过滤、转换),
unzipper的流式 API 是最佳选择。
ZIP 文件可能包含恶意路径(如 ../../../etc/passwd),安全的库会自动规范化路径。
extract-zip:内置防护,拒绝包含 .. 的路径。unzipper:通过 sanitize 选项默认启用路径清理。adm-zip:不自动防护,需开发者自行校验 entry.entryName。node-unzip-2:无有效防护,存在高危漏洞。// unzipper:安全默认
fs.createReadStream('malicious.zip')
.pipe(unzipper.Extract({ path: './safe' }))
// 自动阻止 ../../../ 攻击
// adm-zip:需手动检查
zip.getEntries().forEach(entry => {
if (entry.entryName.includes('..')) return; // 必须自己做
zip.extractEntryTo(...);
});
💡 结论:处理不可信 ZIP 源时,优先选
extract-zip或unzipper。
| 需求场景 | 推荐库 | 理由 |
|---|---|---|
| 解压整个 ZIP 到目录 | extract-zip | 专注、安全、零配置 |
| 读取/修改 ZIP 内容 | adm-zip | 唯一支持完整读写 |
| 流式处理大 ZIP 文件 | unzipper | 内存恒定,支持异步迭代 |
| 构建数据处理流水线 | unzipper | Stream + Async Iterator 完美契合 |
| 旧项目临时兼容 | 避免 node-unzip-2 | 已废弃,有安全风险 |
extract-zip —— 它解决了最常见、最危险的问题。adm-zip,但限制文件大小(<50MB),并手动校验路径。unzipper,发挥其流式优势。node-unzip-2 —— 技术债不值得背。记住:没有“最好”的库,只有“最合适”当前任务的工具。明确你的需求边界,就能避开大多数坑。
选择 adm-zip 如果你需要同时读取和创建 ZIP 文件,并且希望在内存中直接操作 ZIP 条目(例如动态添加/修改文件后再写入磁盘)。它适合需要完整 ZIP 编辑能力的场景,比如生成带签名的部署包或解析嵌套压缩结构。但注意它不基于流,大文件可能占用较多内存。
选择 extract-zip 如果你只需要将整个 ZIP 文件安全、可靠地解压到目标目录,且无需访问单个条目内容。它被广泛用于自动化工具(如下载并解压二进制依赖),内部使用 yauzl 实现只读解压,安全性高,但不支持创建或修改 ZIP 文件。
选择 node-unzip-2 仅当你必须兼容旧项目且无法升级时。它是已废弃的 node-unzip 的非官方延续,缺乏活跃维护,存在潜在安全风险和内存泄漏问题。新项目应避免使用,优先考虑 unzipper 或 extract-zip。
选择 unzipper 如果你需要流式处理 ZIP 文件(例如边下载边解压)、逐条目访问内容,或使用现代 async/await 与 for-await-of 语法。它内存效率高,支持管道(pipe)和事件驱动模型,适合处理大文件或构建数据处理流水线。
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.