こんにちは。
今回は、個人的に地味に待望していた機能が実装されたので、そのご紹介となります。
ネイティブの圧縮伸張
昨今のHTTP通信は、帯域や読込時間を削減するために何らかの圧縮処理がほぼ必ず施されています。
そして、それらの圧縮処理は実行エンジン内部で自動的に行われているため、ふだん私たちがJavaScriptでHTTP通信するときは意識する必要もありません。
しいて言えば Accept-Encoding
ヘッダでどのアルゴリズムが使用されているか、垣間を見ることができるくらいです。
最近のJavaScriptは他の言語と遜色ないほど高速で汎用になり、バイナリ操作や計算をする機会も増えてきました。
私も「汎用ランタイム環境としてのChromium系」というかたちでウェブアプリを作成しています。
その過程で、やはりデータを圧縮したり伸張したりしたい場面は出てくるもので、しかし標準機能としては提供されていないため、何らかのサードパーティ製ライブラリを使用する必要がありました。
(幸いJavaScriptには pako という優秀なライブラリがあるので重宝していました)
しかし、上述のとおり実行エンジン内部でHTTP通信のために圧縮処理をしているということは、内部にも圧縮機構を搭載していることを意味します。
しかもネイティブなので実行速度も信頼性も断然上です。
この内部の圧縮機構をJavaScript上で使えるように整備したAPIが CompressionStream
というわけです。
HTTPで使用される圧縮アルゴリズムは DEFLATE 系なので、大抵の場合は zlib が実装されています。
(正確にはもうひとつ brotli と呼ばれるアルゴリズムも搭載されていますが、これは巨大辞書を使用した文章を高効率で圧縮するためのアルゴリズムなので、今回は除外しています)
使用例
名前に "Stream" とあることからも分かるように、このAPIは Streams API のなかで機能します。
ストリームは、通信においては便利な仕組ですが、ことバイナリ操作においては、そのまま使用するには少し複雑な場合があります。
そこで、バイナリデータも簡単に扱えるラップ関数を用意してみました。
function deflateEncode(data, gzip){
return new Response(new Blob([data]).stream().pipeThrough(new CompressionStream(gzip ? "gzip" : "deflate"))).arrayBuffer();
}
function deflateDecode(data, gzip){
return new Response(new Blob([data]).stream().pipeThrough(new DecompressionStream(gzip ? "gzip" : "deflate"))).arrayBuffer();
}
まず、入力されたデータをBLOBへ変換します。
これは Blob.prototype.stream()
を使用するためで、固定データをストリーム化するいちばん手っ取り早い方法です。
次に、ストリームを CompressionStream
へパイプします。
CompressionStream
インスタンスは readable
と writable
プロパティを持っているので、そのままパイプの接続先として適用できます。
なお、圧縮アルゴリズムはDEFLATEとGZIPを選択できます。
どちらもアルゴリズム自体は同じなので、単純にファイルシステム周りのオプションヘッダの有無ということになりますが、ファイルシステムと切り離されているJavaScript上で圧縮にGZIPを使うのはあまり意味がないようにも思えます。
GZIPは受けたデータを伸張する場合に活躍しそうです。
最後に、ストリームを終端して ArrayBuffer
で返します。
Response
は本来 fetch
関連として実装された機能ですが、仕様としてネットワーク通信からは切り離されており、任意のストリームを受けることができます。
そのうちのひとつとして fetch
の応答があるわけです。
Response
も Blob.prototype.stream()
と同じく、ストリームを固定データ化するいちばん手っ取り早い方法です。
適当なサンプルデータを用意して圧縮してみるサンプルです。
なおランダムなので圧縮率は低いです。
Chrome v98 で実行しました。
const sample = new Uint8Array(new Float32Array(4 * 0x100000).map(() => Math.random()).buffer);
const encode = await deflateEncode(sample, false);
const decode = await deflateDecode(encode, false);
denoでも使えます (v1.19で待望の実装!)
さいごに
他にも、ネイティブ実装を整備してJavaScriptで活用しようとする試みがいくつかあるので、そのうち紹介できればと思います。
例えば Web Codecs API など、これはネイティブのメディアコーデックを使えるようにすることで、わざわざWASMでffmpegなどを用意しなくても動画変換などが出来てしまう、割と革新的なAPIです。