archiver、compressing、tar、tar-stream 和 zip-stream 都是用于在 Node.js 环境中处理文件归档和压缩的核心工具。archiver 是一个流式归档接口,支持多种格式(如 zip 和 tar),适合动态生成大型归档文件;compressing 是蚂蚁金服出品的高层封装库,提供同步和异步的简单 API,适合快速实现文件压缩需求;tar 是处理 tar 格式的标准库,专注于 POSIX tar 规范的解析与生成;tar-stream 提供基于流的 tar 构建与提取能力,适合处理流式数据管道;zip-stream 则专注于生成 zip 格式的流,通常作为 archiver 的底层依赖或独立用于轻量级 zip 生成。这些库共同解决了前端构建工具、后端文件服务及数据备份场景中的归档压缩问题。
在 Node.js 生态中,处理文件归档和压缩是构建工具、备份系统和文件服务的常见需求。虽然 Promise.all 或简单的 fs 操作能解决部分问题,但面对大文件、流式数据或多格式支持时,我们需要更专业的工具。archiver、compressing、tar、tar-stream 和 zip-stream 正是为此而生。它们的设计哲学不同:有的追求高层易用性,有的专注底层流控制。让我们从实际工程角度拆解它们的差异。
compressing 是典型的高层封装库。它隐藏了流和缓冲区的细节,提供类似 fs 的同步/异步 API。
// compressing: 一行代码压缩文件夹
const compressing = require('compressing');
await compressing.zip.compressDir('./source', './output.zip');
archiver 则是一个流式归档引擎。它不直接操作文件系统,而是通过流管道处理数据,适合动态内容。
// archiver: 流式创建 ZIP
const archiver = require('archiver');
const output = fs.createWriteStream('./output.zip');
const archive = archiver('zip', { zlib: { level: 9 } });
archive.pipe(output);
archive.directory('./source', false);
await archive.finalize();
tar 和 tar-stream 专注于 tar 格式,但层级不同。tar 提供高层方法(如 create/extract),而 tar-stream 暴露原始的流事件。
// tar: 高层 API 创建 tar 包
const tar = require('tar');
await tar.create({ gzip: true, file: './output.tar.gz' }, ['./source']);
// tar-stream: 底层流式构建
const tarStream = require('tar-stream');
const pack = tarStream.pack();
const output = fs.createWriteStream('./output.tar');
pack.pipe(output);
pack.entry({ name: 'file.txt', size: 12 }, (err, stream) => {
stream.write('Hello World');
stream.end();
});
pack.finalize();
zip-stream 是 archiver 的底层依赖之一,专注于生成 ZIP 流,没有多格式支持。
// zip-stream: 纯 ZIP 流生成
const ZipStream = require('zip-stream');
const output = fs.createWriteStream('./output.zip');
const zip = new ZipStream();
zip.pipe(output);
zip.entry(fs.createReadStream('./file.txt'), { name: 'file.txt' });
zip.finalize();
处理大文件时,内存管理是关键。
compressing 在压缩大目录时可能占用较高内存,因为它内部会缓冲部分元数据。适合中小规模文件。archiver、tar-stream 和 zip-stream 基于流设计,数据分块处理,内存占用恒定。适合 GB 级文件或网络流。tar 在启用 gzip 时也是流式的,但默认行为依赖具体方法。💡 提示:如果你的应用运行在内存受限的环境(如 Serverless 函数),优先选择流式库(
archiver或tar-stream)。
archiver: 支持 zip, tar, tar.gz, tar.bz2 等,通过插件机制扩展。compressing: 支持 zip, tar, gzip, bzip2,但每种格式有独立方法。tar / tar-stream / zip-stream: 仅支持单一格式(tar 或 zip)。// archiver: 轻松切换格式
const archiveZip = archiver('zip');
const archiveTar = archiver('tar');
// compressing: 不同格式调用不同方法
await compressing.zip.compressDir(...);
await compressing.tar.compressDir(...);
当需要压缩非文件系统内容(如数据库查询结果、内存字符串)时:
archiver 和 zip-stream 允许直接传入 Buffer 或流。tar-stream 需要手动构造 entry 流。compressing 和 tar 主要面向文件系统路径,动态内容支持较弱。// archiver: 添加内存字符串
archive.append('Dynamic Content', { name: 'dynamic.txt' });
// tar-stream: 手动创建流
pack.entry({ name: 'dynamic.txt', size: 15 }, (err, stream) => {
stream.write('Dynamic Content');
stream.end();
});
archiver 提供丰富的事件(error, warning, progress),适合监控长任务。tar-stream 暴露底层事件,适合精细控制。compressing 通过 Promise reject 抛出错误,简单但缺乏中间状态。// archiver: 监听进度
archive.on('progress', (data) => {
console.log(`Processed ${data.entries.processed} files`);
});
// compressing: 简单 try/catch
try {
await compressing.zip.compressDir(...);
} catch (err) {
console.error('Compression failed', err);
}
你需要将构建产物打包成 ZIP 供下载,文件可能动态生成。
archiverconst archive = archiver('zip');
archive.pipe(res); // 直接管道到 HTTP 响应
buildFiles.forEach(file => archive.append(file.content, { name: file.path }));
await archive.finalize();
编写一个 CLI 脚本,定期压缩日志目录。
compressingawait compressing.tar.compressDir('/var/logs', './logs-backup.tar.gz');
将 Kafka 消息流实时打包成 tar 文件存储到 S3。
tar-streamconst pack = tarStream.pack();
pack.pipe(s3UploadStream);
kafkaStream.on('data', (msg) => {
pack.entry({ name: `msg-${Date.now()}.json`, size: msg.length }, (err, stream) => {
stream.write(msg);
stream.end();
});
});
需要与 Linux 系统严格兼容的 tar 包。
tarawait tar.create({ gzip: true, portable: true, file: './backup.tar.gz' }, ['./data']);
compressing 的同步阻塞风险:虽然它提供同步 API,但在主线程中压缩大文件会阻塞事件循环。务必在 Worker 线程或使用异步方法。archiver 的 finalize 遗忘:忘记调用 archive.finalize() 会导致输出文件不完整。这是新手常见错误。tar-stream 的手动大小计算:添加 entry 时必须预先知道数据大小(size 字段),否则需使用 entry 的流模式。zip-stream 的单一性:它只做 ZIP,如果需要 tar 或其他格式,需额外引入库,不如直接用 archiver。| 特性 | archiver | compressing | tar | tar-stream | zip-stream |
|---|---|---|---|---|---|
| 主要格式 | Zip, Tar, Gzip 等 | Zip, Tar, Gzip 等 | Tar 仅 | Tar 仅 | Zip 仅 |
| API 层级 | 中高层 (流式) | 高层 (同步/异步) | 高层 | 底层 (流式) | 底层 (流式) |
| 内存效率 | ⭐⭐⭐⭐⭐ (流式) | ⭐⭐⭐ (部分缓冲) | ⭐⭐⭐⭐ (流式) | ⭐⭐⭐⭐⭐ (流式) | ⭐⭐⭐⭐⭐ (流式) |
| 动态内容支持 | ✅ 优秀 | ❌ 较弱 | ❌ 较弱 | ✅ 优秀 (手动) | ✅ 优秀 |
| 学习曲线 | 中等 | 低 | 低 | 高 | 中高 |
| 典型用途 | 动态归档、HTTP 流 | 脚本、CLI 工具 | 标准 Tar 操作 | 自定义流管道 | 轻量级 ZIP 生成 |
选择库的核心在于数据流向和控制粒度:
compressing 或 tar。archiver(多格式)或 tar-stream/zip-stream(单格式)。tar-stream 的底层事件。这些工具没有绝对的优劣,只有场景的匹配。理解它们的流式本质,能让你的 Node.js 应用在文件处理上更加稳健高效。
选择 archiver 当你需要以流式方式动态生成大型归档文件(如 ZIP 或 TAR),且希望在一个统一的 API 下切换不同格式。它非常适合构建工具、备份系统或需要边读取边压缩的场景,能有效控制内存占用。
选择 compressing 如果你追求开发效率,需要简单的同步/异步 API 来快速完成文件或文件夹的压缩与解压,而不关心底层的流式细节。它适合脚本工具、CLI 应用或对性能要求不极端的常规业务场景。
选择 tar 当你只需要处理标准的 tar 格式,且希望使用社区最广泛认可、维护最稳定的专用库。它适合需要严格遵循 POSIX tar 规范或与 Unix 生态系统深度集成的项目。
选择 tar-stream 当你需要在流式管道中精细控制 tar 包的构建或提取过程,例如将数据库流直接打包或从网络流中实时解压。它比 tar 更底层,适合高度定制化的流处理架构。
选择 zip-stream 当你只需要生成 ZIP 格式的流,且不需要 archiver 的多格式支持或高层抽象。它适合作为轻量级依赖嵌入到自定义的流式处理管道中,专注于单一的 ZIP 生成任务。
A streaming interface for archive generation
Visit the API documentation for a list of all methods available.
npm install archiver --save
import fs from "fs";
import { ZipArchive } from "archiver";
// create a file to stream archive data to.
const output = fs.createWriteStream(__dirname + "/example.zip");
const archive = new ZipArchive({
zlib: { level: 9 }, // Sets the compression level.
});
// listen for all archive data to be written
// 'close' event is fired only when a file descriptor is involved
output.on("close", function () {
console.log(archive.pointer() + " total bytes");
console.log(
"archiver has been finalized and the output file descriptor has closed.",
);
});
// This event is fired when the data source is drained no matter what was the data source.
// It is not part of this library but rather from the NodeJS Stream API.
// @see: https://nodejs.org/api/stream.html#stream_event_end
output.on("end", function () {
console.log("Data has been drained");
});
// good practice to catch warnings (ie stat failures and other non-blocking errors)
archive.on("warning", function (err) {
if (err.code === "ENOENT") {
// log warning
} else {
// throw error
throw err;
}
});
// good practice to catch this error explicitly
archive.on("error", function (err) {
throw err;
});
// pipe archive data to the file
archive.pipe(output);
// append a file from stream
const file1 = __dirname + "/file1.txt";
archive.append(fs.createReadStream(file1), { name: "file1.txt" });
// append a file from string
archive.append("string cheese!", { name: "file2.txt" });
// append a file from buffer
const buffer3 = Buffer.from("buff it!");
archive.append(buffer3, { name: "file3.txt" });
// append a file
archive.file("file1.txt", { name: "file4.txt" });
// append files from a sub-directory and naming it `new-subdir` within the archive
archive.directory("subdir/", "new-subdir");
// append files from a sub-directory, putting its contents at the root of archive
archive.directory("subdir/", false);
// append files from a glob pattern
archive.glob("file*.txt", { cwd: __dirname });
// finalize the archive (ie we are done appending files but streams have to finish yet)
// 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
archive.finalize();
Archiver ships with out of the box support for TAR and ZIP archives.