adm-zip、extract-zip、node-unzip-2、node-zip、unzipper 和 yauzl 都是 Node.js 生态中用于处理 ZIP 文件的 npm 包,但它们在功能范围、API 设计和适用场景上有显著差异。部分库专注于 ZIP 文件的解压(如 extract-zip 和 yauzl),部分支持完整的读写操作(如 adm-zip 和 node-zip),还有一些提供了流式处理能力以应对大文件(如 unzipper 和 yauzl)。这些库共同解决了在服务端处理压缩归档文件的常见需求,包括文件提取、内存解析、动态打包等,但开发者需根据具体任务(如是否需要写入、是否处理大文件、是否要求路径安全)选择最合适的工具。
在 Node.js 开发中,处理 ZIP 文件是常见需求 —— 无论是解压用户上传的归档、生成下载包,还是解析依赖资源。但面对 adm-zip、extract-zip、node-unzip-2、node-zip、unzipper 和 yauzl 这些库,开发者常陷入选择困境。它们功能重叠却各有侧重,有的擅长写入,有的专注读取;有的简单直接,有的支持流式处理。本文从真实工程场景出发,深入比较这些库的核心能力、API 设计与适用边界。
首先明确一点:并非所有库都同时支持压缩与解压。这是选型的第一道分水岭。
adm-zip:提供完整的 ZIP 文件创建、修改和提取能力。node-zip:基于 JSZip 封装,支持内存中的 ZIP 构建与解析。unzipper:虽以“解压”命名,但其实也支持 ZIP 写入(通过 ZipWriter)。extract-zip:专为将 ZIP 文件解压到磁盘目录而设计,不支持内存操作或压缩。yauzl:纯只读库,专注于高效、低内存地读取 ZIP 内容,不提供任何写入 API。node-unzip-2:仅支持解压,且主要面向流式处理 ZIP 条目。⚠️ 注意:
node-unzip-2是node-unzip的社区维护分支,原项目已不再活跃。虽然它仍可工作,但在新项目中建议优先评估更现代的替代品。
最常见的需求是“把 ZIP 文件解压到某个文件夹”。此时,extract-zip 是最直接的选择。
// extract-zip: 一行代码解压到目录
const extract = require('extract-zip');
await extract('archive.zip', { dir: './output' });
相比之下,其他库需要更多代码:
// adm-zip: 需要显式调用 extractAllTo
const AdmZip = require('adm-zip');
const zip = new AdmZip('archive.zip');
zip.extractAllTo('./output');
// unzipper: 需要 pipe 到目录流
const unzipper = require('unzipper');
await fs.createReadStream('archive.zip')
.pipe(unzipper.Extract({ path: './output' }))
.promise();
// yauzl: 必须手动遍历条目并写入文件
const yauzl = require('yauzl');
yauzl.open('archive.zip', (err, zipfile) => {
zipfile.on('entry', (entry) => {
// 手动处理每个 entry,创建目录、写入文件等
});
});
结论:如果只是简单解压到磁盘,extract-zip 最省事。若需精细控制(如过滤文件、转换内容),则需 unzipper 或 yauzl。
当需要在内存中动态生成 ZIP(如打包多个 Buffer)或解析 ZIP 内容而不写入磁盘时,adm-zip 和 node-zip 更合适。
// adm-zip: 添加 Buffer 并获取最终 Buffer
const zip = new AdmZip();
zip.addFile('file.txt', Buffer.from('Hello'));
const buffer = zip.toBuffer();
// node-zip: 基于 JSZip,链式 API
const Zip = require('node-zip');
const zip = new Zip();
zip.file('file.txt', 'Hello');
const buffer = zip.generate({ type: 'nodebuffer' });
而 unzipper 也能读取内存中的 ZIP:
// unzipper: 从 Buffer 解析
const directory = await unzipper.Open.buffer(buffer);
const fileContents = await directory.files[0].buffer();
yauzl 同样支持 Buffer 输入,但需自行管理流:
// yauzl: 从 Buffer 读取
yauzl.fromBuffer(buffer, (err, zipfile) => {
zipfile.on('entry', (entry) => {
zipfile.openReadStream(entry, (err, readStream) => {
// 处理流
});
});
});
处理大型 ZIP 文件时,内存占用是关键。yauzl 和 unzipper 原生支持流式读取,避免一次性加载整个文件。
// yauzl: 流式读取(推荐用于大文件)
const yauzl = require('yauzl');
yauzl.open('large.zip', (err, zipfile) => {
zipfile.on('entry', (entry) => {
zipfile.openReadStream(entry, (err, readStream) => {
readStream.pipe(process.stdout); // 或写入文件
});
});
});
// unzipper: 同样支持流
fs.createReadStream('large.zip')
.pipe(unzipper.Parse())
.on('entry', (entry) => {
entry.pipe(process.stdout);
});
而 adm-zip 和 node-zip 默认将整个 ZIP 加载到内存,不适合大文件:
// adm-zip: 整个文件加载到内存
const zip = new AdmZip('large.zip'); // 可能 OOM
结论:处理 >100MB 的 ZIP 文件时,优先选择 yauzl 或 unzipper。
ZIP 文件可能包含路径遍历攻击(如 ../../../etc/passwd)。安全的库会自动规范化路径。
extract-zip:内置路径校验,防止目录遍历。unzipper:Extract 模式默认启用 forceStream: true 和路径检查。yauzl:不处理路径安全,需开发者自行验证 entry.fileName。adm-zip:extractAllTo() 方法有 overwrite 和 keepOriginalPermission 选项,但路径安全需谨慎处理。// extract-zip: 自动防御路径遍历
await extract('malicious.zip', { dir: './safe' }); // 即使 ZIP 内含 ../ 也会被限制在 ./safe
因此,在处理不可信来源的 ZIP 时,extract-zip 和 unzipper 更安全。
adm-zip:面向对象,API 直观,适合快速原型开发。extract-zip:单一用途,API 极简。node-zip:封装 JSZip,熟悉前端 JSZip 的开发者上手快。unzipper:支持 Promise、流、事件,灵活但略复杂。yauzl:底层、回调风格,学习曲线陡峭,但控制力最强。node-unzip-2:流式 API,但文档较少,社区支持弱。| 场景 | 推荐库 | 理由 |
|---|---|---|
| 简单解压 ZIP 到磁盘 | extract-zip | 一行代码搞定,内置安全防护 |
| 内存中构建/修改 ZIP | adm-zip 或 node-zip | 完整读写 API,易于使用 |
| 流式处理大 ZIP 文件 | yauzl 或 unzipper | 低内存占用,适合生产环境 |
| 精细控制 ZIP 条目 | unzipper | 支持 Promise、流、事件,功能全面 |
| 仅需读取 ZIP 元数据 | yauzl | 轻量、高效、无多余依赖 |
| 避免使用 | node-unzip-2 | 社区维护,功能已被 unzipper 覆盖 |
unzipper:它平衡了功能、安全性和现代 API(Promise/流),既能解压到磁盘,也能处理内存和流式场景。extract-zip:简单任务不必过度设计。adm-zip:成熟稳定,API 清晰。yauzl:但准备好写更多胶水代码。记住:没有“最好”的库,只有“最合适”当前场景的工具。理解每个库的设计边界,才能在工程中做出稳健选择。
选择 adm-zip 如果你需要一个功能完整的 ZIP 库,既能读取也能创建或修改 ZIP 文件,并且偏好面向对象的同步 API。它适合中小型 ZIP 文件的内存操作,例如动态生成包含多个文本或二进制文件的压缩包。但注意它会将整个 ZIP 加载到内存,不适合处理大型文件。
选择 extract-zip 如果你的唯一需求是将 ZIP 文件安全地解压到指定目录,且不需要任何内存解析或流式处理。它提供了最简单的 API,内置路径遍历防护,非常适合处理用户上传的 ZIP 文件并提取到磁盘的场景。
选择 node-unzip-2 仅当你维护一个已有使用该库的旧项目,且无法轻易迁移。作为 node-unzip 的社区分支,它支持流式解压,但缺乏活跃维护和现代 Promise API。在新项目中应优先考虑 unzipper 或 yauzl。
选择 node-zip 如果你熟悉前端 JSZip 库,并希望在 Node.js 中使用类似的链式 API 来构建或解析内存中的 ZIP 对象。它适合需要与前端共享 ZIP 逻辑的全栈项目,但同样会将整个 ZIP 加载到内存,不适合大文件处理。
选择 unzipper 如果你需要一个现代、灵活且功能全面的 ZIP 库,支持 Promise、流式处理、内存解析和磁盘解压。它既能安全地提取文件,也能通过 ZipWriter 创建 ZIP,适用于大多数生产场景,尤其是需要处理不可信来源 ZIP 或大文件的场合。
选择 yauzl 如果你追求极致的性能和低内存占用,且愿意编写更多底层代码来处理 ZIP 条目。它是纯只读库,专为流式读取大型 ZIP 文件而设计,适合需要精细控制解压过程或仅需读取元数据的高性能应用。
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.