adm-zip、extract-zip、node-unzip-2、unzipperはすべてNode.js環境でZIPファイルを扱うためのnpmパッケージです。これらのライブラリは、ZIPアーカイブの展開(解凍)や作成といった基本的な機能を提供しますが、設計思想、APIスタイル、ストリーム対応、メモリ使用量、エラーハンドリングなどの点で大きく異なります。フロントエンド開発者がビルドツールやCI/CDパイプライン内でアセットのダウンロード・展開を行う際などに、適切なライブラリを選定する必要があります。
Node.jsでZIPファイルを扱う必要があるとき、どのライブラリを選ぶべきか迷うことはよくあります。adm-zip、extract-zip、node-unzip-2、unzipperはどれもZIPを扱いますが、内部実装や使い勝手、メモリ使用量、サポート範囲が大きく異なります。この記事では、実際のコードを交えながら、それぞれの強みと弱みを明らかにします。
extract-zip — 展開専用のミニマリストextract-zipは「ZIPをフォルダに展開する」ことだけに焦点を当てたライブラリです。APIは非常にシンプルで、Promiseを返す非同期関数一つで完結します。
// extract-zip
const extract = require('extract-zip');
await extract('archive.zip', { dir: './output' });
// archive.zip の中身が ./output ディレクトリに展開される
ZIP作成やファイルリスト取得はできませんが、その分軽量で信頼性が高いです。Electronのドキュメントでも紹介されているほど、標準的です。
adm-zip — 同期的で多機能adm-zipはZIPの読み込み・書き込み・編集を同期的に扱えるのが特徴です。ファイルリストの取得や個別ファイルの抽出も簡単です。
// adm-zip
const AdmZip = require('adm-zip');
const zip = new AdmZip('archive.zip');
zip.extractAllTo('./output'); // 全て展開
// 特定のファイルだけ取得
const entry = zip.getEntry('config.json');
if (entry) {
const data = entry.getData(); // Buffer
}
ただし、同期APIのため大容量ZIPではメインスレッドをブロックし、メモリに全データを読み込むため、1GB以上のZIPには不向きです。
node-unzip-2 — Streamベースの細かい制御node-unzip-2はStreamを使ってZIPを逐次処理します。メモリ使用量を抑えつつ、各ファイルのイベントをハンドリングできます。
// node-unzip-2
const fs = require('fs');
const unzip = require('node-unzip-2');
fs.createReadStream('archive.zip')
.pipe(unzip.Parse())
.on('entry', (entry) => {
const fileName = entry.path;
if (fileName === 'target.txt') {
entry.pipe(fs.createWriteStream(`./output/${fileName}`));
} else {
entry.autodrain(); // 処理しないファイルは破棄
}
});
ただし、このパッケージはオリジナルのnode-unzipのフォークであり、最近の更新が少ないため、新規プロジェクトでは慎重に検討すべきです。
unzipper — 現代的なStream & Promise対応unzipperはStreamとPromiseの両方をサポートし、柔軟な使い方が可能です。大容量ファイルにも対応し、API設計も洗練されています。
// unzipper — Promise版
const unzipper = require('unzipper');
await unzipper.Open.file('archive.zip')
.then(d => d.extract({ path: './output' }));
// unzipper — Stream版
fs.createReadStream('archive.zip')
.pipe(unzipper.Extract({ path: './output' }))
.on('close', () => console.log('完了'));
さらに、directory()メソッドを使えば、ZIP内のファイルを仮想ディレクトリのように扱えます。
const directory = await unzipper.Open.file('archive.zip');
const file = directory.files.find(f => f.path === 'data.json');
const content = await file.buffer(); // Bufferとして取得
extract-zip: 内部でyauzlを使用しており、Streamベースの展開を行うため、大容量ZIPでもメモリ効率が良いです。adm-zip: 全ZIPコンテンツをメモリに読み込むため、大きなファイルではプロセスがクラッシュする可能性があります。node-unzip-2: Stream処理なのでメモリ使用量は抑えられますが、エラーハンドリングがやや難しく、不安定なケースもあります。unzipper: Stream処理を前提として設計されており、大容量ファイルでも安定して動作します。また、Open.file()は必要な部分だけを読み込む遅延評価方式を採用しています。adm-zip: パスワード付きZIPの作成は可能ですが、展開はサポートしていません。extract-zip: パスワード付きZIPには対応していません。node-unzip-2: パスワード付きZIPの展開はサポートしていません。unzipper: file.buffer({ password: '...' }) のように、一部のメソッドでパスワード指定による展開が可能です(ただし、すべての暗号化方式に対応しているわけではありません)。// unzipper — パスワード付きZIP
const directory = await unzipper.Open.file('secure.zip');
const file = directory.files[0];
const data = await file.buffer({ password: 'secret' });
extract-zip: 安定しており、シンプルな用途には今でも十分使えます。adm-zip: 古くからあるライブラリで、バグフィックスはありますが、設計が古いため新規プロジェクトでは注意が必要です。node-unzip-2: オリジナルのnode-unzipは非推奨となっており、このフォークも最近の更新が少ないため、新規プロジェクトでは使用を避けることを強く推奨します。unzipper: アクティブにメンテナンスされており、Stream/Promiseの両対応、メモリ効率、拡張性のバランスが非常に良いです。extract-zipawait extract(pathToZip, { dir: targetDir });
unzipperconst dir = await unzipper.Open.file('bundle.zip');
const config = JSON.parse((await dir.files.find(f => f.path === 'config.json').buffer()).toString());
unzipper(Stream版)fs.createReadStream('logs.zip')
.pipe(unzipper.Parse())
.on('entry', async (entry) => {
if (entry.path.endsWith('.log')) {
const lines = [];
for await (const chunk of entry) {
lines.push(chunk.toString());
}
await saveToDB(lines);
} else {
entry.autodrain();
}
});
adm-zipconst zip = new AdmZip();
zip.addFile('report.txt', Buffer.from('Hello'));
const buffer = zip.toBuffer();
res.send(buffer);
| 機能 / ライブラリ | adm-zip | extract-zip | node-unzip-2 | unzipper |
|---|---|---|---|---|
| ZIP展開 | ✅(同期) | ✅(非同期) | ✅(Stream) | ✅(Stream/Promise) |
| ZIP作成 | ✅ | ❌ | ❌ | ✅(限定的) |
| 個別ファイルアクセス | ✅ | ❌ | ✅(Stream経由) | ✅(仮想ディレクトリ) |
| 大容量ファイル対応 | ❌(メモリ負荷高) | ✅ | ✅ | ✅ |
| パスワード付きZIP | 作成のみ | ❌ | ❌ | ✅(展開のみ) |
| 新規プロジェクト推奨 | △(小規模向け) | ✅(展開専用) | ❌(非推奨) | ✅(汎用) |
extract-zip — 単純で信頼性が高い。unzipper — 柔軟で安全。adm-zip — 手軽だが、サイズに注意。node-unzip-2 は避ける — 非推奨のフォークであり、代替手段が豊富にあるため。現代のNode.jsプロジェクトでは、unzipperが最もバランスの取れた選択肢です。Stream対応、Promise対応、メモリ効率、そしてアクティブなメンテナンス — これらすべてを兼ね備えているからです。一方で、用途が極めて限定的であれば、extract-zipのようなミニマルなライブラリも有効です。要件に合わせて、無駄のない選択を心がけましょう。
extract-zipは「ZIPをディレクトリに展開する」ことに特化した軽量なライブラリです。非同期PromiseベースのAPIを提供し、ElectronやNext.jsなどの公式ドキュメントでも例として使われることがあります。ZIP作成機能はなく、読み取り専用ですが、用途が明確で安定しているため、単純な展開タスクには最適です。
adm-zipはZIPファイルの読み書き両方に対応し、シンプルなAPIで同期的に操作できる点が特徴です。小規模なプロジェクトや、ZIP内のファイルリストを取得したり、特定のファイルだけを抽出したい場合に向いています。ただし、大容量ファイルではメモリ消費が大きくなる可能性があるため、注意が必要です。
unzipperは現代的なStream対応ZIPライブラリで、読み取り・展開だけでなく、ZIPの作成やパスワード保護ZIPのサポート(一部)も備えています。PromiseとStreamの両方のインターフェースを持ち、大規模ファイルにも対応可能です。バランスの取れた機能とアクティブなメンテナンスにより、多くのユースケースで推奨される選択肢です。
node-unzip-2はオリジナルのnode-unzipのフォークで、Stream APIを活用してメモリ効率の良いZIP展開を実現します。大容量ZIPファイルを処理する必要があり、かつ細かい制御(例:ファイルごとのイベントハンドリング)が求められる場合に有効です。ただし、メンテナンス状況に注意が必要で、新規プロジェクトでは代替手段も検討すべきです。
Unzip written in pure JavaScript. Extracts a zip into a directory. Available as a library or a command line program.
Uses the yauzl ZIP parser.
Make sure you have Node 10 or greater installed.
Get the library:
npm install extract-zip --save
Install the command line program:
npm install extract-zip -g
const extract = require('extract-zip')
async function main () {
try {
await extract(source, { dir: target })
console.log('Extraction complete')
} catch (err) {
// handle any errors
}
}
dir (required) - the path to the directory where the extracted files are writtendefaultDirMode - integer - Directory Mode (permissions), defaults to 0o755defaultFileMode - integer - File Mode (permissions), defaults to 0o644onEntry - function - if present, will be called with (entry, zipfile), entry is every entry from the zip file forwarded from the entry event from yauzl. zipfile is the yauzl instanceDefault modes are only used if no permissions are set in the zip file.
extract-zip foo.zip <targetDirectory>
If not specified, targetDirectory will default to process.cwd().