天天看點

比Gzip更強的 Brotli 支援 WebAssembly 了!

作者:進階前端進階

大家好,很高興又見面了,我是"進階前端‬進階‬",由我帶着大家一起關注前端前沿、深入前端底層技術,大家一起進步,也歡迎大家關注、點贊、收藏、轉發,您的支援是我不斷創作的動力。

比Gzip更強的 Brotli 支援 WebAssembly 了!

大家都知道,如果沒有 JavaScript 膠水代碼,目前無法在浏覽器中将 WebAssembly 用于 Web 應用程式。 但是,開發者已經可以使用 WebAssembly 在浏覽器中執行諸多操作,而這些操作在過去很長一段時間隻能使用 JavaScript 執行。

本文主要和大家讨論 Brotli,即 Google 推出的一種無損壓縮算法,通過變種的 LZ77 算法、Huffman 編碼等方式進行資料壓縮。在年初,我也确實使用 WebAssembly 将用戶端應用成功移植到了 Web,這也是為什麼我一直對 WebAssembly 充滿好奇的原因。我甚至在頭條上開了一個合集《WebAssembly 前沿技術》來專門探讨 WebAssembly ,并将持續關注 WebAssembly 的最新動态。

下面是已釋出部分文章傳送門:

  • 《 進階語言全面擁抱WebAssembly ?JavaScript瑟瑟發抖?》
  • 《 2023 年讓 WebAssembly 大火的 10+應用!》
  • 《 萬字長文!2023 年 WebAssembly 各個運作時性能對比!》
  • 《 讓 JavaScript 在 WebAssembly 上加速運作!》
  • 《全網最火的5+優秀 WebAssembly 運作時!》
  • 《 線上表格再添一員猛将excelize,支援 wasm! 》

正如大家所看見,當大多數開發者還在遲疑是否要在日常開發中引入 WebAssembly 的時候,很多優秀的應用、工具已經開始吃 WebAssembly 的紅利了,而且取得了不錯的成就,這可能也是為什麼各個浏覽器廠商、開發者如此熱衷 WebAssembly 的原因吧。

話不多說,直接進入正題!

1.Brotli 壓縮算法介紹

1.1 什麼是 Brotli

速度對于任何網站都至關重要,在 Web 追求快速加載時間的過程中,有許多不同的技術可以幫助開發者。 一種方法是最大限度地減少網站使用的底層代碼,而不影響其功能。 另一種方式是依賴不同的壓縮技術,比如常見的 GZIP,但 Brotli 壓縮是另一種值得關注的新興方法。

比Gzip更強的 Brotli 支援 WebAssembly 了!

Brotli(發音 [b'rɒdi:]) 是 Google 推出的一種無損壓縮算法,通過變種的 LZ77 算法、Huffman 編碼等方式進行資料壓縮,同時壓縮資料格式規範在 RFC 7932 中定義。與其他壓縮算法相比(如 zip,gzip 等),無論是壓縮時間,還是壓縮體積上看,Brotli 都有着更高的效率。

總之,開啟 Brotli 壓縮功能後,CDN 節點會對資源進行智能壓縮後傳回,縮小傳輸檔案大小,提升檔案傳輸效率,減少帶寬消耗。

目前 Brotli 支援壓縮的檔案類型有: text/xml、text/plain、text/css、application/javascript、application/x-javascript、application/rss+xml、text/javascript、image/tiff、image/svg+xml、application/json、application/xml,幾乎涵蓋了 Web 開發資源的方方面面。

1.2 Brotli 與其他壓縮算法資料化比較

壓縮率表示壓縮算法可以減少多少檔案體積,計算公式如下:

壓縮比 =未壓縮大小/壓縮大小

是以數字越高,算法壓縮能力就越好。接下來,首先看一下 Web 開發中出現的典型檔案格式,即 CSS。 對于特定示例,下圖可以看到 Brotli 壓縮比在最佳上更好,在最快上大緻相同:

比Gzip更強的 Brotli 支援 WebAssembly 了!

當然,還可以通過擴充該集合以包含一些 HTML 和 JavaScript 示例來看到這種模式并不是該示例所獨有的:

比Gzip更強的 Brotli 支援 WebAssembly 了!

前三列顯示“最快”的尺寸減小,接下來的三列顯示“最佳”,最後一列顯示 Brotli 的中等品質水準。 正如上圖中看到的,即使在中等品質級别,Brotli 壓縮率也高于 gzip 和 Deflate 的最佳品質級别。

為了評估 Brotli 是否還可以在 Web 檔案領域之外提供較好的壓縮比,一起來看看 Canterbury Corpus,這是用于測試壓縮算法的流行檔案集。從資料來看,很明顯,Brotli 特别适合較大的檔案,但即使對于較小的檔案,Brotli 也與 Deflate 和 gzip 相當甚至稍好。

比Gzip更強的 Brotli 支援 WebAssembly 了!

更多關于 Brotli 壓縮算法的對比可以參考文末的資料,本文不再過多展開。

2.什麼是 brotli-wasm

brotli-wasm 是 Brotli 的可靠壓縮和解壓縮器,通過 wasm 支援 Node.js 和浏覽器環境。Brotli 在 Node 12+以上版本上可用,但在較舊的 Node 或浏覽器中不可用。 有了 brotli-wasm,開發者就可以在任何地方使用brotli算法。

比Gzip更強的 Brotli 支援 WebAssembly 了!

brotli-wasm 包含一個圍繞 Rust Brotli crate 的壓縮和解壓縮 API 的小包裝器,編譯為 wasm,隻需一些設定即可輕松地在 JavaScript 中使用它。

brotli-wasm 是經過實戰測試的,在 Node.js 和浏覽器中作為 HTTP 工具包的一部分在生産中使用,并包括通過 Node.js 和浏覽器測試進行自動建構以確定這一點。

3.如何使用 brotli-wasm

3.1 安裝 brotli-wasm

開發者能夠像使用其他包一樣将 brotli-wasm 直接導入到 Node 中,或者使用任何支援 ES 子產品和 WebAssembly 的打包器(例如: Webpack v4 或 v5、Vite、Rollup 和大多數其他)在浏覽器中導入。

npm install brotli-wasm           

對于每個目标(node.js、commonjs 打包器和 ESM 打包器),該子產品導出不同的 WASM 檔案和設定,入口點略有不同。 除了一些可能有所不同的其他導出之外,這些入口點都公開了一緻的預設導出 API(例如,Node 同步公開 brotli 方法,而浏覽器由于 WASM 限制始終需要 await)。

在所有建構中(在浏覽器中 await 導出的 Promise 之後),該子產品公開了兩個核心方法:

  • compress(Buffer, [options]) : 使用 Brotli 壓縮緩沖區,同時傳回壓縮後的緩沖區。 可以提供可選的選項對象。 目前唯一支援的選項是 quality,是 1 到 11 之間的數字。
  • decompress(Buffer) : 使用 Brotli 解壓縮緩沖區,傳回原始資料。

對于進階使用資料流用例,開發者可以使用用于流壓縮的 CompressStream 和 DecompressStream 類。

3.2 使用 brotli-wasm

如果想用相同的代碼支援 Node.js 和浏覽器,可以在任何地方使用預設導出的 await 浏覽器相容形式。

下面是 Node.js 中的使用用例:

const brotli = require("brotli-wasm");

const compressedData = brotli.compress(Buffer.from("some input"));
const decompressedData = brotli.decompress(compressedData);

console.log(Buffer.from(decompressedData).toString("utf8")); // Prints 'some input'           

下面是在浏覽器中使用的代碼示例:

import brotliPromise from "brotli-wasm";
// 導入預設導出
const brotli = await brotliPromise;
// 由于 wasm 要求,導入在浏覽器中是異步的!
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
const input = "some input";
const uncompressedData = textEncoder.encode(input);
const compressedData = brotli.compress(uncompressedData);
const decompressedData = brotli.decompress(compressedData);
console.log(textDecoder.decode(decompressedData)); 
// 列印 'some input'           

當然,在浏覽器中也可以使用 CDN 的方式進行導入:

let brotli = await import(
  "https://unpkg.com/[email protected]/index.web.js?module"
).then((m) => m.default);           

該包本身沒有運作時依賴性,如果更喜歡使用 Buffer 而不是 TextEncoder/TextDecoder,可能需要浏覽器 Buffer polyfill,即 browserify-zlib。

下面的代碼是在浏覽器環境中以流的方式使用:

import brotliPromise from "brotli-wasm"; 
const brotli = await brotliPromise; 
const input = "some input";

// 擷取輸入流:
const inputStream = new ReadableStream({
  start(controller) {
    controller.enqueue(input);
    controller.close();
  },
});
// 如有必要,将流資料轉換為 Uint8Arrays:
const textEncoderStream = new TextEncoderStream();
// 可以在此處使用喜歡的任何流分塊大小,具體取決于用例:
const OUTPUT_SIZE = 100;
// 建立一個流以在資料流式傳輸時增量壓縮資料:
const compressStream = new brotli.CompressStream();
const compressionStream = new TransformStream({
  transform(chunk, controller) {
    let resultCode;
    let inputOffset = 0;
    // 壓縮該塊,一次最多産生 OUTPUT_SIZE 輸出位元組,直到
     // 整個輸入已被壓縮。
    do {
      const input = chunk.slice(inputOffset);
      const result = compressStream.compress(input, OUTPUT_SIZE);
      controller.enqueue(result.buf);
      resultCode = result.code;
      inputOffset += result.input_offset;
    } while (resultCode === brotli.BrotliStreamResultCode.NeedsMoreOutput);
    if (resultCode !== brotli.BrotliStreamResultCode.NeedsMoreInput) {
      controller.error(
        `Brotli compression failed when transforming with code ${resultCode}`
      );
    }
  },
  flush(controller) {
   // 一旦塊完成,重新整理所有剩餘資料(再次重複固定輸出)
   // chunks) 來完成流:
    let resultCode;
    do {
      const result = compressStream.compress(undefined, OUTPUT_SIZE);
      controller.enqueue(result.buf);
      resultCode = result.code;
    } while (resultCode === brotli.BrotliStreamResultCode.NeedsMoreOutput);
    if (resultCode !== brotli.BrotliStreamResultCode.ResultSuccess) {
      controller.error(
        `Brotli compression failed when flushing with code ${resultCode}`
      );
    }
    controller.terminate();
  },
});

const decompressStream = new brotli.DecompressStream();
const decompressionStream = new TransformStream({
  transform(chunk, controller) {
    let resultCode;
    let inputOffset = 0;
   // 解壓縮該塊,一次産生最多 OUTPUT_SIZE 輸出位元組,直到
   // 整個輸入已被解壓縮。
    do {
      const input = chunk.slice(inputOffset);
      const result = decompressStream.decompress(input, OUTPUT_SIZE);
      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}`);
    }
  },
  flush(controller) {
    controller.terminate();
  },
});

const textDecoderStream = new TextDecoderStream();

let output = "";
const outputStream = new WritableStream({
  write(chunk) {
    output += chunk;
  },
});

await inputStream
  .pipeThrough(textEncoderStream)
  .pipeThrough(compressionStream)
  .pipeThrough(decompressionStream)
  .pipeThrough(textDecoderStream)
  .pipeTo(outputStream);
console.log(output); 
// 列印 'some input'           

需要注意的是,自 2022 年下半年開始,TransformStream 已在所有浏覽器中可用:https://caniuse.com/mdn-api_transformstream。 自 Node.js v16.5.0 起,它也可在 Node.js 中(實驗性地)使用。

4.brotli-wasm 相關替代品介紹

還有一些其他軟體包可以做 brotli-wasm 類似的事情,但是不幸的是,大部分都無法使用/或無法維護:

  • brotli-dec-wasm : 僅解壓縮器,從 Rust 編譯,功能與 brotli-wasm 類似,積極維護,但沒有可用的壓縮器(按設計)。 如果你隻需要解壓,這個包是個不錯的選擇。
  • Brotli.js : 手寫的 JS 解壓縮器,在大多數情況下似乎工作正常,但在某些邊界情況下會崩潰,并且壓縮器的 emscripten 版本根本無法在浏覽器中工作,最後一次更新還是在 2017 年。
  • wasm-brotli : 與 brotli-wasm 包一樣從 Rust 編譯,包括解壓縮器和壓縮器,但需要一個自定義的異步包裝器來使用 Webpack v4,并且在 Webpack v5 中根本不可用, 最後一次更新還是在 2019 年。

5.本文總結

本文主要和大家介紹 Brotli,即 Google 推出的一種無損壓縮算法,通過變種的 LZ77 算法、Huffman 編碼等方式進行資料壓縮。相信通過本文的閱讀,大家對 Brotli 會有一個初步的了解。

因為篇幅有限,關于 Brotli 的更多用法和特性文章并沒有過多展開,如果有興趣,可以在我的首頁繼續閱讀,同時文末的參考資料提供了大量優秀文檔以供學習。最後,歡迎大家點贊、評論、轉發、收藏,您的支援是我不斷創作的動力。

參考資料

https://github.com/httptoolkit/brotli-wasm

https://www.npmjs.com/package/browserify-zlib

https://kinsta.com/blog/brotli-compression/

https://www.alibabacloud.com/help/zh/cdn/user-guide/configure-brotli-compression

https://blog.csdn.net/weixin_44158429/article/details/130252308

https://devblogs.microsoft.com/dotnet/introducing-support-for-brotli-compression/

https://nickb.dev/blog/wasm-compression-benchmarks-and-the-cost-of-missing-compression-apis/

https://10web.io/site-speed-glossary/brotli-compression/