Jszip: Is it possible to create a zip from an existing file/folder structure on the file system?

Created on 17 Dec 2016  路  8Comments  路  Source: Stuk/jszip

Hello, thanks a lot for the lib.

I am trying to zip a single folder and all its subfolders/files on the server - rather than creating new files on the fly in memory in the browser. I am trying the following:

const JSZip = require('jszip');
const zip = new JSZip();
const fs = require("fs");

var data = zip.generateAsync({base64:false,compression:'DEFLATE'});

zip.folder('../lambdas/').generateAsync({type:'base64', compression:'DEFLATE'}).then(function (base64) {
    fs.writeFileSync('../../build/lambdas.zip', data, 'binary');
});

But the results are not what is expected. The output file comes out tiny, and unzipping it reveals other compressed files that recursively unzip forever.

Any idea how to: zip('existing-folder-contents').saveToDisk('zipped.zip')?

Most helpful comment

The documentation confused me as well. zip.folder creates a new folder within the archive, not the behavior I expected of adding all items from that path to the archive.

It appears zip-dir is built on top of jszip (using a much older version unfortunately) but provides the syntactic sugar I was hoping jszip provided.

All 8 comments

The documentation confused me as well. zip.folder creates a new folder within the archive, not the behavior I expected of adding all items from that path to the archive.

It appears zip-dir is built on top of jszip (using a much older version unfortunately) but provides the syntactic sugar I was hoping jszip provided.

I agree. If someone can solve this it would be most appreciated.

zip-dir worked well for me too, would be nice to add this 'sugar' into the main library

This option would be great if can add to the JSZip. Will this option add to JSZip?

I built something like that here: https://github.com/markandcurry/AngJSPHPZipArchive

I was looking for a way to:

  • zip a folder and its recursive subfolders
  • do this synchronously
  • return the result as a node buffer, as opposed to writing it to disk as a file
  • include the original folder as the container of the contents in the zip, as opposed to directly writing the folder contents to the zip root

I couldn't find anything that meets these criteria so I duct taped something together, perhaps it is of use to someone:

const fs = require('fs')
const path = require('path')
const JSZip = require('jszip-sync')

function getZippedFolderSync(dir) {
    let allPaths = getFilePathsRecursiveSync(dir)

    let zip = new JSZip()
    let zipped = zip.sync(() => {
        for (let filePath of allPaths) {
            let addPath = path.relative(path.join(dir, ".."), filePath)
            // let addPath = path.relative(dir, filePath) // use this instead if you don't want the source folder itself in the zip

            let data = fs.readFileSync(filePath)
            zip.file(addPath, data)
        }
        let data = null;
        zip.generateAsync({type:"nodebuffer"}).then((content) => {
            data = content;
        });
        return data;
    })
    return zipped;
}

// returns a flat array of absolute paths of all files recursively contained in the dir
function getFilePathsRecursiveSync(dir) {
    var results = []
    list = fs.readdirSync(dir)
    var pending = list.length
    if (!pending) return results

    for (let file of list) {
        file = path.resolve(dir, file)
        let stat = fs.statSync(file)
        if (stat && stat.isDirectory()) {
            res = getFilePathsRecursiveSync(file)
            results = results.concat(res)
        } else {
            results.push(file)
        }
        if (!--pending) return results
    }

    return results
}

@MattijsKneppers this was not working for me since jszip-sync dont know the method sync, a bit strange i expected that but vscode tells me no and i dont see this into source files.

So i take your solution and change this a little bit to generate zip file from directory with jszip ( not jszip-async), yeah a bit hard coded now but for me this is enough :)

const resolve = require('path').resolve;
const fs = require('fs');
const JsZip = require('jszip');
const path = require('path');

class CreateZipService {

    constructor() {
        this.rootDirectory = process.cwd();
        this.outDirectory  = resolve(process.cwd(), 'dist');
    }

    /** create zip file for extension */
    async createZipFile() {

        // we know what directory we want
        const sourceDir = resolve(this.rootDirectory, 'dist', 'serWebManagement');

        let zip = new JsZip();
        this.buildZipFromDirectory(sourceDir, zip, sourceDir);

        /** generate zip file content */
        const zipContent = await zip.generateAsync({
            type: 'nodebuffer',
            comment: 'ser-web-manangement',
            compression: "DEFLATE",
            compressionOptions: {
                level: 9
            }
        });

        /** create zip file */
        fs.writeFileSync(resolve(this.outDirectory, `ser-web-management.zip`), zipContent);
    }

    // returns a flat array of absolute paths of all files recursively contained in the dir
    buildZipFromDirectory(dir, zip, root) {
        const list = fs.readdirSync(dir);

        for (let file of list) {
            file = path.resolve(dir, file)
            let stat = fs.statSync(file)
            if (stat && stat.isDirectory()) {
                this.buildZipFromDirectory(file, zip, root)
            } else {
                const filedata = fs.readFileSync(file);
                zip.file(path.relative(root, file), filedata);
            }
        }
    }
}

These examples don't take symlinks into account. Here is my typescript solution:

const getFilePathsRecursively = (dir: string): string[] => {
  if (isBrowser()) {
    throw new Error('getFilePathsRecursively is not supported in browser');
  }

  // returns a flat array of absolute paths of all files recursively contained in the dir
  let results: string[] = [];
  let list = fs.readdirSync(dir);

  var pending = list.length;
  if (!pending) return results;

  for (let file of list) {
    file = path.resolve(dir, file);

    let stat = fs.lstatSync(file);

    if (stat && stat.isDirectory()) {
      results = results.concat(getFilePathsRecursively(file));
    } else {
      results.push(file);
    }

    if (!--pending) return results;
  }

  return results;
};

const getZipOfFolder = (dir: string): JSZip => {
  if (isBrowser()) {
    throw new Error('getZipOfFolder is not supported in browser');
  }

  // returns a JSZip instance filled with contents of dir.

  let allPaths = getFilePathsRecursively(dir);

  let zip = new JSZip();
  for (let filePath of allPaths) {
    // let addPath = path.relative(path.join(dir, '..'), filePath); // use this instead if you want the source folder itself in the zip
    let addPath = path.relative(dir, filePath); // use this instead if you don't want the source folder itself in the zip
    let data = fs.readFileSync(filePath);
    let stat = fs.lstatSync(filePath);
    let permissions = stat.mode;

    if (stat.isSymbolicLink()) {
      zip.file(addPath, fs.readlinkSync(filePath), {
        unixPermissions: parseInt('120755', 8), // This permission can be more permissive than necessary for non-executables but we don't mind.
        dir: stat.isDirectory()
      });
    } else {
      zip.file(addPath, data, {
        unixPermissions: permissions,
        dir: stat.isDirectory()
      });
    }
  }

  return zip;
};

Make sure your platform is set to "UNIX" when final ZIP archive file is created. On Mac, this is not the default behavior and needs to be set manually!

Was this page helpful?
0 / 5 - 0 ratings