Unzip Stream
unzip-stream is a Node.js library for processing zip files using a streaming API. It provides mechanisms to parse zip file contents entry by entry or extract an entire archive to a directory. The current stable version is 0.3.4, with a release cadence that appears to be maintenance-focused rather than frequent feature additions. Key differentiators include its streaming engine, which aims to handle zip files that might cause issues with older libraries like `unzip` or `unzipper`, and its reliance solely on Node.js's built-in zlib for inflation, avoiding compiled dependencies. It supports Zip64 for archives with files larger than 4GB. However, the library acknowledges that the zip file format is not inherently optimized for streaming, suggesting alternatives like `yauzl` or `decompress-zip` for scenarios where the complete zip file is available and random access is preferred. It lacks support for encrypted (password-protected) zips and symlinks.
Common errors
-
Stream is stuck, no more entries are processed.
cause An entry stream was received from `unzip.Parse()` but its data was neither consumed nor explicitly discarded.fixFor every `entry` object, either pipe it to a destination (e.g., `entry.pipe(fs.createWriteStream('path'))`) or call `entry.autodrain()` to dispose of its contents. -
Files are missing or incomplete after extraction, but the stream's 'finish' event fired.
cause The `unzip.Extract()` stream emitted the 'finish' event before all files were fully written to disk.fixChange event listener from `'finish'` to `'close'` when using `unzip.Extract()`: `stream.on('close', () => { /* extraction truly done */ });` -
Filenames within the extracted archive appear as garbled characters (e.g., '����.txt').
cause The zip archive contains filenames encoded in a character set other than UTF-8, and `unzip-stream` is using its default or incorrect fallback decoding.fixProvide a custom `decodeString` function using a character encoding library (like `iconv-lite`) to `unzip.Parse()` or `unzip.Extract()`, e.g., `unzip.Parse({ decodeString: (buffer) => iconvLite.decode(buffer, 'cp866') })`. -
Error: Zip64 required for files over 4GB. (Or similar errors with large files)
cause You are using an older version of `unzip-stream` (prior to 0.3.0) which lacks full support for Zip64, trying to process archives or files larger than 4GB.fixUpgrade your `unzip-stream` package to version `0.3.0` or newer: `npm install unzip-stream@latest`.
Warnings
- gotcha When parsing a zip file using `unzip.Parse()`, you must explicitly consume the data from each entry stream (e.g., by piping it to a write stream) or call `entry.autodrain()` if you do not intend to process its contents. Failure to do so will cause the main archive stream to become stuck and prevent further entries from being processed.
- gotcha When using `unzip.Extract()`, rely on the 'close' event to determine when the entire archive has been fully extracted and all files have been written to disk. The 'finish' event can be emitted prematurely, before all asynchronous file writing operations have completed, leading to incomplete extractions.
- breaking Older versions of `unzip-stream` (prior to 0.3.0) had limited or no support for Zip64, meaning they could fail to process archives or individual files larger than 4GB. Archives containing such large files would either error out or produce corrupted data.
- gotcha The zip file format is not ideally suited for streaming, as it traditionally places metadata at the end of the file. While `unzip-stream` attempts to mitigate this with a new streaming engine, edge cases or corrupted archives might still behave unpredictably. For scenarios with complete zip files, alternative libraries like `yauzl` or `decompress-zip` (which read from the end of the file) might offer greater robustness.
- gotcha `unzip-stream` does not support encrypted (password-protected) zip files or UNIX symlinks within archives. Attempts to process such archives will likely result in errors or incorrect extraction.
- gotcha When dealing with non-UTF8 filenames within zip archives, the default decoding might lead to garbled characters. The `Parse` and `Extract` methods allow providing a custom `decodeString` function to handle specific encodings.
Install
-
npm install unzip-stream -
yarn add unzip-stream -
pnpm add unzip-stream
Imports
- unzip
const unzip = require('unzip-stream');import * as unzip from 'unzip-stream';
- unzip.Parse
import * as unzip from 'unzip-stream'; unzip.Parse();
- unzip.Extract
import * as unzip from 'unzip-stream'; unzip.Extract();
Quickstart
import fs from 'fs';
import path from 'path';
import * as unzip from 'unzip-stream';
const archivePath = path.resolve('./example.zip');
const outputPath = path.resolve('./extracted_files');
// Create a dummy zip file for demonstration if it doesn't exist
if (!fs.existsSync(archivePath)) {
console.warn('Creating a dummy example.zip for quickstart. In a real scenario, use a valid zip file.');
const JSZip = await import('jszip'); // Dynamic import for JSZip
const zip = new JSZip.default();
zip.file('hello.txt', 'Hello, Unzip Stream!');
zip.file('folder/world.txt', 'World!');
const content = await zip.generateAsync({ type: 'nodebuffer' });
fs.writeFileSync(archivePath, content);
}
// Ensure output directory exists
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath, { recursive: true });
}
fs.createReadStream(archivePath)
.pipe(unzip.Parse())
.on('entry', function (entry) {
const filePath = entry.path;
const type = entry.type; // 'Directory' or 'File'
const size = entry.size; // might be undefined in some archives
console.log(`Processing entry: ${filePath} (Type: ${type}, Size: ${size || 'unknown'})`);
if (type === 'File' && filePath === 'hello.txt') {
const writeStream = fs.createWriteStream(path.join(outputPath, path.basename(filePath)));
entry.pipe(writeStream);
writeStream.on('finish', () => console.log(`Extracted ${filePath}`));
} else {
// Important: Call autodrain() for entries you don't consume to prevent stream from getting stuck.
entry.autodrain();
if (type === 'File') console.log(`Skipping extraction for ${filePath}, autodraining.`);
}
})
.on('close', () => {
console.log('Finished parsing the archive. Check output in:', outputPath);
})
.on('error', (err) => {
console.error('An error occurred during parsing:', err);
});