90

Is there a way to zip files using JavaScript?? For an example, like in Yahoo mail, when you chose to download all the attachments from an email, it gets zipped and downloaded in a single zip file. Is JavaScript capable of doing that? If so, please provide a coding example.

I found this library called jszip to do the task but it has known and unresolved issues.

How do I solve the problem?

3
  • possible duplicate of stackoverflow.com/questions/2095697/… Commented Dec 22, 2011 at 19:23
  • 1
    @kvc I saw that too but my question is to zip files. Not unzip. So thought of asking it as another post :) Commented Dec 22, 2011 at 19:28
  • You might be able to create a windows shell javascript that does this but you cannot do this in the browser. Commented Dec 22, 2011 at 19:35

9 Answers 9

71

JSZip has been updated over the years. Now you can find it on its GitHub repo

It can be used together with FileSaver.js

You can install them using npm:

npm install jszip --save
npm install file-saver --save

And then import and use them:

import JSZip from 'jszip';
import FileSaver from 'file-saver';

const zip = new JSZip();
zip.file('idlist.txt', 'PMID:29651880\r\nPMID:29303721');
zip.generateAsync({ type: 'blob' }).then(function (content) {
    FileSaver.saveAs(content, 'download.zip');
});

Then you will download a zip file called download.zip, once you've extracted it, and you can find inside a file called idlist.txt, which has got two lines:

PMID:29651880
PMID:29303721

And for your reference, I tested with the following browsers, and all passed:

  • Firefox 59.0.2 (Windows 10)
  • Chrome 65.0.3325.181 (Windows 10)
  • Microsoft Edge 41.16299.371.0 (Windows 10)
  • Internet Explorer 11.0.60 (Windows 10)
  • Opera 52 (Mac OSX 10.13)
  • Safari 11 (Mac OSX 10.13)
Sign up to request clarification or add additional context in comments.

1 Comment

This doesn't work for me. I get this: UnhandledPromiseRejectionWarning: Error: blob is not supported by this platform Also, how do I zip up an already existing file in the same directory?
28

If you don't care about IE, client-zip is much faster and smaller than JSZip and is meant to solve exactly this problem (yes, this is a shameless but completely relevant plug for my library 😉).

You would do something like this (where files could be an array of fetch Responses for example, though there are many supported inputs):

import { downloadZip } from "client-zip/index.js"
import FileSaver from "file-saver"

const content = await downloadZip(files).blob()
FileSaver.saveAs(content, "download.zip");

Essentially the same usage as in Yuci's answer but updated for 2020.

12 Comments

But client-zip do not compress the data
That's not necessarily a bad thing. Indeed, many browsers automatically unzip downloaded archives, so compression would only increase CPU load (for compression, and then again decompression) for no bandwidth or storage savings at all. The main reason for zipping here seems to be to achieve a one-click download of multiple attachments.
Didn't say that is a bad thing. But you mentioned 'exactly this problem' that may sound that it should do the same stuff as JSZip. Client-zip seems awesome to do what it propouses.
All right. To be clear : I do not claim client-zip solves the same group of problems as JSZip, just the one in this question.
the updated client-zip seems even much easier without the need of filesaver
|
12

By using JSZIP we can generate and download zip file in JavaScript. For that you have to follow the steps below

  1. Download jszip zip file from http://github.com/Stuk/jszip/zipball/master
  2. Extract the zip and find jszip.js file inside dist folder
  3. Import jszip.js file in your html file like below

    <script type="text/javascript" src="jszip.js"></script>
    
  4. Add below function in your code and call it

    onClickDownload: function () {
        var zip = new JSZip();
        for (var i = 0; i < 5; i++) {
            var txt = 'hello';
            zip.file("file" + i + ".txt", txt);
        }
        zip.generateAsync({
            type: "base64"
        }).then(function(content) {
            window.location.href = "data:application/zip;base64," + content;
        });       
    }
    
  5. You can download sample code from my git repository here (GIT link)

2 Comments

It's a pretty bad idea to generate the output in Base64, and even a worse idea to use a Data URI like that. A waste of memory and CPU. Use a blob URL instead.
Excellent, I changed the type to 'blob' and it worked like a charm! I used an anchor link on the page to trigger the download instead of changing the page url. var uriContent = URL.createObjectURL(contentBlob); var lnkDownload = document.getElementById('lnkDownload'); lnkDownload.download = 'MyDownload.zip'; lnkDownload.href = uriContent; lnkDownload.click();
11

I developed a new solution to generate zip files using JavaScript. The solution is in the public domain.

The Zip class.

Zip {

    constructor(name) {
        this.name = name;
        this.zip = new Array();
        this.file = new Array();
    }
    
    dec2bin=(dec,size)=>dec.toString(2).padStart(size,'0');
    str2dec=str=>Array.from(new TextEncoder().encode(str));
    str2hex=str=>[...new TextEncoder().encode(str)].map(x=>x.toString(16).padStart(2,'0'));
    hex2buf=hex=>new Uint8Array(hex.split(' ').map(x=>parseInt(x,16)));
    bin2hex=bin=>(parseInt(bin.slice(8),2).toString(16).padStart(2,'0')+' '+parseInt(bin.slice(0,8),2).toString(16).padStart(2,'0'));
    
    reverse=hex=>{
        let hexArray=new Array();
        for(let i=0;i<hex.length;i=i+2)hexArray[i]=hex[i]+''+hex[i+1];
        return hexArray.filter((a)=>a).reverse().join(' '); 
    }
    
    crc32=r=>{
        for(var a,o=[],c=0;c<256;c++){
            a=c;
            for(var f=0;f<8;f++)a=1&a?3988292384^a>>>1:a>>>1;
            o[c]=a;
        }
        for(var n=-1,t=0;t<r.length;t++)n=n>>>8^o[255&(n^r[t])];
        return this.reverse(((-1^n)>>>0).toString(16).padStart(8,'0'));
    }
    
    fetch2zip(filesArray,folder=''){
        filesArray.forEach(fileUrl=>{
            let resp;               
            fetch(fileUrl).then(response=>{
                resp=response;
                return response.arrayBuffer();
            }).then(blob=>{
                new Response(blob).arrayBuffer().then(buffer=>{
                    console.log(`File: ${fileUrl} load`);
                    let uint=[...new Uint8Array(buffer)];
                    uint.modTime=resp.headers.get('Last-Modified');
                    uint.fileUrl=`${this.name}/${folder}${fileUrl}`;                            
                    this.zip[fileUrl]=uint;
                });
            });             
        });
    }
    
    str2zip(name,str,folder){
        let uint=[...new Uint8Array(this.str2dec(str))];
        uint.name=name;
        uint.modTime=new Date();
        uint.fileUrl=`${this.name}/${folder}${name}`;
        this.zip[uint.fileUrl]=uint;
    }
    
    files2zip(files,folder){
        for(let i=0;i<files.length;i++){
            files[i].arrayBuffer().then(data=>{
                let uint=[...new Uint8Array(data)];
                uint.name=files[i].name;
                uint.modTime=files[i].lastModifiedDate;
                uint.fileUrl=`${this.name}/${folder}${files[i].name}`;
                this.zip[uint.fileUrl]=uint;                            
            });
        }
    }
    
    makeZip(){
        let count=0;
        let fileHeader='';
        let centralDirectoryFileHeader='';
        let directoryInit=0;
        let offSetLocalHeader='00 00 00 00';
        let zip=this.zip;
        for(const name in zip){
            let modTime=(()=>{
                const lastMod=new Date(zip[name].modTime);
                const hour=this.dec2bin(lastMod.getHours(),5);
                const minutes=this.dec2bin(lastMod.getMinutes(),6);
                const seconds=this.dec2bin(Math.round(lastMod.getSeconds()/2),5);
                const year=this.dec2bin(lastMod.getFullYear()-1980,7);
                const month=this.dec2bin(lastMod.getMonth()+1,4);
                const day=this.dec2bin(lastMod.getDate(),5);                        
                return this.bin2hex(`${hour}${minutes}${seconds}`)+' '+this.bin2hex(`${year}${month}${day}`);
            })();                   
            let crc=this.crc32(zip[name]);
            let size=this.reverse(parseInt(zip[name].length).toString(16).padStart(8,'0'));
            let nameFile=this.str2hex(zip[name].fileUrl).join(' ');
            let nameSize=this.reverse(zip[name].fileUrl.length.toString(16).padStart(4,'0'));
            let fileHeader=`50 4B 03 04 14 00 00 00 00 00 ${modTime} ${crc} ${size} ${size} ${nameSize} 00 00 ${nameFile}`;
            let fileHeaderBuffer=this.hex2buf(fileHeader);
            directoryInit=directoryInit+fileHeaderBuffer.length+zip[name].length;
            centralDirectoryFileHeader=`${centralDirectoryFileHeader}50 4B 01 02 14 00 14 00 00 00 00 00 ${modTime} ${crc} ${size} ${size} ${nameSize} 00 00 00 00 00 00 01 00 20 00 00 00 ${offSetLocalHeader} ${nameFile} `;
            offSetLocalHeader=this.reverse(directoryInit.toString(16).padStart(8,'0'));
            this.file.push(fileHeaderBuffer,new Uint8Array(zip[name]));
            count++;
        }
        centralDirectoryFileHeader=centralDirectoryFileHeader.trim();
        let entries=this.reverse(count.toString(16).padStart(4,'0'));
        let dirSize=this.reverse(centralDirectoryFileHeader.split(' ').length.toString(16).padStart(8,'0'));
        let dirInit=this.reverse(directoryInit.toString(16).padStart(8,'0'));
        let centralDirectory=`50 4b 05 06 00 00 00 00 ${entries} ${entries} ${dirSize} ${dirInit} 00 00`;
        this.file.push(this.hex2buf(centralDirectoryFileHeader),this.hex2buf(centralDirectory));                
        let a = document.createElement('a');
        a.href = URL.createObjectURL(new Blob([...this.file],{type:'application/octet-stream'}));
        a.download = `${this.name}.zip`;
        a.click();              
    }
}

Then, create a new object Zip.

  z=new Zip('myZipFileName');

You you can:

  • Load files of your directory to zip object with fecth2zip(filesArray,folder).
  filesArray=[
    'file01.ext',
    'file02.ext',
    'file...'
  ];
  z.fetch2zip(filesArray,'public/');
  • Create a new file from a string with str2zip(nameFile,content,directory).
  z.str2zip('test.txt','content','public/teste/');
  • Or upload to zip.

    Put onchange event into the input file and send the files to function files2zip(this.files).

  <input type="file" onchange="z.files2zip(this.files)" value='files' multiple>

After placing all the objects inside your Zip object file, just download it.

  <input type="button" onclick="z.makeZip()" value='Zip'>

The class can also be found here: https://github.com/pwasystem/zip/

7 Comments

Great idea - nicely written - but doesn't work for me. I tried the following and it downloaded a file but it wasn't a valid zip? var z=new Zip('myZipFileName'); z.str2zip('test.txt','content'); z.makeZip();
@SimonSawyer I had the same issue but then found the bug: modTime is a func instead of the returned value
really cool thanks, perfect to generate simple zip + easy to edit (i've modified it so the addToZip function return Promise & have added a blobToZip function). however one question : there is a blank file added at the root of the zip named as the zip; why is it there ? (i've removed it and had no issue - debian/firefox)
@mikakun - Thank you. In the future I will think of some implementations to make the script faster.
@Spiderpoison thanks for the reply & it's fast enough for me but what about that blank file at the root ? any reason for it to be there ?
|
6

This is an older question but I ran across it searching for a solution to creating a zip archive.

For my use case, I'm creating several thousand zip archives in node.js from a very large logger source every minute consisting of up to 200 files in each archive.

I had tried JSZip with very poor results due to performance issues and a memory leak not worth the time to track down. Since my use case is fairly extreme, it's quite the stress test.

I came across another pure javascript zip library worth mentioning here for others to check out.

I ended up using fflate.

https://github.com/101arrowz/fflate

This library has been extremely performant. I'm only using the zip archive feature with level 9 compression, but fflate is a full featured library which works server side and complete browser support (2011+). I'm not including any examples here as fflate documentation is very complete. I'd highly recommend it as an alternative.

1 Comment

Thanks for the sharing, this fflate library looks very clean.
2

I'd recommend going straight to using Node's built-in library Zlib for this, which includes images; encode in base 64 using "buffers". Rather than using npm packages. Reasons being:

  • Zlib is a Node native library - has been kept up-to-date for nearly 10 years now - so the proofs there for long-term supports
  • Node allows you work with Buffers - i.e. you can convert your text string/images to the raw binary data and compress it that way with Zlib
  • Easily compress and decompress large files - leverage node streams to compress files in MBs or GBs

The fact you are using jszip, would allow me to guess that you are using npm as well as node; assumes you have set up your environment correctly, i.e. node installed globally.

Example: input.txt compressed to become input.txt.gz

const zlib = require('zlib');
const fs = require('fs');
const gzip = zlib.createGzip();
const input = fs.createReadStream('input.txt');
const output = fs.createWriteStream('input.txt.gz');

input.pipe(gzip).pipe(output);

Step 1: So you require each of the native modules from node - require is part of ES5. Zlib as previously mentioned, and fs module, the File System module.

const zlib = require('zlib');
const fs = require('fs');

Step 2: The fs module, this allows you to create a readstream, are specifically called to read chunks of data. This will return a readstream object; readable stream

const input = fs.createReadStream(FILE PATH HERE);

__Note: This readstream object then gets piped again; this chaining of pipes on readsteam objects can occur endlessly, making pipes very flexible.

ReadStream.pipe(DoesSomething).pipe(SomethingElse).pipe(ConvertToWriteStream)

Step 3: The readstream object, that has been piped and compressed is then converted to writestream object.

const output = fs.createWriteStream('input.txt.gz');

input.pipe(gzip).pipe(output); // returned filename input.txt.gz, within local directory

So this library allows you easily enter a file path and decide where you want your compressed file to be. You can also choose to do the reverse, if need be.

1 Comment

Thanks for sharing. However, I guess the question sought a browser-side solution rather than a server-side's. Any idea on the browser-side? Thanks.
0

With the new HTML5 file APIs and the typed arrays, you can pretty much do anything you want in JavaScript. However, the browser support isn't going to be great. I'm guessing that's what you meant by "unresolved issues". I would recommend, for the time being, to do it on the server. For example, in PHP, you could use this extension.

1 Comment

This answer is not incorrect, PHP will hit its limits though when files get bigger and user counts do, too.
0

I created this function as an example of how to use a anchor tag and jszip to create a zip file and start a download of said zip file:

async function GenerateZipDownload() {
const imageDownload = "https://unsplash.com/photos/two-people-in-scuba-gear-swimming-in-the-ocean-SuGTwrtPCg4";
const file = await fetch(imageDownload).then(r => r.blob());

const zip = new JSZip();
zip.file(`filename.jpg`, file); // adds the image file to the zip file

const zipData = await zip.generateAsync({
    type: "blob",
    streamFiles: true
})
const link = document.createElement('a');
link.href = window.URL.createObjectURL(zipData);
link.download = `scuba-gear-swimming-data.zip`
link.click();
} 

you can call the zip.file function as much as you want to add more files to the outputted zip file. I wrote about this in more detail in my blog post here: https://www.cjoshmartin.com/blog/creating-zip-files-with-javascript

Comments

0

For Node.js, I came across @zip-js/zip-js, and I found it really easy to work with. Here's a sample from an article I wrote on it:

export async function blobToBase64(blob: Blob): Promise<string> {
    const buffer = await blob.arrayBuffer();
    return Buffer.from(buffer).toString("base64");
}

export async function blobToFile(blob: Blob, path: string): Promise<void> {
    const buffer = await blob.arrayBuffer();
    fs.writeFileSync(path, Buffer.from(buffer));
    return;
}

export async function zipFolder(sourcePath: string): Promise<Blob> {
    const blobWriter = new BlobWriter("application/zip");
    const writer = new ZipWriter(blobWriter);

    // Recursive function to walk through the directory and add files/folders to the zip
    const walk = async (dir: string, writer: ZipWriter<Blob>) => {
        const files = fs.readdirSync(dir);
        for (const file of files) {
            const fullPath = path.join(dir, file);
            const relativePath = path.relative(sourcePath, fullPath);
            if (fs.statSync(fullPath).isDirectory()) {
                // Add folder
                await writer.add(`${relativePath}/`, undefined);
                await walk(fullPath, writer); // Recursively add folder contents
            } else {
                const fileBlob = new Blob([fs.readFileSync(fullPath)]);
                // Add file
                await writer.add(relativePath, new BlobReader(fileBlob));
            }
        }
    };

    await walk(sourcePath, writer);
    await writer.close();

    return await blobWriter.getData());
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.