Create ZIP archives in the browser with JSZip
Creating ZIP archives directly in the browser can significantly enhance user experience by eliminating the need for server-side compression. In this DevTip, we'll explore how to use JSZip and the HTML5 File API to let users select, drag-and-drop, and bundle multiple files into a ZIP archive entirely client-side.
Benefits of client-side ZIP generation
Generating ZIP files in the browser offers several advantages:
- Reduced server load: No need for server-side processing.
- Improved user experience: Instant feedback and faster downloads.
- Enhanced privacy: Files never leave the user's device.
Overview of JSZip and the HTML5 File API
JSZip is a JavaScript library that allows you to create, read, and edit ZIP files directly in the browser. Version 3.10.1 provides an API for handling ZIP archives. Combined with the HTML5 File API, it enables seamless client-side file handling.
Installation
You can install JSZip in several ways:
# Using npm
npm install jszip
# Using yarn
yarn add jszip
Alternatively, include it via CDN:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
Setting up file input and drag-and-drop
Let's start by creating a simple HTML structure:
<label for="fileInput">Choose files for the ZIP archive</label>
<input type="file" id="fileInput" multiple />
<div id="dropZone">Drag and drop files here</div>
<button id="zipButton">Create ZIP</button>
<div id="progress"></div>
Add some basic JavaScript to handle file selection:
const fileInput = document.getElementById('fileInput')
const dropZone = document.getElementById('dropZone')
let selectedFiles = []
fileInput.addEventListener('change', (event) => {
selectedFiles = Array.from(event.target.files)
})
dropZone.addEventListener('dragover', (event) => {
event.preventDefault()
dropZone.classList.add('active')
})
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('active')
})
dropZone.addEventListener('drop', (event) => {
event.preventDefault()
dropZone.classList.remove('active')
selectedFiles = Array.from(event.dataTransfer.files)
})
Reading files asynchronously with FileReader
When you need to inspect file bytes yourself, use FileReader with error handling. JSZip can also
accept a File directly, as the archive example below does:
function readFileAsync(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () =>
resolve({
name: file.name,
data: reader.result,
})
reader.onerror = () => reject(reader.error)
reader.readAsArrayBuffer(file)
})
}
Integrating files into JSZip
Now, let's integrate the files into a JSZip instance and generate the ZIP archive with progress tracking:
async function createZip(files) {
const progressElement = document.getElementById('progress')
const zip = new JSZip()
// Check file size limits
const totalSize = files.reduce((sum, file) => sum + file.size, 0)
if (totalSize > 100 * 1024 * 1024) {
// 100MB limit
alert('Total file size exceeds 100MB limit')
return
}
// Add files to zip
const names = new Set()
for (const file of files) {
if (names.has(file.name)) {
progressElement.textContent = `Duplicate filename: ${file.name}. Rename it before archiving.`
return
}
names.add(file.name)
zip.file(file.name, file)
progressElement.textContent = `Added ${file.name} to archive`
}
// Generate and download the zip
try {
progressElement.textContent = 'Generating ZIP file...'
const content = await zip.generateAsync(
{
type: 'blob',
compression: 'DEFLATE',
compressionOptions: { level: 6 },
},
(metadata) => {
progressElement.textContent = `Generating ZIP: ${Math.round(metadata.percent)}%`
},
)
const url = URL.createObjectURL(content)
const a = document.createElement('a')
a.href = url
a.download = 'archive.zip'
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
setTimeout(() => URL.revokeObjectURL(url), 60000)
progressElement.textContent = 'ZIP download started'
} catch (error) {
console.error('Error generating ZIP:', error)
progressElement.textContent = 'Error generating ZIP file'
}
}
document.getElementById('zipButton').addEventListener('click', () => {
if (selectedFiles.length > 0) {
createZip(selectedFiles)
} else {
alert('Please select or drop files first.')
}
})
Handling large files and performance considerations
Client-side ZIP generation is convenient but has limitations:
- Memory usage: Large files can consume significant memory.
- Performance: Processing large files may slow down the browser.
- Browser limitations: Different browsers have different memory constraints.
To mitigate these issues:
- Implement file size limits (as shown in the example).
- Provide user feedback during processing with progress indicators.
- Consider moving compression into a Web Worker to keep the UI responsive. A worker still needs
enough memory for its inputs and output;
generateAsync({type: 'blob'})does not stream the resulting archive to disk.
Browser compatibility
Use a modern browser with FileReader, Blob URLs, promises, and async/await. Keep a file-input
fallback for devices where dragging files is unavailable, and test actual target browsers instead
of assuming that support for the File API implies support for every JavaScript feature used here.
Best practices and common pitfalls
- Error handling: Always handle errors gracefully at each step.
- File size limits: Clearly inform users about file size limitations.
- Progress indicators: Provide visual feedback during processing.
- Memory management: Release object URLs after use with
URL.revokeObjectURL(). - Compression levels: Balance between file size and performance (higher compression = slower processing).
- File naming: Handle duplicate file names to prevent overwriting in the ZIP.
- Testing: Test across different browsers and with various file types and sizes.
Conclusion
Generating ZIP archives directly in the browser with JSZip and the HTML5 File API is straightforward and beneficial for both users and servers. For production environments, consider implementing file size limits and progress indicators to ensure a smooth user experience.
