Skip to content

ustclug-dev/brotli-dec-wasm

Repository files navigation

brotli-dec-wasm

npm npm unpacked size

Brotli decompressor for browsers and web workers with WASM, in about 200KB.

For compressors, see Alternatives.

Features

  • Optimized for size, suitable for browsers and web workers.
  • Streaming decompression.
  • Drop-in replacement for brotli-wasm.

Get Started

Install the package:

npm install brotli-dec-wasm

The default export is a promise that resolves to the WASM module:

import brotliPromise from "brotli-dec-wasm";

const brotli = await brotliPromise;
const compressedData = new Uint8Array(/* ... */);
const decompressed = brotli.decompress(compressedData);

Streaming decompression

Use DecompressStream to decompress data in chunks. Call decompress(input, outputSize) in a loop, handling three result codes:

  • NeedsMoreOutput: the current input chunk produced more output than outputSize bytes. Call again with the remaining input (sliced by input_offset).
  • NeedsMoreInput: the current input chunk is fully consumed. Feed the next chunk.
  • ResultSuccess: decompression is complete.
import brotliPromise from "brotli-dec-wasm";

const brotli = await brotliPromise;
const stream = new brotli.DecompressStream();
const chunks = [];

const compressedChunks = [new Uint8Array(/* ... */) /* ... */];
for (const chunk of compressedChunks) {
  let resultCode;
  let inputOffset = 0;
  do {
    const input = chunk.slice(inputOffset);
    const result = stream.decompress(input, 1024);
    chunks.push(result.buf);
    resultCode = result.code;
    inputOffset += result.input_offset;
  } while (resultCode === brotli.BrotliStreamResultCode.NeedsMoreOutput);
}

Using with TransformStream

DecompressStream works with the browser TransformStream API for piped workflows:

import brotliPromise from "brotli-dec-wasm";

const brotli = await brotliPromise;
const decompressStream = new brotli.DecompressStream();

const decompressionStream = new TransformStream({
  transform(chunk, controller) {
    let resultCode;
    let inputOffset = 0;
    do {
      const input = chunk.slice(inputOffset);
      const result = decompressStream.decompress(input, 1024);
      controller.enqueue(result.buf);
      resultCode = result.code;
      inputOffset += result.input_offset;
    } while (resultCode === brotli.BrotliStreamResultCode.NeedsMoreOutput);
    if (
      resultCode !== brotli.BrotliStreamResultCode.NeedsMoreInput &&
      resultCode !== brotli.BrotliStreamResultCode.ResultSuccess
    ) {
      controller.error(`Brotli decompression failed with code ${resultCode}`);
    }
  },
});

await compressedReadableStream.pipeThrough(decompressionStream).pipeTo(outputWritableStream);

Using with a Web Worker

Decompression runs synchronously on the calling thread. For large payloads this can freeze the UI. To avoid that, run the decompression in a Web Worker:

// worker.js
import brotliPromise from "brotli-dec-wasm";

const brotli = await brotliPromise;

self.onmessage = (e) => {
  try {
    const result = brotli.decompress(new Uint8Array(e.data));
    self.postMessage(result, [result.buffer]);
  } catch (err) {
    self.postMessage({ error: err.message });
  }
};
// main.js
const worker = new Worker(new URL("./worker.js", import.meta.url), { type: "module" });

function decompress(data) {
  return new Promise((resolve, reject) => {
    worker.onmessage = (e) => {
      if (e.data.error) reject(new Error(e.data.error));
      else resolve(e.data);
    };
    const buf = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
    worker.postMessage(buf, [buf]);
  });
}

const result = await decompress(compressedData);

Using the WASM module directly

The JS API generated by wasm-pack and the WASM binary file are also exported for cases where you need to instantiate the WASM module with a URL:

import init from "brotli-dec-wasm/web";
import wasmUrl from "brotli-dec-wasm/web/bg.wasm";

Check *.asset.* files in example/webpack for examples.

Webpack 5

Set experiments.asyncWebAssembly: true (or experiments.syncWebAssembly: true for legacy code) in your webpack config. Without this, webpack 5 will not enable WebAssembly support.

Benchmark

Decompressing 1MB data in headless Chromium (vitest + playwright):

Input ops/s mean
1MB random data (incompressible, ~1MB compressed) ~480 ~2.1ms
1MB repeated data (highly compressible, 41 bytes compressed) ~440 ~2.3ms

Environment: AMD Ryzen 7 5800H, performance governor, Linux 6.19.6.

Run pnpm run bench to reproduce. Fixtures are generated by node test/gen_fixture.js.

Alternatives

  • brotli-wasm: A compressor and decompressor for Brotli, supporting Node and browsers via WASM. If you need a compressor, use it.

Security

Use version >= 1.3.3. Earlier versions depend on the unmaintained Rust crate wee_alloc, which has open serious issues.

License

Copyright (C) 2026 Yulong Ming i@myl7.org.

Apache License, Version 2.0 or MIT License, at your option.

About

Brotli decompressor for browsers and web workers with WASM, in about 200KB

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-Apache.txt
MIT
LICENSE-MIT.txt

Stars

Watchers

Forks

Packages

 
 
 

Contributors