brocha
Version:
Universal Brotli decompressor written in 100% TypeScript with zero dependencies. Blazing fast, lightweight, and compatible with any ES2015+ environment.
415 lines (289 loc) • 14.6 kB
Markdown
<div align="center">
![][badge-jsr-score] ![][badge-jsr-pkg] ![][badge-npm]
</div>
---
This package provides a blazing fast TypeScript implementation of the [Brotli]
decompression algorithm, suitable for use in any ES2015+ environment. It offers
a performant, portable, and reliable alternative to existing solutions, with
support for custom dictionaries and a small footprint.
[](
decoders, and significantly faster than JavaScript-only alternatives.
<picture align="left" width="32" height="48">
<source media="(prefers-color-scheme: dark)" srcset="https://api.iconify.design/simple-icons:deno.svg?height=2.75rem&width=3rem&color=%23fff" />
<img align="left" src="https://api.iconify.design/simple-icons:deno.svg?height=2.75rem&width=3rem" alt="Deno" width="32" height="48" />
</picture>
```sh
deno add npm:brocha
```
<img align="left" src="https://api.iconify.design/logos:npm.svg?height=2rem&width=2rem&inline=true" alt="NPM" width="32" height="48" />
```sh
npm i brocha
```
<details><summary><b><u>Additional NPM install methods</u></b> ⤵︎</summary><br>
<img align="right" src="https://api.iconify.design/logos:bun.svg" alt="Bun" width="32" height="48" />
```sh
bun add brocha
```
<img align="right" src="https://api.iconify.design/devicon:pnpm.svg?height=2.5rem&width=2.5rem&inline=true" alt="PNPM" width="32" height="48" />
```sh
pnpm add brocha
```
<img align="right" src="https://api.iconify.design/logos:yarn.svg?height=2rem&width=2rem&inline=true" alt="Yarn" width="32" height="48" />
```sh
yarn add brocha
```
</details>
---
This package is dual-published to both [npm] and [JSR], and can be installed
using any of your favorite package managers or CDNs. The JSR distribution is
available at [`@nick/brocha`][JSR].
<picture align="left" width="32" height="48">
<source media="(prefers-color-scheme: dark)" srcset="https://api.iconify.design/simple-icons:deno.svg?height=2.75rem&width=3rem&color=%23fff" />
<img align="left" src="https://api.iconify.design/simple-icons:deno.svg?height=2.75rem&width=3rem" alt="Deno" width="32" height="48" />
</picture>
```sh
deno add jsr:@nick/brocha
```
<img align="left" src="https://api.iconify.design/simple-icons:jsr.svg?color=%23fb0" alt="JSR" width="32" height="48" />
```sh
npx jsr add @nick/brocha
```
<details><summary><b><u>Additional JSR install methods</u></b> ⤵︎</summary><br>
<img align="right" src="https://api.iconify.design/logos:bun.svg" alt="Bun" width="32" height="48" />
```sh
bunx jsr add @nick/brocha
```
<img align="right" src="https://api.iconify.design/devicon:pnpm.svg?height=2.5rem&width=2.5rem&inline=true" alt="PNPM" width="32" height="48" />
```sh
pnpm dlx jsr add @nick/brocha
```
<img align="right" src="https://api.iconify.design/logos:yarn.svg?height=2rem&width=2rem&inline=true" alt="Yarn" width="32" height="48" />
```sh
yarn dlx jsr add @nick/brocha
```
</details>
---
Thanks to the widespread CDN support for NPM-distributed packages, `brocha` is
available to use right in the browser! Simply import it from your favorite CDN:
```ts
import { decompress } from "https://esm.sh/brocha";
```
<details><summary><b><u>Additional CDN import methods</u></b> ⤵︎</summary><br>
```ts
import { decompress } from "https://cdn.skypack.dev/brocha";
```
```ts
import { decompress } from "https://unpkg.com/brocha";
```
```ts
import { decompress } from "https://cdn.jsdelivr.net/npm/brocha";
```
</details>
---
```ts
import { decompress } from "@nick/brocha";
const response = await fetch("file:///compressed.br");
const compressedData = new Uint8Array(await response.arrayBuffer());
const decompressedData = decompress(compressedData);
console.log(
`Decompressed ${compressedData.length}B -> ${decompressedData.length}B`,
);
```
> [!TIP]
>
> The `decompress` function is ready to use immediately upon import. Simply pass
> in your Brotli-compressed data and receive the decompressed `Uint8Array`.
---
Decompresses a [`BufferSource`] object containing Brotli-encoded data, returning
a new `Uint8Array` instance with the decompressed data.
```ts ignore
decompress(input: BufferSource, options?: BrotliDecodeOptions): Uint8Array;
```
- `input`: The Brotli-compressed [`BufferSource`] to be decoded.
- `options`: Optional decompression options, allowing you to use a custom
dictionary.
- `Uint8Array`: The decompressed data.
> [!NOTE]
>
> The [`decompress`](
> requires no additional setup beyond importing it from the `brocha` package.
```ts no-eval
import { decompress } from "@nick/brocha";
import * as fs from "node:fs";
const compressed = fs.readFileSync("data.br");
const decompressed = decompress(compressed);
const inputKB = (compressed.byteLength / 1024).toFixed(0);
const outputKB = (decompressed.byteLength / 1024).toFixed(0);
console.log(
`${inputKB}K → ${outputKB}K (+${(outputKB / inputKB).toFixed(2)}x)`,
);
// Example log: "115K → 483K (+4.20x)"
```
```ts no-eval
import { decompress } from "@nick/brocha";
const compressedData = /* Brotli-compressed data */;
const dictionary = new Uint8Array([/* custom dictionary bytes */]);
const options = { customDictionary: dictionary };
const decompressedData = decompress(compressedData, options);
console.log(decompressedData);
```
> [!WARNING]
>
> Custom dictionaries are an advanced feature, and must be used with care. If an
> invalid dictionary is provided, or if it does not match the one that was used
> to compress the data you are trying to decompress, the decompression process
> will either fail with an error or return corrupted data.
---
Options to customize the behavior of the Brotli decompression process.
```ts
interface BrotliDecodeOptions {
customDictionary?: BufferSource | null;
}
```
Custom dictionary to use for the Brotli decompression.
`null` _(uses the default dictionary)_
The dictionary **must** be a valid Brotli dictionary that **exactly** matches
the one used when the input data was compressed. Otherwise, the decompressor
will either throw an exception or corrupted data will be returned.
---
This package is designed to be lightweight and fast, with a focus on performance
and efficiency. The following benchmarks were run on a 2021 MacBook Pro with an
M1 Pro chip using Deno 2.1.2.
The results demonstrate the performance of this package compared to other
popular Brotli decompression tools, suggesting it is a viable alternative to
existing [WebAssembly-based solutions](
- Performance is mostly on par with WebAssembly decoders like [brotli-wasm],
which typically show small speed advantages of ~15-20% over this package.
- Compared to other pure-JS implementations (specifically [npm:brotli]),
`brocha` consistently clocks speeds ~1.75x faster across all benchmarks.
- Performance is about 35-50% that of the native Node.js `node:zlib` module.
> [!NOTE]
>
> The native `node:zlib` module is written in C++ and is highly optimized for
> performance. While _"~2.5x slower"_ sounds like a poor result, it's actually
> quite fast for a pure JavaScript implementation, which will never be able to
> match the performance of a native module.
```sh
> deno bench -A --no-check
benchmark time/iter (avg) iter/s (min … max) p75 p99 p995
--------------------- ----------------------------- --------------------- --------------------------
group basic json (6.5 KB -> 27.5 KB)
jsr:@nick/brocha 399.0 µs 2,506 (324.8 µs … 3.1 ms) 381.8 µs 1.6 ms 2.1 ms
npm:brotli 666.1 µs 1,501 (553.7 µs … 3.0 ms) 626.2 µs 1.9 ms 2.3 ms
npm:brotli-wasm 322.5 µs 3,101 (265.4 µs … 7.0 ms) 276.6 µs 1.7 ms 2.5 ms
npm:brotli-dec-wasm 306.7 µs 3,261 (279.2 µs … 4.7 ms) 288.3 µs 915.1 µs 1.6 ms
node:zlib 144.5 µs 6,921 (136.5 µs … 1.8 ms) 141.8 µs 194.1 µs 245.7 µs
summary
jsr:@nick/brocha
2.76x slower than node:zlib
1.30x slower than npm:brotli-dec-wasm
1.24x slower than npm:brotli-wasm
1.67x faster than npm:brotli
group dprint-plugin-graphql.wasm (147 KB -> 768 KB)
jsr:@nick/brocha 6.5 ms 154.0 ( 6.0 ms … 7.7 ms) 6.7 ms 7.7 ms 7.7 ms
npm:brotli 12.2 ms 82.1 ( 10.6 ms … 57.6 ms) 11.3 ms 57.6 ms 57.6 ms
npm:brotli-wasm 6.5 ms 153.3 ( 5.9 ms … 31.6 ms) 6.1 ms 31.6 ms 31.6 ms
npm:brotli-dec-wasm 6.3 ms 158.8 ( 6.2 ms … 7.1 ms) 6.3 ms 7.1 ms 7.1 ms
node:zlib 3.4 ms 292.2 ( 3.2 ms … 4.6 ms) 3.5 ms 4.2 ms 4.6 ms
summary
jsr:@nick/brocha
1.90x slower than node:zlib
1.03x slower than npm:brotli-dec-wasm
1.00x faster than npm:brotli-wasm
1.88x faster than npm:brotli
group dprint-plugin-jupyter.wasm (354 KB -> 1.68 MB)
jsr:@nick/brocha 14.4 ms 69.5 ( 13.9 ms … 15.9 ms) 14.5 ms 15.9 ms 15.9 ms
npm:brotli 24.6 ms 40.6 ( 23.9 ms … 26.4 ms) 24.8 ms 26.4 ms 26.4 ms
npm:brotli-wasm 13.9 ms 71.8 ( 13.6 ms … 15.0 ms) 14.1 ms 15.0 ms 15.0 ms
npm:brotli-dec-wasm 14.9 ms 67.3 ( 14.5 ms … 15.4 ms) 15.0 ms 15.4 ms 15.4 ms
node:zlib 8.4 ms 119.5 ( 8.0 ms … 9.3 ms) 8.4 ms 9.3 ms 9.3 ms
summary
jsr:@nick/brocha
1.72x slower than node:zlib
1.03x slower than npm:brotli-wasm
1.03x faster than npm:brotli-dec-wasm
1.71x faster than npm:brotli
group dprint-plugin-typescript.wasm (746 KB -> 4.01 MB)
jsr:@nick/brocha 37.5 ms 26.7 ( 32.8 ms … 42.2 ms) 38.6 ms 42.2 ms 42.2 ms
npm:brotli 60.5 ms 16.5 ( 55.6 ms … 73.5 ms) 62.9 ms 73.5 ms 73.5 ms
npm:brotli-wasm 31.9 ms 31.4 ( 30.8 ms … 33.1 ms) 32.6 ms 33.1 ms 33.1 ms
npm:brotli-dec-wasm 33.8 ms 29.6 ( 32.9 ms … 35.0 ms) 34.3 ms 35.0 ms 35.0 ms
node:zlib 18.1 ms 55.3 ( 17.4 ms … 20.2 ms) 18.5 ms 20.2 ms 20.2 ms
summary
jsr:@nick/brocha
2.07x slower than node:zlib
1.18x slower than npm:brotli-wasm
1.11x slower than npm:brotli-dec-wasm
1.61x faster than npm:brotli
group ten megs of lipsum (1.71 MB -> 9.77 MB)
jsr:@nick/brocha 64.1 ms 15.6 ( 60.1 ms … 69.1 ms) 66.9 ms 69.1 ms 69.1 ms
npm:brotli 109.6 ms 9.1 (104.6 ms … 115.2 ms) 114.4 ms 115.2 ms 115.2 ms
npm:brotli-wasm 48.5 ms 20.6 ( 46.8 ms … 51.2 ms) 49.8 ms 51.2 ms 51.2 ms
npm:brotli-dec-wasm 52.3 ms 19.1 ( 49.9 ms … 54.5 ms) 53.5 ms 54.5 ms 54.5 ms
node:zlib 28.0 ms 35.8 ( 25.4 ms … 30.2 ms) 28.8 ms 30.2 ms 30.2 ms
summary
jsr:@nick/brocha
2.29x slower than node:zlib
1.32x slower than npm:brotli-wasm
1.23x slower than npm:brotli-dec-wasm
1.71x faster than npm:brotli
```
> The `decompress` function from this package is the baseline, with some other
> popular Brotli decompression tools for comparison.
---
### Prior Art
This project was adapted from the original [brotli] source code, developed by
the Google Brotli team and licensed under the MIT license.
#### WebAssembly Decoders
- [brotli-decompressor]
- [brotli-dec-wasm]
- [brotli-wasm]
#### JavaScript Decoders
- [npm:brotli]
---
<div align="center">
<br>
[![JSR][JSR-badge]][JSR] [![JSR][badge-jsr-score]][JSR]
</div>
[]: https://nick.mit-license.org "MIT © 2024+ Nicholas Berlette. All rights reserved."
[]: https://github.com/nberlette "Nicholas Berlette on GitHub"
[]: https://github.com/nberlette/brocha "View the @nick/brocha project on GitHub"
[]: https://github.com/nberlette/brocha/issues "View issues for the @nick/brocha project on GitHub"
[]: https://jsr.io/@nick/brocha/doc "View the @nick/brocha documentation on jsr.io"
[]: https://www.npmjs.com/package/brocha "View the brocha package on npm"
[]: https://crates.io/crates/brotli-decompressor "View the brotli-decompressor crate on crates.io"
[]: https://crates.io/brotli-wasm "View the brotli-wasm crate on crates.io"
[]: https://crates.io/brotli-dec-wasm "View the brotli-dec-wasm crate on crates.io"
[]: https://jsr.io/badges/@nick "Install @nick/brocha with JSR"
[]: https://img.shields.io/npm/v/brocha?logo=npm&color=firebrick&labelColor=firebrick&label= "View brocha on npm"
[]: https://jsr.io/badges/@nick "View all of @nick's packages on jsr.io"
[]: https://jsr.io/badges/@nick/brocha "View @nick/brocha on jsr.io"
[]: https://jsr.io/badges/@nick/brocha/score "View the score for @nick/brocha on jsr.io"
[]: https://github.com/google/brotli "View the Brotli project on GitHub"
[]: https://www.npmjs.com/package/brotli "View the brotli package on npm"
[]: https://www.npmjs.com/package/brocha "View the brocha package on npm"
[`BufferSource`]: https://developer.mozilla.org/en-US/docs/Web/API/BufferSource "MDN Web Docs: BufferSource"