unzipit

repository·main·Indexed 41 days ago

https://github.com/greggman/unzipit

A high-performance, random-access unzip library for browser and Node.js version 2.1.1. It is designed for memory efficiency by loading only requested parts of a zip file. Features include support for web workers for parallel decompression, a custom Reader interface for Node.js, and HTTPRangeReader for fetching remote files via byte ranges without downloading the entire archive.

Tokens
6.3K
Snippets
26
Records
30
Agent score
50%

What's inside unzipit

  1. Handle non-UTF-8 filenames

    main

    The ZIP standard does not guarantee Unicode. If a zip file uses a different encoding for filenames, unzip() might fail to create a valid string key in the entries object. In these cases, use unzipRaw() to get an array of entries and access the entry.nameBytes property to decode the filename manually using your preferred encoding (e.g., TextDecoder).

    // Using unzipRaw to handle non-UTF-8 names
    const { entries } = await unzipRaw(blob);
    for (const entry of entries) {
      // Decode using a specific encoding like windows-1252
      const decodedName = new TextDecoder('windows-1252').decode(entry.nameBytes);
      console.log('Decoded filename:', decodedName);
    }
  2. Stream large zip files using HTTPRangeReader

    main

    To avoid downloading an entire zip file before processing, use HTTPRangeReader if your server supports HTTP range requests. This allows unzipit to read only the central directory at the end of the file and then fetch specific entry data on demand via range requests.

    import { unzip, HTTPRangeReader } from 'unzipit';
    
    async function readFiles(url) {
      const reader = new HTTPRangeReader(url);
      const { zip, entries } = await unzip(reader);
      // Entries are now available, and their data will be fetched via range requests as needed
      const stream = await entries['some/huge/file.bin'].stream();
    }
  3. Unzip a file without workers

    main

    To unzip a file using the main thread, import unzip and pass a URL or a compatible data object (like Blob, ArrayBuffer, SharedArrayBuffer, TypedArray, or a custom Reader). The unzip function returns an object containing entries, where each entry allows you to retrieve its data as an ArrayBuffer or a Blob (with an optional MIME type).

    import {unzip} from 'unzipit';
    
    async function readFiles(url) {
      const {entries} = await unzip(url);
    
      // print all entries and their sizes
      for (const [name, entry] of Object.entries(entries)) {
        console.log(name, entry.size);
      }
    
      // read an entry as an ArrayBuffer
      const arrayBuffer = await entries['path/to/file'].arrayBuffer();
    
      // read an entry as a blob and tag it with mime type 'image/png'
      const blob = await entries['path/to/otherFile'].blob('image/png');
    }
  4. Install and use unzipit in the browser

    main

    You can use unzipit via npm/bundlers, by including the distributed files directly, or via CDN.

    Via ESM Import (Local File):

    import * as unzipit from `./unzipit.module.js`;

    Via Script Tag (Local File):

    <script src="unzipit.min.js"></script>

    Via CDN:

    import * as unzipit from 'https://unpkg.com/unzipit@2.0.1/dist/unzipit.module.js';
    // or
    <script src="https://unpkg.com/unzipit@2.0.1/dist/unzipit.js"></script>
  5. Unzip a file with workers

    main

    To improve performance, you can offload decompression to web workers. You must first call setOptions with the workerURL pointing to the unzipit-worker.module.js file before calling unzip.

    import {unzip, setOptions} from 'unzipit';
    
    setOptions({workerURL: 'path/to/unzipit-worker.module.js'});
    
    async function readFiles(url) {
      const {entries} = await unzip(url);
      // ...
    }
  6. Implement a custom Reader for Node.js

    main

    To avoid loading the entire zip file into memory in Node.js, implement a Reader interface. The reader must provide getLength() and read(offset, length) methods.

    Stateless Reader: Opens the file for every read call. No manual cleanup required.

    Stateful Reader: Opens the file once and keeps it open. You must call reader.close() manually to prevent file descriptor leaks.

    class FileReader {
      constructor(filename) {
        this.fhp = fsPromises.open(filename);
      }
      async close() {
        const fh = await this.fhp;
        await fh.close();
      }
      async getLength() {
        if (this.length === undefined) {
          const fh = await this.fhp;
          const stat = await fh.stat();
          this.length = stat.size;
        }
        return this.length;
      }
      async read(offset, length) {
        const fh = await this.fhp;
        const data = new Uint8Array(length);
        await fh.read(data, 0, length, offset);
        return data;
      }
    }
    
    async function doStuff() {
      const reader = new FileReader(filename);
      const {zip, entries} = await unzipit.unzip(reader);
      // ...
      await reader.close();
    }
  7. Use unzipit in Node.js

    main

    In Node.js, you cannot pass a URL directly. You must either pass an ArrayBuffer, SharedArrayBuffer, TypedArray, or implement a custom Reader object.

    Using an ArrayBuffer:

    const unzipit = require('unzipit');
    const fsPromises = require('fs').promises;
    
    async function readFiles(filename) {
      const buf = await fsPromises.readFile(filename);
      const {zip, entries} = await unzipit.unzip(new Uint8Array(buf));
      // ...
    }

    Using Workers in Node.js: To use workers in Node, use require.resolve to provide the worker path and call unzipit.cleanup() to allow the process to exit.

    const unzipit = require('unzipit');
    
    unzipit.setOptions({workerURL: require.resolve('unzipit/dist/unzipit-worker.js')});
    
    // ... use unzip ...
    
    // Call this to shut down workers and allow Node to exit
    unzipit.cleanup();
  8. Debug live browser tests

    main

    When running or inspecting live browser tests, you can disable timeouts by appending ?timeout=0 to the URL. This is useful for debugging long-running or complex test cases in the browser environment.

    http://localhost:8080/tests/?timeout=0
  9. Unzip multiple entries in parallel

    main

    You can maximize throughput by extracting multiple entries simultaneously using Promise.all. This is particularly effective when using workers.

    import {unzip, setOptions} from 'unzipit';
    
    setOptions({workerURL: 'path/to/unzipit-worker.module.js'});
    
    async function readFiles(url) {
      const {entries} = await unzipit.unzip(url);
      const names = Object.keys(entries);
      const blobs = await Promise.all(Object.values(entries).map(e => e.blob()));
    
      // names and blobs are now parallel arrays so do whatever you want.
      const blobsByName = Object.fromEntries(names.map((name, i) => [name, blobs[i]]));
    }
  10. Access entry data with ZipEntry methods

    main

    Once you have a ZipEntry object, you can retrieve its content using several asynchronous methods:

    • async blob(type?: string): Returns a Blob for the entry. You can optionally specify a MIME type.
    • async arrayBuffer(): Returns an ArrayBuffer for the entry.
    • async stream(options?: StreamOptions): Returns a ReadableStream<Uint8Array>. This is the most memory-efficient way to handle large entries as it decompresses data on-the-fly.
    • async text(): Returns the entry content as a UTF-8 string.
    • async json(): Returns the entry content parsed as JSON.

    Note on Memory: blob() and arrayBuffer() load the entire entry into memory. For very large files, use stream() to avoid memory exhaustion.

    const { entries } = await unzip(blob);
    const entry = entries['large-file.bin'];
    
    // Efficiently stream a large entry to a writable stream
    const writable = await fileHandle.createWritable();
    await (await entry.stream({ chunkSize: 16 * 1024 * 1024 })).pipeTo(writable);
  11. Shut down workers with cleanup()

    main

    If you have enabled workers, call cleanup() to shut them down. This is particularly important in Node.js environments to allow the process to exit gracefully, as the process will wait for workers to terminate.

    import { cleanup } from 'unzipit';
    
    // Call when the application is shutting down
    cleanup();
  12. Use unzip() and unzipRaw() to extract zip files

    main

    The unzip and unzipRaw functions are the primary entry points for extracting zip files. They accept a url, Blob, TypedArray, ArrayBuffer, or a Reader.

    • unzip(src): Returns a ZipInfo object where entries is a dictionary mapping filenames (strings) to ZipEntry objects. Use this for standard UTF-8 filenames.
    • unzipRaw(src): Returns a ZipInfoRaw object where entries is an array of ZipEntry objects. Use this if the zip file contains non-UTF-8 filenames; you can then use entry.nameBytes to decode the names manually.
    // Standard usage with UTF-8 filenames
    const { zip, entries } = await unzip(blob);
    const entry = entries['path/to/file.txt'];
    
    // Usage for non-UTF-8 filenames
    const { zip, entries } = await unzipRaw(blob);
    // entries is an array, so iterate or find by nameBytes
    for (const entry of entries) {
      const name = new TextDecoder('windows-1252').decode(entry.nameBytes);
      console.log(name);
    }