Сжатие и распаковка ZIP в Node.js с Archiver и Unzipper

Почему стоит сжимать файлы
Сжатие файлов уменьшает объём данных для хранения и передачи, упрощает организацию и может повысить безопасность при использовании шифрования. Кратко — причины сжатия:
- Экономия пространства на носителях
- Быстрая передача по сети (меньше байт — меньше задержка и трафик)
- Структурирование и упаковка связанных файлов
- Возможность проверки целостности и версионирования
- Парольная защита и шифрование (с ограничениями)
Важно: сжатие не всегда выгодно. Уже сжатые форматы (JPEG, MP4, PDF с изображениями) часто не уменьшаются заметно и требуют дополнительных CPU-ресурсов.
Коротко о пакетах Archiver и Unzipper
Определения в одну строку:
- Archiver — библиотека для потоковой генерации архивов (ZIP/TAR/GZIP) в Node.js.
- Unzipper — библиотека для потоковой распаковки ZIP в Node.js.
Archiver интегрируется с модулем fs и поддерживает настройку уровня сжатия, добавление файлов/папок и создание многотомных архивов. Unzipper позволяет извлекать содержимое ZIP простым потоком и поддерживает промисы.
Когда использовать Archiver + Unzipper
- Нужна потоковая обработка (не загружать большие файлы в память)
- Требуется гибкое добавление файлов и каталогов
- Нужна совместимость с модулем fs и стандартными потоками Node.js
Быстрая настройка проекта
- Создайте проект и инициализируйте npm:
mkdir node-zip-archiver
cd node-zip-archiver
npm init -y- Установите зависимости:
npm install archiver unzipper --save- Создайте файл, например
app.js.
Создание ZIP из файла
Ниже — функция, которая принимает имя файла и создаёт ZIP-архив с тем же именем и расширением .zip.
const archiver = require('archiver')
const fs = require('fs')
// создать ZIP из файла
const createZipFromFile = (file) => {
const filePath = __dirname + '/' + file
const output = fs.createWriteStream(filePath + '.zip')
const archive = archiver('zip', {
zlib: { level: 9 } // установить максимальный уровень сжатия
})
archive.pipe(output);
archive.file(filePath, { name: file })
archive.finalize()
}Пояснения:
- archive.file(…) добавляет конкретный файл в архив;
- опция name управляет именем файла внутри архива (без исходного пути);
- zlib.level — баланс скорость/сжатие (0–9).
Создание ZIP из папки
Для каталогов используется метод directory. Параметр false означает: включить только содержимое папки, без корневой папки; true — включить саму папку.
// создать ZIP из папки
const createZipFromFolder = (folder) => {
const folderPath = __dirname + '/' + folder
const output = fs.createWriteStream(folderPath + '.zip')
const archive = archiver('zip', {
zlib: { level: 9 } // установить максимальный уровень сжатия
})
archive.pipe(output)
archive.directory(folderPath, false)
archive.finalize()
}Рекомендация по флагу:
- Если вы распаковываете архив в общую директорию, включение корневой папки (
true) помогает избежать ‘захламления’ текущей папки.
Распаковка ZIP
Пример функции распаковки с использованием unzipper. Функция асинхронная и создаёт папку extracted рядом с текущим файлом, если её нет.
const unzipper = require('unzipper')
const fs = require('fs')
// функция для извлечения ZIP
const extractZip = async (file) => {
const filePath = __dirname + '/' + file
const outputPath = __dirname + '/extracted'
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath)
}
await fs.createReadStream(filePath)
.pipe(unzipper.Extract({ path: outputPath }))
.promise()
}Примечание: unzipper.Extract создаёт директории автоматически при необходимости.
Пример тестового запуска
Небольшой IIFE для проверки функций:
(async function () {
const file = 'test.pdf'
const folder = 'test_folder'
const zipFile = 'test.pdf.zip'
createZipFromFile(file)
console.log('ZIP archive successfully created from file')
createZipFromFolder(folder)
console.log('ZIP archive successfully created from folder')
await extractZip(zipFile)
console.log('ZIP archive extracted successfully')
})()Этот код сразу вызывается и позволит быстро проверить рабочие сценарии.
Когда сжатие не помогает: контрпримеры
- Форматы уже сжатых данных: JPEG, PNG (оптимизированные), MP4, MPEG, многие PDF-файлы — дополнительные попытки сжать могут не дать выигрыша и только потратят CPU.
- Низкая пропускная способность CPU: если сервер имеет слабый процессор, агрессивное сжатие (level 9) замедлит обработку быстрее, чем выигрыш по сети.
- Мелкие файлы: упаковка тысяч мелких файлов в один архив может уменьшить overhead при передаче, но ухудшит параллелизм и даст больше задержек для частичного доступа.
Альтернативные подходы и пакеты
- node: использовать встроенный модуль zlib для GZIP- или DEFLATE-потоков (не архивирует несколько файлов в контейнер).
- adm-zip: просто использовать для быстрых операций с ZIP в памяти (подходит для небольших архивов).
- yauzl и node-stream-zip: библиотеки для чтения ZIP с низким потреблением памяти и контролем над извлечением.
Выбор зависит от требования: потоковая обработка — Archiver + Unzipper; манипуляции в памяти — adm-zip.
Производительность и эвристики
- Уровень сжатия: компромисс между скоростью и размером. По умолчанию используйте уровень 6–8 в продакшене; 9 только для фоновых задач.
- Потоки: всегда используйте потоковые операции, если файлы > 50–100 МБ, чтобы не задействовать ОЗУ.
- Параллелизм: пакетуйте логически связанные файлы вместе; избегайте создания сотен маленьких архивов в одной итерации.
Правило большого пальца: если ожидаемые выгоды по трафику меньше затрат CPU/времени — не стоит сжимать.
Безопасность и целостность
- ZIP-шифрование по стандарту PKZIP считается слабым. Для конфиденциальных данных используйте внешнее шифрование (AES-GCM) перед упаковкой или шифруйте содержимое с помощью проверенных библиотек.
- Контроль целостности: храните и/или передавайте контрольные суммы (SHA-256) для критичных файлов и архивов.
- Паранойя в открытии архивов: не распаковывайте загруженные из ненадёжных источников архивы на продакшн-серверах без проверки путей (zip-slip атак). Всегда проверяйте пути файлов внутри архива на предмет выхода за ожидаемую директорию.
Пример проверки zip-slip (псевдокод):
// Перед записью файла из архива проверить
const targetPath = path.join(outputPath, entryPath)
if (!targetPath.startsWith(path.resolve(outputPath) + path.sep)) {
throw new Error('Небезопасный путь в архиве')
}Контрольные списки по ролям
Для разработчика:
- написать модуль-обёртку вокруг archiver/unzipper
- покрыть юнит-тестами (создание/распаковка, именифеста, целостности)
- добавить логирование и таймауты
Для DevOps:
- мониторинг CPU при массовом сжатии
- планирование задач сжатия в off-peak
- резервирование диска и очистка временных файлов
Для QA:
- тесты с большими файлами (>1 ГБ)
- тесты zip-slip и некорректных архивов
- тесты совместимости с другими клиентами (Windows, macOS, Linux)
Критерии приёмки
- Архивы успешно создаются из заданных файлов/папок
- Распаковка возвращает байтово-идентичное содержимое (для бинарных файлов) или ожидаемую структуру
- Нет утечек памяти при обработке больших файлов (инструмент — heap snapshot)
- Защита от zip-slip: все пути внутри архива валидированы
Тестовые случаи и приёмаемые критерии
- Создание архива из одного большого файла (1–5 ГБ) без OOM — пройдено.
- Создание архива из папки со вложенностью до 10 уровней — структура сохранена.
- Распаковка архива с некорректными именами (../../) — блокируется.
- Сравнение контрольной суммы: исходный файл и распакованный равны (SHA-256).
Отладка и распространённые ошибки
- Ошибка: EBUSY / EPERM при записи ZIP — проверьте права и что файл не открыт другой программой.
- Ошибка: ENOENT при архивировании — проверьте корректность путей и рабочую директорию (__dirname).
- Проблема: архив создался, но пустой — убедитесь, что
archive.finalize()вызывается и что записи готовы к pipe.
Логирование: добавьте прослушку событий archiver (archive.on('warning', ...), archive.on('error', ...)) и stream (output.on('close', ...)) для диагностики.
Совместимость и миграция
- Node.js: Archiver и Unzipper работают с Node 8+ (проверьте минимальные требования в package.json пакетов). Для поддержки старых версий Node может потребоваться полифилл потоков.
- Платформы: ZIP — кроссплатформенный формат; однако проверяйте специфические атрибуты (права файлов, символические ссылки) при переносе между Linux и Windows.
Совет по миграции: создайте тестовый набор архивов и прогони его на целевых платформах.
Шаблон SOP для пакетной обработки файлов
- Сбор входных файлов в временной директории.
- Генерация контрольных сумм для каждого файла.
- Создание архива с уровнем сжатия, выбранным согласно политике (напр., 6).
- Перемещение архива в хранилище и проверка checksum архива.
- Очистка временной директории.
- Мониторинг завершённых задач и алерты при ошибках.
Короткий глоссарий
- Архив — контейнер для одного или нескольких файлов (например, .zip).
- Stream — потоковое чтение/запись данных в Node.js.
- ZIP-slip — уязвимость, когда файл из архива распаковывается за пределы целевой директории.
Примеры практических сценариев
- Веб-сервис генерирует бэкап проекта: упаковать папку проекта с исключением node_modules, добавить manifest.json с метаданными и контрольной суммой.
- Экспорт данных: собрать CSV-файлы отчётов в архив и отдать пользователю одной ссылкой.
- Обмен мультимедиа между сервисами: предварительно сжать, затем передать по S3/HTTP.
Резюме
Сжатие ZIP в Node.js с помощью archiver и unzipper — простой и надёжный путь для организации хранения и передачи файлов. Архивы удобны, но требуют внимания к безопасности (zip-slip, шифрование), производительности (уровень сжатия) и совместимости. Выберите потоковый подход для больших объёмов данных и используйте контрольные суммы для гарантии целостности.
Important: всегда тестируйте pipeline с реальными объёмами и типами данных, которые будут в продакшне.
Ключевые ссылки: документация пакетов archiver и unzipper в npm (по имени пакета).
Похожие материалы
Чёрный экран веб‑камеры в Windows — быстрое устранение
Списки и чек-листы в Microsoft Word
Amahi — домашний сервер на Linux
Удалить или деактивировать аккаунт LinkedIn
Лучшие IPTV‑приложения для Android и Android TV