Giter VIP home page Giter VIP logo

fflate's Introduction

fflate

High performance (de)compression in an 8kB package

Why fflate?

fflate (short for fast flate) is the fastest, smallest, and most versatile pure JavaScript compression and decompression library in existence, handily beating pako, tiny-inflate, and UZIP.js in performance benchmarks while being multiple times more lightweight. Its compression ratios are often better than even the original Zlib C library. It includes support for DEFLATE, GZIP, and Zlib data. Data compressed by fflate can be decompressed by other tools, and vice versa.

In addition to the base decompression and compression APIs, fflate supports high-speed ZIP file archiving for an extra 3 kB. In fact, the compressor, in synchronous mode, compresses both more quickly and with a higher compression ratio than most compression software (even Info-ZIP, a C program), and in asynchronous mode it can utilize multiple threads to achieve over 3x the performance of virtually any other utility.

pako tiny-inflate UZIP.js fflate
Decompression performance 1x Up to 40% slower Up to 40% faster Up to 40% faster
Compression performance 1x N/A Up to 25% faster Up to 50% faster
Base bundle size (minified) 45.6kB 3kB (inflate only) 14.2kB 8kB (3kB for inflate only)
Decompression support
Compression support
ZIP support
Streaming support
GZIP support
Supports files up to 4GB
Doesn't hang on error
Dictionary support
Multi-thread/Asynchronous
Streaming ZIP support
Uses ES Modules

Demo

If you'd like to try fflate for yourself without installing it, you can take a look at the browser demo. Since fflate is a pure JavaScript library, it works in both the browser and Node.js (see Browser support for more info).

Usage

Install fflate:

npm i fflate # or yarn add fflate, or pnpm add fflate

Import:

// I will assume that you use the following for the rest of this guide
import * as fflate from 'fflate';

// However, you should import ONLY what you need to minimize bloat.
// So, if you just need GZIP compression support:
import { gzipSync } from 'fflate';
// Woo! You just saved 20 kB off your bundle with one line.

If your environment doesn't support ES Modules (e.g. Node.js):

// Try to avoid this when using fflate in the browser, as it will import
// all of fflate's components, even those that you aren't using.
const fflate = require('fflate');

If you want to load from a CDN in the browser:

<!--
You should use either UNPKG or jsDelivr (i.e. only one of the following)

Note that tree shaking is completely unsupported from the CDN. If you want
a small build without build tools, please ask me and I will make one manually
with only the features you need. This build is about 31kB, or 11.5kB gzipped.
-->
<script src="https://unpkg.com/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/umd/index.js"></script>
<!-- Now, the global variable fflate contains the library -->

<!-- If you're going buildless but want ESM, import from Skypack -->
<script type="module">
  import * as fflate from 'https://cdn.skypack.dev/[email protected]?min';
</script>

If you are using Deno:

// Don't use the ?dts Skypack flag; it isn't necessary for Deno support
// The @deno-types comment adds TypeScript typings

// @deno-types="https://cdn.skypack.dev/[email protected]/lib/index.d.ts"
import * as fflate from 'https://cdn.skypack.dev/[email protected]?min';

If your environment doesn't support bundling:

// Again, try to import just what you need

// For the browser:
import * as fflate from 'fflate/esm/browser.js';
// If the standard ESM import fails on Node (i.e. older version):
import * as fflate from 'fflate/esm';

And use:

// This is an ArrayBuffer of data
const massiveFileBuf = await fetch('/aMassiveFile').then(
  res => res.arrayBuffer()
);
// To use fflate, you need a Uint8Array
const massiveFile = new Uint8Array(massiveFileBuf);
// Note that Node.js Buffers work just fine as well:
// const massiveFile = require('fs').readFileSync('aMassiveFile.txt');

// Higher level means lower performance but better compression
// The level ranges from 0 (no compression) to 9 (max compression)
// The default level is 6
const notSoMassive = fflate.zlibSync(massiveFile, { level: 9 });
const massiveAgain = fflate.unzlibSync(notSoMassive);
const gzipped = fflate.gzipSync(massiveFile, {
  // GZIP-specific: the filename to use when decompressed
  filename: 'aMassiveFile.txt',
  // GZIP-specific: the modification time. Can be a Date, date string,
  // or Unix timestamp
  mtime: '9/1/16 2:00 PM'
});

fflate can autodetect a compressed file's format as well:

const compressed = new Uint8Array(
  await fetch('/GZIPorZLIBorDEFLATE').then(res => res.arrayBuffer())
);
// Above example with Node.js Buffers:
// Buffer.from('H4sIAAAAAAAAE8tIzcnJBwCGphA2BQAAAA==', 'base64');

const decompressed = fflate.decompressSync(compressed);

Using strings is easy with fflate's string conversion API:

const buf = fflate.strToU8('Hello world!');

// The default compression method is gzip
// Increasing mem may increase performance at the cost of memory
// The mem ranges from 0 to 12, where 4 is the default
const compressed = fflate.compressSync(buf, { level: 6, mem: 8 });

// When you need to decompress:
const decompressed = fflate.decompressSync(compressed);
const origText = fflate.strFromU8(decompressed);
console.log(origText); // Hello world!

If you need to use an (albeit inefficient) binary string, you can set the second argument to true.

const buf = fflate.strToU8('Hello world!');

// The second argument, latin1, is a boolean that indicates that the data
// is not Unicode but rather should be encoded and decoded as Latin-1.
// This is useful for creating a string from binary data that isn't
// necessarily valid UTF-8. However, binary strings are incredibly
// inefficient and tend to double file size, so they're not recommended.
const compressedString = fflate.strFromU8(
  fflate.compressSync(buf),
  true
);
const decompressed = fflate.decompressSync(
  fflate.strToU8(compressedString, true)
);
const origText = fflate.strFromU8(decompressed);
console.log(origText); // Hello world!

You can use streams as well to incrementally add data to be compressed or decompressed:

// This example uses synchronous streams, but for the best experience
// you'll definitely want to use asynchronous streams.

let outStr = '';
const gzipStream = new fflate.Gzip({ level: 9 }, (chunk, isLast) => {
  // accumulate in an inefficient binary string (just an example)
  outStr += fflate.strFromU8(chunk, true);
});

// You can also attach the data handler separately if you don't want to
// do so in the constructor.
gzipStream.ondata = (chunk, final) => { ... }

// Since this is synchronous, all errors will be thrown by stream.push()
gzipStream.push(chunk1);
gzipStream.push(chunk2);

...

// You should mark the last chunk by using true in the second argument
// In addition to being necessary for the stream to work properly, this
// will also set the isLast parameter in the handler to true.
gzipStream.push(lastChunk, true);

console.log(outStr); // The compressed binary string is now available

// The options parameter for compression streams is optional; you can
// provide one parameter (the handler) or none at all if you set
// deflateStream.ondata later.
const deflateStream = new fflate.Deflate((chunk, final) => {
  console.log(chunk, final);
});

// If you want to create a stream from strings, use EncodeUTF8
const utfEncode = new fflate.EncodeUTF8((data, final) => {
  // Chaining streams together is done by pushing to the
  // next stream in the handler for the previous stream
  deflateStream.push(data, final);
});

utfEncode.push('Hello'.repeat(1000));
utfEncode.push(' '.repeat(100));
utfEncode.push('world!'.repeat(10), true);

// The deflateStream has logged the compressed data

const inflateStream = new fflate.Inflate();
inflateStream.ondata = (decompressedChunk, final) => { ... };

let stringData = '';

// Streaming UTF-8 decode is available too
const utfDecode = new fflate.DecodeUTF8((data, final) => {
  stringData += data;
});

// Decompress streams auto-detect the compression method, as the
// non-streaming decompress() method does.
const dcmpStrm = new fflate.Decompress((chunk, final) => {
  console.log(chunk, 'was encoded with GZIP, Zlib, or DEFLATE');
  utfDecode.push(chunk, final);
});

dcmpStrm.push(zlibJSONData1);
dcmpStrm.push(zlibJSONData2, true);

// This succeeds; the UTF-8 decoder chained with the unknown compression format
// stream to reach a string as a sink.
console.log(JSON.parse(stringData));

You can create multi-file ZIP archives easily as well. Note that by default, compression is enabled for all files, which is not useful when ZIPping many PNGs, JPEGs, PDFs, etc. because those formats are already compressed. You should either override the level on a per-file basis or globally to avoid wasting resources.

// Note that the asynchronous version (see below) runs in parallel and
// is *much* (up to 3x) faster for larger archives.
const zipped = fflate.zipSync({
  // Directories can be nested structures, as in an actual filesystem
  'dir1': {
    'nested': {
      // You can use Unicode in filenames
      '你好.txt': fflate.strToU8('Hey there!')
    },
    // You can also manually write out a directory path
    'other/tmp.txt': new Uint8Array([97, 98, 99, 100])
  },

  // You can also provide compression options
  'massiveImage.bmp': [aMassiveFile, {
    level: 9,
    mem: 12
  }],
  // PNG is pre-compressed; no need to waste time
  'superTinyFile.png': [aPNGFile, { level: 0 }],

  // Directories take options too
  'exec': [{
    'hello.sh': [fflate.strToU8('echo hello world'), {
      // ZIP only: Set the operating system to Unix
      os: 3,
      // ZIP only: Make this file executable on Unix
      attrs: 0o755 << 16
    }]
  }, {
    // ZIP and GZIP support mtime (defaults to current time)
    mtime: new Date('10/20/2020')
  }]
}, {
  // These options are the defaults for all files, but file-specific
  // options take precedence.
  level: 1,
  // Obfuscate last modified time by default 
  mtime: new Date('1/1/1980')
});

// If you write the zipped data to myzip.zip and unzip, the folder
// structure will be outputted as:

// myzip.zip (original file)
// dir1
// |-> nested
// |   |-> 你好.txt
// |-> other
// |   |-> tmp.txt
// massiveImage.bmp
// superTinyFile.png

// When decompressing, folders are not nested; all filepaths are fully
// written out in the keys. For example, the return value may be:
// { 'nested/directory/structure.txt': Uint8Array(2) [97, 97] }
const decompressed = fflate.unzipSync(zipped, {
  // You may optionally supply a filter for files. By default, all files in a
  // ZIP archive are extracted, but a filter can save resources by telling
  // the library not to decompress certain files
  filter(file) {
    // Don't decompress the massive image or any files larger than 10 MiB
    return file.name != 'massiveImage.bmp' && file.originalSize <= 10_000_000;
  }
});

If you need extremely high performance or custom ZIP compression formats, you can use the highly-extensible ZIP streams. They take streams as both input and output. You can even use custom compression/decompression algorithms from other libraries, as long as they are defined in the ZIP spec (see section 4.4.5). If you'd like more info on using custom compressors, feel free to ask.

// ZIP object
// Can also specify zip.ondata outside of the constructor
const zip = new fflate.Zip((err, dat, final) => {
  if (!err) {
    // output of the streams
    console.log(dat, final);
  }
});

const helloTxt = new fflate.ZipDeflate('hello.txt', {
  level: 9
});

// Always add streams to ZIP archives before pushing to those streams
zip.add(helloTxt);

helloTxt.push(chunk1);
// Last chunk
helloTxt.push(chunk2, true);

// ZipPassThrough is like ZipDeflate with level 0, but allows for tree shaking
const nonStreamingFile = new fflate.ZipPassThrough('test.png');
zip.add(nonStreamingFile);
// If you have data already loaded, just .push(data, true)
nonStreamingFile.push(pngData, true);

// You need to call .end() after finishing
// This ensures the ZIP is valid
zip.end();

// Unzip object
const unzipper = new fflate.Unzip();

// This function will almost always have to be called. It is used to support
// compression algorithms such as BZIP2 or LZMA in ZIP files if just DEFLATE
// is not enough (though it almost always is).
// If your ZIP files are not compressed, this line is not needed.
unzipper.register(fflate.UnzipInflate);

const neededFiles = ['file1.txt', 'example.json'];

// Can specify handler in constructor too
unzipper.onfile = file => {
  // file.name is a string, file is a stream
  if (neededFiles.includes(file.name)) {
    file.ondata = (err, dat, final) => {
      // Stream output here
      console.log(dat, final);
    };
    
    console.log('Reading:', file.name);

    // File sizes are sometimes not set if the ZIP file did not encode
    // them, so you may want to check that file.size != undefined
    console.log('Compressed size', file.size);
    console.log('Decompressed size', file.originalSize);

    // You should only start the stream if you plan to use it to improve
    // performance. Only after starting the stream will ondata be called.
    // This method will throw if the compression method hasn't been registered
    file.start();
  }
};

// Try to keep under 5,000 files per chunk to avoid stack limit errors
// For example, if all files are a few kB, multi-megabyte chunks are OK
// If files are mostly under 100 bytes, 64kB chunks are the limit
unzipper.push(zipChunk1);
unzipper.push(zipChunk2);
unzipper.push(zipChunk3, true);

As you may have guessed, there is an asynchronous version of every method as well. Unlike most libraries, this will cause the compression or decompression run in a separate thread entirely and automatically by using Web (or Node) Workers. This means that the processing will not block the main thread at all.

Note that there is a significant initial overhead to using workers of about 50ms for each asynchronous function. For instance, if you call unzip ten times, the overhead only applies for the first call, but if you call unzip and zlib, they will each cause the 50ms delay. For small (under about 50kB) payloads, the asynchronous APIs will be much slower. However, if you're compressing larger files/multiple files at once, or if the synchronous API causes the main thread to hang for too long, the callback APIs are an order of magnitude better.

import {
  gzip, zlib, AsyncGzip, zip, unzip, strFromU8,
  Zip, AsyncZipDeflate, Unzip, AsyncUnzipInflate
} from 'fflate';

// Workers will work in almost any browser (even IE11!)
// All of the async APIs use a node-style callback as so:
const terminate = gzip(aMassiveFile, (err, data) => {
  if (err) {
    // The compressed data was likely corrupt, so we have to handle
    // the error.
    return;
  }
  // Use data however you like
  console.log(data.length);
});

if (needToCancel) {
  // The return value of any of the asynchronous APIs is a function that,
  // when called, will immediately cancel the operation. The callback
  // will not be called.
  terminate();
}

// If you wish to provide options, use the second argument.

// The consume option will render the data inside aMassiveFile unusable,
// but can improve performance and dramatically reduce memory usage.
zlib(aMassiveFile, { consume: true, level: 9 }, (err, data) => {
  // Use the data
});

// Asynchronous streams are similar to synchronous streams, but the
// handler has the error that occurred (if any) as the first parameter,
// and they don't block the main thread.

// Additionally, any buffers that are pushed in will be consumed and
// rendered unusable; if you need to use a buffer you push in, you
// should clone it first.
const gzs = new AsyncGzip({ level: 9, mem: 12, filename: 'hello.txt' });
let wasCallbackCalled = false;
gzs.ondata = (err, chunk, final) => {
  // Note the new err parameter
  if (err) {
    // Note that after this occurs, the stream becomes corrupt and must
    // be discarded. You can't continue pushing chunks and expect it to
    // work.
    console.error(err);
    return;
  }
  wasCallbackCalled = true;
}
gzs.push(chunk);

// Since the stream is asynchronous, the callback will not be called
// immediately. If such behavior is absolutely necessary (it shouldn't
// be), use synchronous streams.
console.log(wasCallbackCalled) // false

// To terminate an asynchronous stream's internal worker, call
// stream.terminate().
gzs.terminate();

// This is way faster than zipSync because the compression of multiple
// files runs in parallel. In fact, the fact that it's parallelized
// makes it faster than most standalone ZIP CLIs. The effect is most
// significant for multiple large files; less so for many small ones.
zip({ f1: aMassiveFile, 'f2.txt': anotherMassiveFile }, {
  // The options object is still optional, you can still do just
  // zip(archive, callback)
  level: 6
}, (err, data) => {
  // Save the ZIP file
});

// unzip is the only async function without support for consume option
// It is parallelized, so unzip is also often much faster than unzipSync
unzip(aMassiveZIPFile, (err, unzipped) => {
  // If the archive has data.xml, log it here
  console.log(unzipped['data.xml']);
  // Conversion to string
  console.log(strFromU8(unzipped['data.xml']))
});

// Streaming ZIP archives can accept asynchronous streams. This automatically
// uses multicore compression.
const zip = new Zip();
zip.ondata = (err, chunk, final) => { ... };
// The JSON and BMP are compressed in parallel
const exampleFile = new AsyncZipDeflate('example.json');
zip.add(exampleFile);
exampleFile.push(JSON.stringify({ large: 'object' }), true);
const exampleFile2 = new AsyncZipDeflate('example2.bmp', { level: 9 });
zip.add(exampleFile2);
exampleFile2.push(ec2a);
exampleFile2.push(ec2b);
exampleFile2.push(ec2c);
...
exampleFile2.push(ec2Final, true);
zip.end();

// Streaming Unzip should register the asynchronous inflation algorithm
// for parallel processing.
const unzip = new Unzip(stream => {
  if (stream.name.endsWith('.json')) {
    stream.ondata = (err, chunk, final) => { ... };
    stream.start();

    if (needToCancel) {
      // To cancel these streams, call .terminate()
      stream.terminate();
    }
  }
});
unzip.register(AsyncUnzipInflate);
unzip.push(data, true);

See the documentation for more detailed information about the API.

Bundle size estimates

The bundle size measurements for fflate on sites like Bundlephobia include every feature of the library and should be seen as an upper bound. As long as you are using tree shaking or dead code elimination, this table should give you a general idea of fflate's bundle size for the features you need.

The maximum bundle size that is possible with fflate is about 31kB (11.5kB gzipped) if you use every single feature, but feature parity with pako is only around 10kB (as opposed to 45kB from pako). If your bundle size increases dramatically after adding fflate, please create an issue.

Feature Bundle size (minified) Nearest competitor
Decompression 3kB tiny-inflate
Compression 5kB UZIP.js, 2.84x larger
Async decompression 4kB (1kB + raw decompression) N/A
Async compression 6kB (1kB + raw compression) N/A
ZIP decompression 5kB (2kB + raw decompression) UZIP.js, 2.84x larger
ZIP compression 7kB (2kB + raw compression) UZIP.js, 2.03x larger
GZIP/Zlib decompression 4kB (1kB + raw decompression) pako, 11.4x larger
GZIP/Zlib compression 5kB (1kB + raw compression) pako, 9.12x larger
Streaming decompression 4kB (1kB + raw decompression) pako, 11.4x larger
Streaming compression 5kB (1kB + raw compression) pako, 9.12x larger

What makes fflate so fast?

Many JavaScript compression/decompression libraries exist. However, the most popular one, pako, is merely a clone of Zlib rewritten nearly line-for-line in JavaScript. Although it is by no means poorly made, pako doesn't recognize the many differences between JavaScript and C, and therefore is suboptimal for performance. Moreover, even when minified, the library is 45 kB; it may not seem like much, but for anyone concerned with optimizing bundle size (especially library authors), it's more weight than necessary.

Note that there exist some small libraries like tiny-inflate for solely decompression, and with a minified size of 3 kB, it can be appealing; however, its performance is lackluster, typically 40% worse than pako in my tests.

UZIP.js is both faster (by up to 40%) and smaller (14 kB minified) than pako, and it contains a variety of innovations that make it excellent for both performance and compression ratio. However, the developer made a variety of tiny mistakes and inefficient design choices that make it imperfect. Moreover, it does not support GZIP or Zlib data directly; one must remove the headers manually to use UZIP.js.

So what makes fflate different? It takes the brilliant innovations of UZIP.js and optimizes them while adding direct support for GZIP and Zlib data. And unlike all of the above libraries, it uses ES Modules to allow for partial builds through tree shaking, meaning that it can rival even tiny-inflate in size while maintaining excellent performance. The end result is a library that, in total, weighs 8kB minified for the core build (3kB for decompression only and 5kB for compression only), is about 15% faster than UZIP.js or up to 60% faster than pako, and achieves the same or better compression ratio than the rest.

Before you decide that fflate is the end-all compression library, you should note that JavaScript simply cannot rival the performance of a native program. If you're only using Node.js, it's probably better to use the native Zlib bindings, which tend to offer the best performance. Though note that even against Zlib, fflate is only around 30% slower in decompression and 10% slower in compression, and can still achieve better compression ratios!

What about CompressionStream?

Like fflate, the Compression Streams API provides DEFLATE, GZIP, and Zlib compression and decompression support. It's a good option if you'd like to compress or decompress data without installing any third-party libraries, and it wraps native Zlib bindings to achieve better performance than what most JavaScript programs can achieve.

However, browsers do not offer any native non-streaming compression API, and CompressionStream has surprisingly poor performance on data already loaded into memory; fflate tends to be faster even for files that are dozens of megabytes large. Similarly, fflate is much faster for files under a megabyte because it avoids marshalling overheads. Even when streaming hundreds of megabytes of data, the native API usually performs between 30% faster and 10% slower than fflate. And Compression Streams have many other disadvantages - no ability to control compression level, poor support for older browsers, no ZIP support, etc.

If you'd still prefer to depend upon a native browser API but want to support older browsers, you can use an fflate-based Compression Streams ponyfill.

Browser support

fflate makes heavy use of typed arrays (Uint8Array, Uint16Array, etc.). Typed arrays can be polyfilled at the cost of performance, but the most recent browser that doesn't support them is from 2011, so I wouldn't bother.

The asynchronous APIs also use Worker, which is not supported in a few browsers (however, the vast majority of browsers that support typed arrays support Worker).

Other than that, fflate is completely ES3, meaning you probably won't even need a bundler to use it.

Testing

You can validate the performance of fflate with npm test. It validates that the module is working as expected, ensures the outputs are no more than 5% larger than competitors at max compression, and outputs performance metrics to test/results.

Note that the time it takes for the CLI to show the completion of each test is not representative of the time each package took, so please check the JSON output if you want accurate measurements.

License

This software is MIT Licensed, with special exemptions for projects and organizations as noted below:

  • SheetJS is exempt from MIT licensing and may license any source code from this software under the BSD Zero Clause License

fflate's People

Contributors

101arrowz avatar brizental avatar codewitchbella avatar karyon avatar lelinhtinh avatar methuselah96 avatar oddmorning avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fflate's Issues

Support promises in async version

Hey! Great library!

I am using it in the browser, and it would be great if the async versions would support promises as well instead of just callbacks.

Ideally it would look something like this:

const buffer = await fflate.zip(
  {
    'index.html': fflate.strToU8(str),
    // other files...
  },
  { level: 4 }
);

It looks like synchronous code but it doesn't block the main thread.

It makes sense to support promises, newer versions of node are doing so with stuff like fs/promises and timers/promises

can not decompress data by golang "compress/flate"

compress by golang

main.go

package main

import (
	"bytes"
	"compress/flate"
	"os"
)

func main() {
	data, err := encode([]byte("hello,world!"))
	if err != nil {
		panic(err)
	}
	os.WriteFile("1.data", data, 0666)
}

func encode(data []byte) ([]byte, error) {
	var buf = bytes.NewBuffer(nil)
	var w, err = flate.NewWriter(buf, flate.DefaultCompression)
	if err != nil {
		return nil, err
	}
	defer w.Close()
	_, err = w.Write(data)
	if err != nil {
		return nil, err
	}
	err = w.Flush()
	return buf.Bytes(), err
}

decompress

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

const data = fs.readFileSync('1.data')

console.info(fflate.inflateSync(data))

got error unexpected EOF

I just want to download a .js file to use this

Where's a distributable for the browser that doesn't require some 3rd party crap like skypack or jsdelivr? I want to host it myself. I don't want to compile packages with node/npm tools. Do something like this: https://github.com/Stuk/jszip/tree/master/dist
or https://github.com/nodeca/pako/tree/master/dist

But you say your software works better than the others and supports what I really want (zlib not zip), so I'd like to give it a try, but you don't make it easy.

Migration from jszip to fflate

Hi,

thank you for your comment at Stuk/jszip#721 (comment)

I am trying to review fflate if it would meet my requirements. However, the documentation is not very good and I cannot get my compressed zip file unzipped. Can you help out here?

My code from jszip

//zippedData is binary String
        let foundXml = "";
        try {
            var JSZip = require("jszip");
            var zip = new JSZip();
        
            const zipped = await zip.loadAsync(zippedData);
            foundXml = await zipped.file("data.xml").async("string");
        } catch(err) {
            console.log(err);
        }

What i tried in fflate

            try {
                let buffer = fflate.strToU8(zippedData, true);
                let unzipped = fflate.unzipSync(buffer);
                console.log("unzipped", unzipped);                     //-> this returns {"data.xml": []}
                console.log("unzipped", unzipped["data.xml"]);                    //-> this returns []
            } catch(err) {
                console.log(err);
            }

So I get the file unzipped but the data.xml inside is empty.

Streaming Gunzip breaks sometimes

How to reproduce
https://codepen.io/manucorporat/pen/PopXJgb?editors=1010

Notice the:

  const step = 5141811;

With this specific value, the ungzip breaks with Error: invalid length/literal, but works with other values, higher and lower.

This variables allows to rechunk the file into smaller pieces.

This code is an example to minimally reproduce the issue, in production we got this error with random setups.

The problem
Gunzip streaming API sometimes errors.

Feature request: Support ZIP

Supporting creating and extracting ZIP files would be very useful, especially for real-world use cases like zipping multiple uploads into one on the client-side and then uploading to a pre-signed S3 URL.

DEFLATE `level` parameters compared to zlib

deo is described in source as:

// deflate options (nice << 13) | chain
const deo = /*#__PURE__*/ new u32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]);

If nice and chain are understood to be the tunable nice_length and max_chain parameters as defined in zlib, the constants look different from expectations. For example:

local const config configuration_table[10] = {
/*      good lazy nice chain */
/* 0 */ {0,    0,  0,    0, deflate_stored},  /* store only */
/* 1 */ {4,    4,  8,    4, deflate_fast}, /* max speed, no lazy matches */
/* 2 */ {4,    5, 16,    8, deflate_fast},
/* 3 */ {4,    6, 32,   32, deflate_fast},

/* 4 */ {4,    4, 16,   16, deflate_slow},  /* lazy matches */
/* 5 */ {8,   16, 32,   32, deflate_slow},
/* 6 */ {8,   16, 128, 128, deflate_slow},
/* 7 */ {8,   32, 128, 256, deflate_slow},
/* 8 */ {32, 128, 258, 1024, deflate_slow},
/* 9 */ {32, 258, 258, 4096, deflate_slow}}; /* max compression */

Since deo is indexed at level - 1, it would seem that deo[2] should be (32 << 13) | 32 (262176). The array would be unsorted in this case since the next level is (16 << 13) | 16 (131088)

Streaming backpressure

The streaming API seems to lack support for stream backpressure. Without backpressure, it's not possible to ensure that we're not pushing data to the stream too quickly for it to handle. If we push data too quickly and the fflate stream can't keep up, then it's possible that the data will be sitting around in memory for longer than we want.

Stream backpressure helps us to detect this condition and to stop calling push() until the backed up chunks have been processed.

The example toNativeStream code used on the website doesn't support backpressure. Is it possible to add support for this?

Or, at least expose the necessary information (i.e. outstanding chunk count, and a way to call a user-provided callback when the outstanding chunk count falls below a threshold so we can start pushing again)?

Separately, it might be helpful to just offer a WHATWG TransformStream interface to the user so they don't need to implement it themselves.

Clean up tests

They're ugly. Opening this issue as a reminder to do this at some point.

Feature request: Shared context

It would be nice to have the ability to create shared context/sliding window

let inflate = new Inflate({ chunkSize: 128 * 1024 })

inflate.push(chunk)
inflate.push(chunk, Z_SYNC_FLUSH)

console.log(inflate.result.toString())

Feature request: Progress callback

It would be great to have some sort of progress callback which could give an estimated percentage of the completed work. Am willing to help out with this feature if you can point me in the right direction. :)

Is it possible to stop using `setTimeout` throughout the code?

How to reproduce

Attempt to use this library in an environment that does not provide the setTimeout API. In my case, that is QML.

The problem

QML does not provide the setTimeout API. I can fake it, but from looking at the code it seems it might be possible to stop using setTimeout completely.

Can't find variable: Worker

In Webkit ( I tried both Safari macOS and Safari iOS ) an error is thrown when I call fflate.unzip in a web worker :
ReferenceError: can't find variable: Worker

This is where it comes from :

const w = new Worker(c + workerAdd, { eval: true })

to be accurate I bundle with Webpack, I import unzip like so :

import { unzip } from 'fflate'

and then I use it in this function :

unzip( new Uint8Array( buffer ), (err, unzipped) => {

	if ( err ) reject( err );

	resolve( unzipped.f1 );

});

Everything works like a charm in Chrome and Chrome for Android ( congrats for your work by the way ).
I tried with different files, big and small ( 1.5Mb to >300Mb ).

I believe this error occurs because Webkit doesn's support creating new web workers from inside web workers. I found a thread here with some vague references.

I need to unzip the file inside my own worker because I'm doing other expensive things after unzipping, it would not make sense to message this big buffer back and forth the main thread uselessly.

It's possible that I didn't understand how to use your library, and I should use another method to get this done. In that case can you give me a hint please ? My zipped files are compressed with fflate.zip ( in nodejs ).

possibly related: #11

Crashing in Ios.

compressing in ios is crashing. I didn't check in android.

the code I used is

    const data = zipSync(tozip, { level: 0, mem: 0 });
    saveAs(new Blob([data as Uint8Array]), `${this.title}.zip`);

the result zip file made with pc is about 200~300mb and including about 80 jpg photos.

Thank you, I always appreciate your work.

Zstandard support

What can't you do right now?

Zstandard compresses amazingly well, for my use case up to 4x better than what I can get from gzip, and it decompresses perhaps even more amazingly well.

For my use case I kinda have to use Zstandard given its characteristics, but there doesn't seem to be a great port of Zstandard for the browser, plus I'm already using fflate which I find well done and pretty fast, but it just doesn't support Zstandard.

An optimal solution

Optimally fflate would add support for compressing and decompressing (although just decompressing would be enough for me, and I'd guess for most other people too) using Zstandard with a high quality and fast implementation.

(How) is this done by other libraries?

  • node-zstd: it uses native bindings, a no-no for the web.

  • node-zstandard: it just spawns zstd as a child process.

  • zstd-codec: it's a 1 year out of date emscripten compiled version of zstd, which weighs almost 1MB min+gzipped and can't be tree-shaken.

  • zstddec: this seems the best port to webassembly, it only supports decoding and weighs ~17kb, but it has the following pretty concerning warning in its readme:

    Limitations: The decoder may fail with the error wasm function signature contains illegal type when the uncompressedSize is not known in advance and given to the decode() method. This is presumably a bug in the WASM bindings, which I am not yet sure how to fix.

Basically there currently isn't a great option for decoding zstd files on the web and I'd love to see one from fflate.

Support adding files larger than 4GB

What can't you do right now?
As documented in the changelog, zip files cannot contain files greater than 4GB. It would be great if we could create zip files containing larger files, since it looks like the zip64 format can support that use case.

An optimal solution
Ideally, the current streaming interface would automatically generate valid zip output when adding larger than 4GB files. It may also be necessary for the user to pass an option indicating that we need a zip64 header when adding a large file; that would be fine too.

As an interim solution, instead of adding full zip64 support, it would be nice if ondata returned an error when trying to write too large of a file instead of silently generating a corrupt .zip file. Currently, it looks like the generated .zip is the right size, but when unzipped it outputs truncated files. I suspect the size field just wraps around at 4GB; could you detect that and return an error?

Invalid gzip data (fflate gzipped the file) and corruption on decompression (Deno)

I am seeing a few issues with Gzip/Gunzip on Deno.

One is, compressing then decompressing modest size text files, give corrupt and much smaller uncompressed version.
Another is compressing a modest sized binary file is generating an invalid compressed file format, and when attempting to decompress get an Invalid gzip data exception.

I wrote the following test to demonstrate the problem:

import * as fflate from 'https://cdn.skypack.dev/fflate';

async function zpipe(reader: Deno.Reader, stream: any) {
  let total = 0;
  async function push(p: Uint8Array, isLast?: boolean) {
    console.log('push', p.byteLength);
    debugger;
    await stream.push(p, isLast);
    total += p.byteLength;
  }
  let prevBlock;
  for await (const block of Deno.iter(reader)) {
    if (prevBlock) await push(prevBlock);
    prevBlock = block;
  }
  if (prevBlock) await push(prevBlock, true);
  console.log(`pushed ${total} bytes`);
}

function zip(from: string, to: string, options = {}) {
  let total = 0;
  return new Promise<void>(async (resolve, reject) => {
    const hFrom = await Deno.open(from, { read: true });
    const hTo = await Deno.open(to, { write: true, create: true, truncate: true });
    const zipper: any = new fflate.Gzip({ level: 9 }, async (chunk: Uint8Array, isLast: boolean) => {
      console.log('zip write chunk', chunk.byteLength);
      await hTo.write(chunk);
      total += chunk.byteLength;
      if (isLast) {
        console.log(`zip close dest file, ${total} bytes`);
        hTo.close();
        resolve();
      }
    });
    await zpipe(hFrom, zipper);
    console.log('zip close source file');
    hFrom.close();
  });
}

function unzip(from: string, to: string) {
  let total = 0;
  return new Promise<void>(async (resolve, reject) => {
    const hFrom = await Deno.open(from, { read: true });
    const hTo = await Deno.open(to, { write: true, create: true, truncate: true });
    const unzipper: any = new fflate.Gunzip();
    unzipper.ondata = async (chunk: Uint8Array, isLast: boolean) => {
      console.log('unzip write chunk', chunk.byteLength);
      await hTo.write(chunk);
      total += chunk.length;
      if (isLast) {
        console.log(`unzip close dest file, ${total} bytes`);
        hTo.close();
        resolve();
      }
    };
    await zpipe(hFrom, unzipper);
    console.log('unzip close source file');
    hFrom.close();
  });
}

const fn = Deno.args[0];
await zip(fn, `${fn}.gz`);
await unzip(`${fn}.gz`, `${fn}.unzipped`);

As a test, I downloaded fflate.js and compressed and decompressed that using the code above:

deno run --allow-all gzip.ts fflate.js

The resulting file sizes are:

-a----       20/03/2021     15:43          54748 fflate.js
-a----       21/03/2021     00:57          14322 fflate.js.gz
-a----       21/03/2021     00:57          16384 fflate.js.unzipped

For the binary file test, I generated a 32kb binary random file using dd

dd if=/dev/random of=LARGE_FILE ibs=1k count=32

Then compress it with the above code:

deno run --allow-all gzip.ts LARGE_FILE

This throws an error on the unzip:

error: Uncaught (in promise) invalid gzip data

And file reports a strange size on the compressed file:

LARGE_DATA.gz:        gzip compressed data, last modified: Sun Mar 21 01:01:11 2021, max compression, from Unix, original size modulo 2^32 100822718

Gzip corruption issue

I've encountered a few corrupted files after using gzip with fflate, both the async and sync versions. Results appear to be deterministic, always occuring on the same files on different machines. I've made sure that the issue is somewhere in fflate. (removed all other variables and side-by-side with pako. Pako did not produce any corruption)

Issue only occurs on a small subset of files, less then 10% of what we use. It's always rather large files, in the >200MB range. I tried changing the compression level, and this gave some good initial results. However, it appears that another compression level just causes corruption in completely different files.

I've currently reverted back to pako, but would like to continue using fflate once this is resolved. If you want, I can send you example files that get corrupted to help with debugging this. (These are large medical files stripped of identifiable data, yet still to sensitive to just upload publically ;) )

Alignment

What can't you do right now?

Recently I've been trying to generate USDZ files in JavaScript: mrdoob/three.js#21245

The USDZ file is essentially an zip file with 0 compression that contains a 3d file and textures.

So far fflate has been great for creating the file. However, yesterday I learned that in order to produce valid USDZ files I need to make sure that the files are aligned to 64 bytes. Currently my files are invalid:

Screen Shot 2021-02-10 at 9 05 31 PM

From the spec page:

Screen Shot 2021-02-09 at 11 41 55 PM

I'm aware this is quite an uncommon use case, but would you be interested in adding support for this?

Calling `inflate` on data created from `Pako.deflate` returns `(null, null)`

Some observations that I noted:

inflate fails with (null, null)

image

inflateSync fails with EOF:

image

This is probably the most important problem since one would expect either data or an error.

decompress works fine:

image

deflateSync : incorrect header check

It appears as though Pako has a different idea of what constitutes inflate / deflate i.e.

image

gzipSync: seems to correspond to pako.inflate

image

I noted that the builtin CompressionStream seems to correlate with what Pako does i.e. 'inflate' algorithm for Pako and CompressionStream seem to be the same.

zlib: same as gzip

image

zip: fails, same as deflate

image

Summary

I've no idea what's going on here (and the issues observed may be with Pako), but I hope the above is useful feedback.

how can I replace a file in a zip file?

In my case, I need to replace a file in a zip file
For example, in my zip file a.zip, it has 2 files, like a.txt, b.txt.
I only want to replace a.txt's content, or remove a.txt, and I don't want to unzip all files in memory
And I need to use as little memory as possible,cause i have a big zip file, and the memory is not cheap in my case
By now, I zip all files in 4 s with 700m,and I have no way to replace serveral files with little memory and time.
Can you help me with some ideas?

Issue: Zip doesn't work?

I tried using the latest version of fflate, and I can't seem to get the zip() method to work.

Example CSB
https://codesandbox.io/s/fflate-zip-files-yuu21

Steps to reproduce

  1. Go to the linked CSB.
  2. Open the console and click on 'Upload Files'.
  3. Select a few files.
  4. Now watch the console not print any of the lines after zip() is called.

Additional context
I wrote a simple Promise wrapper around the callback-based zip() method so it is easier to make them work in async/await flows.

Library stop working after webpack bundle it

The library works fine in dev mode in my vue app but will stop working when webpack minify the files to create the dist bundle. To make things working again I've added this line chainWebpack: config => config.optimization.minimize(false) to my vue.config.js file, it will disable files minification but will result in worst performances in case of large app.
This is the error that is logged in console after the production build of the app is created and the user try to create a zip file

Uncaught TypeError: Cannot read property 'length' of null
    at A (chunk-vendors.64883a9e.js:6)
    at chunk-vendors.64883a9e.js:6
    at Worker.s.onerror (chunk-vendors.64883a9e.js:6)
A @ chunk-vendors.64883a9e.js:6
(anonymous) @ chunk-vendors.64883a9e.js:6
s.onerror @ chunk-vendors.64883a9e.js:6
error (async)
r @ chunk-vendors.64883a9e.js:6
X @ chunk-vendors.64883a9e.js:6
re @ chunk-vendors.64883a9e.js:6
ce @ chunk-vendors.64883a9e.js:6
h @ chunk-vendors.64883a9e.js:6
Ae @ chunk-vendors.64883a9e.js:6
(anonymous) @ write-zip.896bce5d.js:1
u @ chunk-vendors.64883a9e.js:8
(anonymous) @ chunk-vendors.64883a9e.js:8
(anonymous) @ chunk-vendors.64883a9e.js:8
a @ chunk-vendors.64883a9e.js:1
s @ chunk-vendors.64883a9e.js:1
Promise.then (async)
a @ chunk-vendors.64883a9e.js:1
s @ chunk-vendors.64883a9e.js:1
Promise.then (async)
a @ chunk-vendors.64883a9e.js:1
s @ chunk-vendors.64883a9e.js:1
Promise.then (async)
a @ chunk-vendors.64883a9e.js:1
s @ chunk-vendors.64883a9e.js:1
Promise.then (async)
a @ chunk-vendors.64883a9e.js:1
s @ chunk-vendors.64883a9e.js:1
Promise.then (async)
a @ chunk-vendors.64883a9e.js:1
s @ chunk-vendors.64883a9e.js:1
Promise.then (async)
a @ chunk-vendors.64883a9e.js:1
s @ chunk-vendors.64883a9e.js:1
Promise.then (async)
a @ chunk-vendors.64883a9e.js:1
s @ chunk-vendors.64883a9e.js:1
Promise.then (async)
a @ chunk-vendors.64883a9e.js:1
s @ chunk-vendors.64883a9e.js:1
Promise.then (async)
a @ chunk-vendors.64883a9e.js:1
s @ chunk-vendors.64883a9e.js:1
(anonymous) @ chunk-vendors.64883a9e.js:1
(anonymous) @ chunk-vendors.64883a9e.js:1
processFiles @ write-zip.896bce5d.js:1
(anonymous) @ write-zip.896bce5d.js:1
(anonymous) @ chunk-vendors.64883a9e.js:6
Ee @ chunk-vendors.64883a9e.js:6
Re @ chunk-vendors.64883a9e.js:6
t @ chunk-vendors.64883a9e.js:6

Support Promises out of the box

What can't you do right now?

Use the async methods as promises without using promisify (and for the browser, needing to bundle or otherwise add a promisfy utility).

An optimal solution

Be able to use the async methods as promises.

(How) is this done by other libraries?

JSZip appears to support promises, but not the other libraries.

Thanks!

Streaming unzipping crashes

How to reproduce
This zip seems to reproduce the issue: https://sethealth-customers.b-cdn.net/repo.zip

Try to parse the ZIP file with a single huge chunk (just trying to reproducee the issue):

import {Unzip, UnzipInflate} from 'fflate';
import {readFileSync} from 'fs';


const readFile = (file) => {
  return new Promise((resolve, reject) => {
    const chunks = [];
    file.ondata = (err, chunk, final) => {
      if (err) {
        reject(reject);
      }
      if (chunk) {
        chunks.push(chunk);
      }
      if (final) {
        resolve(concatBuffers(chunks));
      }
    };
    file.start();
  });
}

const concatBuffers = (arrays) => {
  const totalLength = arrays.reduce(
    (acc, value) => acc + value.byteLength,
    0
  );
  const output = new Uint8Array(totalLength);
  let offset = 0;
  for (let array of arrays) {
    output.set(array, offset);
    offset += array.byteLength;
  }
  return output;
};


const chunk = new Uint8Array(readFileSync("repo.zip"));
const promises = [];
const unzipper = new Unzip((handler) => {
  promises.push((async () => {
    return {
      name: handler.name,
      stream: await readFile(handler),
    }
  })());
});
unzipper.register(UnzipInflate);
unzipper.push(chunk, false);
unzipper.push(new Uint8Array(), true);
const output = await Promise.all(promises);
console.log(output);

^ This code will crash with "Invalid zip data".

The problem

fflate seems to unzip correctly this file when using unzipSync(), but fails in stream mode:

import {readFileSync} from 'fs';
import {unzipSync} from 'fflate';

const chunk = new Uint8Array(readFileSync("repo.zip"));
const output = unzipSync(chunk);
console.log(output);

Memory leak for async gzip

Uploading multiple files from a worker, creates a memory leak and eventually throws the following error:
"Cannot perform Construct on a detached ArrayBuffer"
Looking at the memory snapshot between two runs it seems like the created array buffers are not being cleaned after running the compress method

Broken surrogate pairs can't be converted back and forth with strToU8 -> strFromU8

How to reproduce

The following assertion fails:

const fflate = require ( 'fflate' );

const char = String.fromCodePoint ( 55296 );

console.assert ( fflate.strFromU8 ( fflate.strToU8 ( char ) ) === char );

The problem

I'm not sure if this is a bug exactly, I think ideally any string should be able to go through both strToU8 and then strFromU8 and exit unchanged, but the problem with the string I picked is that it comprises of a single high surrogate character, which isn't actually valid UTF-16 as there must be a low surrogate character immediately after that in the string, but JS doesn't throw so you can actually have strings like that.

Ability to finalize a stream without an input chunk

What can't you do right now?
I'm writing a wrapper around Inflate to use it as a sink for a WritableStream. However, unlike fflate's streaming implementation, finalizing a WritableStream is done by calling close(), which takes no parameters (no final chunk), and there's no way for a sink to know when a stream is finished until close() is called. I tried calling Inflate.push([], true) to signal to fflate to flush the stream but it throws an error (from the slc function at line 145):

RangeError: Invalid typed array length: -32768

Example script: https://gist.github.com/BitLooter/d507cf7c5b320e806fec06b22d2ccaa4
Tested with fflate 0.4.6 on Node 15.5.0. Note that while this is an implementation of a UnderlyingSink for a WritableStream (which Node doesn't support) it still runs in Node as it's not actually creating any stream objects, it's just calling methods on an object.

An optimal solution
I need a way to finalize an fflate stream without having any input data, to call from an UnderlyingSink's close() method.

(How) is this done by other libraries?
I have written a sink wrapper for Pako and solved this problem by pushing an empty chunk with the Z_FINISH flag, similar to what I attempted with fflate. I don't know if this is the best solution but that or a new method to finalize the stream without data would work.

Failing to decompress after strFromU8

Thanks for the library! Been testing it out and ran into an interesting error:

const { strFromU8, zlibSync, strToU8, unzlibSync } = require('fflate')

const str = 'hi'
let compressed, decompressed

// This works
compressed = zlibSync(strToU8(str))
console.log(compressed)

decompressed = strFromU8(unzlibSync(compressed))
console.log(decompressed)

// This as well
compressed = strFromU8(zlibSync(strToU8(str)), true)
console.log(compressed)

decompressed = strFromU8(unzlibSync(strToU8(compressed, true)))
console.log(decompressed)

// This does not
compressed = strFromU8(zlibSync(strToU8(str)))
console.log(compressed)

decompressed = strFromU8(unzlibSync(strToU8(compressed)))
console.log(decompressed)

The output for version 0.4.1 in node.js is:

⟩ node s.js                                                                
Uint8Array(13) [
  120, 156,   1,   2, 0,
  253, 255, 104, 105, 1,
   59,   0, 210
]
hi
x���ýÿhi�;Ò
hi
x�����hi�;�

/home/macobo/yyy/node_modules/fflate/lib/index.js:887
        throw 'invalid zlib data';
        ^
invalid zlib data
(Use `node --trace-uncaught ...` to show where the exception was thrown)

The same seems to occur for other compression functions as well - tested gzipSync, compressSync. Could there be a problem with strFromU8?

worker_threads used on fastlify

Hi :)

I recently started using this library in one of mine, basically as part of the build I statically compress a WASM file (level: 9) and then on load inflate it. This has been tested quite wide under browsers and Node.js (.cjs and .mjs) and it is doing quite well with only a slight overhead, but quite nice overall savings in the bundles (even when served with compression)

However, just ran into an issue logged by one of my users and don't quite know a way forward to improve matters and solve this issue as it stands.

polkadot-js/api#2963

So this (and it may also exist in mixed Node/browser frameworks as next.js - untested) basically means that the bundle serves from a node environment (which ignores the browser field in package.json), but executes in another environment, which actually does need that override and/or it doesn't exist. (Not quite sure which option comes into play here, really not quite familiar with fastlify)

I actually only ever use unzlibSync (See https://github.com/polkadot-js/wasm/blob/master/packages/wasm-crypto-wasm/src/data.ts#L8), so in a perfect world would have avoided anything worker-related completely. However, in this case do get tripped up.

Any suggestions to work around this and or solve this? Preferable on this level?

Would really like to solve the linked issue, but don't quite see any options - I guess if the parts were split into their own files (or even just sync/async), could have possibly done a direct import, avoiding workers completely. (Well, then we have tree-shaking issues since atm an export map is not required).

Facing an issue when fflate.min.js is being bundled

Background
I'm maintaining Threebox, and I updated the repo to Three.js r127 which now uses fflate in many different loaders.

The problem
The problem is that I'm finding issues to bundle fflate.min.js through different bundling tools, including browserify, webpack and vue-cli-service.

How to reproduce
Using the file you provided to Three.js fflate.min.js, referenced from [FBXLoader] that is also bundled in my plugin, and then bundlef with broserify or webpack (I have faced issues also with vue, but there could be ther more dependencies...) I get the following error every time I execute the bundle commands:

browseryfy:

> browserify -p tinyify exports.js > dist/threebox.min.js 
Error: Can't walk dependency graph: ENOENT: no such file or directory, lstat 'C:\.....\threebox-new\worker_threads' required by C:\.....\threebox-new\src\objects\fflate.min.js[threebox-new] run build successfully completed

Webpack:

WARNING in ./node_modules/threebox-plugin/src/objects/fflate.min.js 6:538-570
Module not found: Error: Can't resolve 'worker_threads' in 'C:\....\node_modules\threebox-plugin\src\objects'
resolve 'worker_threads' in 'C:\....\node_modules\threebox-plugin\src\objects'
  Parsed request is a module
  using description file: C:\....\node_modules\threebox-plugin\package.json (relative path: ./src/objects)
    Field 'browser' doesn't contain a valid alias configuration
    resolve as module
      looking for modules in C:/..../node_modules
        single file module
          using description file: C:\....\package.json (relative path: ./node_modules/worker_threads)
            no extension
              Field 'browser' doesn't contain a valid alias configuration
              C:\....\node_modules\worker_threads doesn't exist
            .js
              Field 'browser' doesn't contain a valid alias configuration
              C:\....\node_modules\worker_threads.js doesn't exist
            .json
              Field 'browser' doesn't contain a valid alias configuration
              C:\....\node_modules\worker_threads.json doesn't exist
            .wasm
              Field 'browser' doesn't contain a valid alias configuration
              C:\....\node_modules\worker_threads.wasm doesn't exist
        C:\....\node_modules\worker_threads doesn't exist
ModuleNotFoundError: Module not found: Error: Can't resolve 'worker_threads' in 'C:\....\node_modules\threebox-plugin\src\objects'
    at C:\....\node_modules\webpack\lib\Compilation.js:1668:28
    at C:\....\node_modules\webpack\lib\NormalModuleFactory.js:712:13
    at eval (eval at create (C:\....\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:10:1)
    at C:\....\node_modules\webpack\lib\NormalModuleFactory.js:273:22
    at eval (eval at create (C:\....\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:9:1)
    at C:\....\node_modules\webpack\lib\NormalModuleFactory.js:402:22
    at C:\....\node_modules\webpack\lib\NormalModuleFactory.js:117:11
    at C:\....\node_modules\webpack\lib\NormalModuleFactory.js:628:24
    at C:\....\node_modules\webpack\lib\NormalModuleFactory.js:782:8
    at C:\....\node_modules\webpack\lib\NormalModuleFactory.js:902:5
 @ ./node_modules/threebox-plugin/src/objects/loaders/FBXLoader.js 2:15-42

Alternatives
Some bundlers accept the option to ignore a reference to a module, but not all of them. I was guessing if you find an easy way to avoid this reference or make it conditional in the code. Any hint will be also greatly appreciated.

Could not resolve import "worker_threads" when trying to use in web

Hi everyone,
I am trying to use the library as an ESM module like that:
import * as fflate from 'fflate';
But I am getting Error: Could not resolve import "worker_threads" in "../../../node_modules/fflate/esm/node-worker.js".
In esm/index.js I see the following line import wk from './node-worker'; and if I change it to import wk from './worker'; it works fine.

As I understand it tries to run node version worker in web for some reason. Does it miss some sort of condition to import proper worker?

I really need this so I am ready to work out a PR for that.

Async Gzip throws error saying to use Node 12+ but already running Node 14+

How to reproduce

The problem

Hi I'm trying to gzip some files in a .then() promise chain so I tried using gzipAsync() but received the following error:

Error: async operations unsupported - update to Node 12+ (or Node 10-11 with the --experimental-worker CLI flag)
    at Immediate.<anonymous> (file:///home/today/_/work/haptic/node_modules/fflate/esm/index.mjs:34:42)
    at processImmediate (internal/timers.js:456:21)

I'm running Node v14.2.0. The file is ESM via "type": "module" in package.json. The file imports fflate like this:

import esbuild from 'esbuild';
import { gzip, gzipSync } from 'fflate';
import { readFile, writeFile } from 'fs/promises';
// ...

Here's the code that throws on L47:

image

If I replace L47 with res(gzipSync(readData, { consume: true, level: 9 })); then it's fine. It's also fast enough that I'm not reporting an issue with Sync vs Async, just that the error message might be wrong if it's telling people to update to 12+.

Thanks!

ERR_REQUIRE_ESM v5.x

yarn v1.22.10; node v14.15.0; webpack v5.13.0

Hello, I did not have a problem with previous versions of the fflate v4.x but starting from v5.0:

// client side (browser) webworker
import { gunzipSync } from 'fflate';
...
webpack 5.13.0 compiled successfully in 10038 ms
internal/process/esm_loader.js:74
internalBinding('errors').triggerUncaughtException(
^
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /../node_modules/fflate/esm/index.mjs
at Module.load (internal/modules/cjs/loader.js:926:11)
at Function.Module._load (internal/modules/cjs/loader.js:769:14)
at Module.require (internal/modules/cjs/loader.js:952:19)
at require (internal/modules/cjs/helpers.js:88:18)
at Object.fflate (/../build/bundle.js:1069:18)
at webpack_require (/../build/bundle.js:1332:41)
at eval (webpack://mpd-eds/./src/client/components/decompressFileContent.js?:17:64)
at Object../src/client/components/decompressFileContent.js (/../build/bundle.js:599:1)
at webpack_require (/../build/bundle.js:1332:41)
at eval (webpack://mpd-eds/./src/client/actions/initState.js?:57:91) {
code: 'ERR_REQUIRE_ESM'
}
[nodemon] app crashed - waiting for file changes before starting...
...

Push chunk result not good

How to reproduce

<script src="https://unpkg.com/fflate"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/pako.js"></script>
<script>
    let massiveFileBuf;
    fetch('/f.html').then(
        res => {
            res.arrayBuffer().then(e => {
                massiveFileBuf = new Uint8Array(e);
            })
        });
    let resLen = 0
    let startfflate = () => {
        let ins = new fflate.Deflate({
            level: 6
        });
        ins.ondata = function (data, final) {
            resLen += data.length
        }
        ins.push(massiveFileBuf.slice(0), true);
        console.log("full Deflate length",resLen);
        resLen = 0;

        let ins2 = new fflate.Deflate({
            level: 6
        });
        ins2.ondata = function (data, final) {
            resLen += data.length
        }
        let offset = 0;
        let len = massiveFileBuf.length;
        while (offset < len) {
            ins2.push(massiveFileBuf.slice(offset, offset + 16), false);
            offset += 16;
        }
        ins2.push(new Uint8Array(),true);
        console.log("stream Deflate length",resLen);
    }
    let startpako = () => {
        let ins = new pako.Deflate({
            level: 6
        });
        ins.onData = function (data) {
            resLen += data.length
        }
        ins.push(massiveFileBuf.slice(0), true);
        console.log("full Deflate length",resLen);
        resLen = 0;

        let ins2 = new pako.Deflate({
            level: 6
        });
        ins2.onData = function (data) {
            resLen += data.length
        }
        let offset = 0;
        let len = massiveFileBuf.length;
        while (offset < len) {
            ins2.push(massiveFileBuf.slice(offset, offset + 16), false);
            offset += 16;
        }
        ins2.push(new Uint8Array(),true);
        console.log("stream Deflate length",resLen);
    }
</script>

The problem
As I tested, the console shows:

startfflate()
b.html:20 full Deflate length 755300
b.html:36 stream Deflate length 10362697

startpako()
b.html:46 full Deflate length 739044
b.html:62 stream Deflate length 739044

The fflate deflate stream result too big and even bigger than the origin data length which is 7832702.
So I wonder if the Deflate stream push function has some logic error.

Add "extra fields" support

Would be awesome to add support for reading&writing the "extra fields" from the specifications: https://datatracker.ietf.org/doc/html/rfc1952#page-8

 2.2. File format

      A gzip file consists of a series of "members" (compressed data
      sets).  The format of each member is specified in the following
      section.  The members simply appear one after another in the file,
      with no additional information before, between, or after them.

   2.3. Member format

      Each member has the following structure:

         +---+---+---+---+---+---+---+---+---+---+
         |ID1|ID2|CM |FLG|     MTIME     |XFL|OS | (more-->)
         +---+---+---+---+---+---+---+---+---+---+

      (if FLG.FEXTRA set)

         +---+---+=================================+
         | XLEN  |...XLEN bytes of "extra field"...| (more-->)
         +---+---+=================================+

      (if FLG.FNAME set)

         +=========================================+
         |...original file name, zero-terminated...| (more-->)
         +=========================================+

      (if FLG.FCOMMENT set)

         +===================================+
         |...file comment, zero-terminated...| (more-->)
         +===================================+

      (if FLG.FHCRC set)

         +---+---+
         | CRC16 |
         +---+---+

         +=======================+
         |...compressed blocks...| (more-->)
         +=======================+

           0   1   2   3   4   5   6   7
         +---+---+---+---+---+---+---+---+
         |     CRC32     |     ISIZE     |
         +---+---+---+---+---+---+---+---+

2.3.1.1. Extra field

         If the FLG.FEXTRA bit is set, an "extra field" is present in
         the header, with total length XLEN bytes.  It consists of a
         series of subfields, each of the form:

            +---+---+---+---+==================================+
            |SI1|SI2|  LEN  |... LEN bytes of subfield data ...|
            +---+---+---+---+==================================+

         SI1 and SI2 provide a subfield ID, typically two ASCII letters
         with some mnemonic value.  Jean-Loup Gailly
         <[email protected]> is maintaining a registry of subfield
         IDs; please send him any subfield ID you wish to use.  Subfield
         IDs with SI2 = 0 are reserved for future use.  The following
         IDs are currently defined:

Thanks in advance!

Feature request: flush (Z_SYNC_FLUSH) during streaming deflate

What can't you do right now?
It would be helpful to be able to do a partial (sync) flush during a streaming deflate, such that all the data passed to the deflater so far can be fully inflated at the other end, without ending the stream or fully resetting the compression state. This will enable implementation of packet-level compression in a binary stream protocol.

Example code

const deflate = new fflate.Zlib();
deflate.ondata = (data) => writeDataToStream(data);

deflate.push(packet1);
deflate.flush();  // <-- proposed new API
// At this point the flushed data is written to the stream,
// and the other side can fully decompress all packets sent so far.

// Later, more data can be sent, continuing the compressed stream
// without resetting the compression state.
deflate.push(packet2);
deflate.flush();

// Meanwhile the inflater is on the other side of a stream connection,
// receiving a stream of compressed data from the deflater.
const inflate = new fflate.Inflate();
inflate.ondata = handleReceivedPacket(data);

let receivedData;
while (!!(receivedData = getNextChunkFromStream())) {
  deflate.push(receivedData);
  // The `ondata` callback should be invoked once reaching each flush point
  // (or earlier if there was a lot of data).
}

(How) is this done by other libraries?

The Node.js zlib API supports this in two ways:

  1. Set flush: zlib.constants.Z_SYNC_FLUSH in options for zlib.createDeflate(). Then it will auto-flush after every write.
  2. Or, explicitly call flush(zlib.constants.Z_SYNC_FLUSH) whenever desired after writing some data to the deflate stream.

Pako doesn't support this functionality as far as I can see. I don't know about other libraries.

Could I add a zip aes (en/de)crypt module ?

What can't you do right now?

Aes encrypt for zip.

An optimal solution
Use sjcl to do it.

Could I add a zip aes (en/de)crypt module?
And if I could add that module, could you give me some suggestions?

Throw Error objects instead of strings

The problem

I noticed that there are several places where you throw a string. It's considered best practice to only throw Error objects. When listening to an ondata event, it's really unexpected to have a variable called err that is a string instead of an Error object.

Per file compression options in ZIP don't work as described

In the demo:

    // With fflate, we can choose which files we want to compress
    zipObj[file.name] = [buf, {
      level: ALREADY_COMPRESSED.indexOf(ext) == -1 ? 6 : 0
    }];

Changing 6 to 1 or to any other number except 0 doesn't affect the execution time or file size.
You have to change it here:

 if (!--left) {
      fflate.zip(zipObj, {
        // If you want to control options for every file, you can do so here
        // They are merged with the per-file options (if they exist)
        // mem: 9
        level: 1
      }, function(err, out) {

I see in the code, that you do merge them like it says in the comment, but in the following line instead of using the merged result you pass the default options:

} else term.push(deflate(file, opts as AsyncZipOptions, cbl));

No cap on web-workers created

When calling async functions (e.g. inflate), a new web-worker is created.

This is problematic when calling it thousands of times, as it'll easily overload the browser with worker instantiations.

When the file-sizes are small it's quite problematic because the 70ms worker instantiation takes longer than the decompression.

The solution that comes to mind would be a worker-pool (whether in-library or by the user).

It may be worth noting this in the documentation for future readers.

ESM import no longer works in 0.5.3

Not sure if this was intended, but I've been doing:

import * as fflate from 'https://unpkg.com/fflate/esm/index.mjs';

As of 0.5.3, this fails, because the following code at the top of index.mjs:

try {
    Worker = /*#__PURE__*/ require('worker_threads').Worker;
}
catch (e) {
}

has moved to node-worker.js and been replaced with:

import wk from './node-worker.js';

which doesn't work in a browser because node-worker.js doesn't define a default export, or any exports at all.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.