Crear archivos ZIP en el navegador con JSZip
Crear archivos ZIP directamente en el navegador puede mejorar significativamente la experiencia de usuario, ya que elimina la necesidad de comprimir del lado del servidor. En este DevTip veremos cómo usar JSZip y la File API de HTML5 para que los usuarios puedan seleccionar, arrastrar y soltar, y agrupar varios archivos en un archivo ZIP completamente del lado del cliente.
Ventajas de la generación de ZIP del lado del cliente
Generar archivos ZIP en el navegador ofrece varias ventajas:
- Menos carga en el servidor: no hace falta procesamiento del lado del servidor.
- Mejor experiencia de usuario: retroalimentación instantánea y descargas más rápidas.
- Mayor privacidad: los archivos nunca salen del dispositivo del usuario.
Descripción general de JSZip y la File API de HTML5
JSZip es una biblioteca de JavaScript que te permite crear, leer y editar archivos ZIP directamente en el navegador. La versión 3.10.1 ofrece una API para manejar archivos ZIP. Combinada con la File API de HTML5, permite un manejo de archivos del lado del cliente sin complicaciones.
Instalación
Puedes instalar JSZip de varias maneras:
# Using npm
npm install jszip
# Using yarn
yarn add jszip
Como alternativa, inclúyelo mediante un CDN:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
Configurar el campo de entrada de archivos y arrastrar y soltar
Empecemos creando una estructura HTML sencilla:
<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>
Agrega algo de JavaScript básico para gestionar la selección de archivos:
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)
})
Leer archivos de forma asíncrona con FileReader
Cuando necesites inspeccionar tú mismo los bytes de un archivo, usa FileReader con manejo de
errores. JSZip también puede aceptar directamente un File, como lo hace
el ejemplo de archivo ZIP que aparece más abajo:
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)
})
}
Integrar archivos en JSZip
Ahora, integremos los archivos en una instancia de JSZip y generemos el archivo ZIP con seguimiento del progreso:
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.')
}
})
Manejo de archivos grandes y consideraciones de rendimiento
La generación de ZIP del lado del cliente es práctica, pero tiene limitaciones:
- Uso de memoria: los archivos grandes pueden consumir mucha memoria.
- Rendimiento: procesar archivos grandes puede ralentizar el navegador.
- Limitaciones del navegador: cada navegador tiene distintas restricciones de memoria.
Para mitigar estos problemas:
- Implementa límites de tamaño de archivo (como se muestra en el ejemplo).
- Ofrece retroalimentación al usuario durante el procesamiento con indicadores de progreso.
- Considera mover la compresión a un Web Worker para mantener la interfaz receptiva. Un worker sigue
necesitando suficiente memoria para sus entradas y su salida;
generateAsync({type: 'blob'})no envía en streaming al disco el archivo ZIP resultante.
Compatibilidad con navegadores
Usa un navegador moderno con FileReader, URL de Blob, promesas y async/await. Mantén una alternativa con
campo de entrada de archivos para los dispositivos en los que arrastrar archivos no está disponible,
y prueba en los navegadores de destino reales en lugar de suponer que la compatibilidad con la File
API implica compatibilidad con todas las funciones de JavaScript que se usan aquí.
Buenas prácticas y errores comunes
- Manejo de errores: gestiona siempre los errores con elegancia en cada paso.
- Límites de tamaño de archivo: informa claramente a los usuarios sobre las limitaciones de tamaño de archivo.
- Indicadores de progreso: ofrece retroalimentación visual durante el procesamiento.
- Gestión de memoria: libera las URL de objeto después de usarlas con
URL.revokeObjectURL(). - Niveles de compresión: equilibra el tamaño del archivo y el rendimiento (mayor compresión = procesamiento más lento).
- Nombres de archivo: gestiona los nombres de archivo duplicados para evitar sobrescrituras dentro del ZIP.
- Pruebas: prueba en distintos navegadores y con diversos tipos y tamaños de archivo.
Conclusión
Generar archivos ZIP directamente en el navegador con JSZip y la File API de HTML5 es sencillo y beneficioso tanto para los usuarios como para los servidores. En entornos de producción, considera implementar límites de tamaño de archivo e indicadores de progreso para garantizar una experiencia de usuario fluida.
