ウェブブラウザの JavaScript でデータの圧縮と展開を行う場合、JavaScript ライブラリの「pako」が選択肢に上がることが多いでしょう。しかし、現在はどのブラウザでも Compression Streams API がサポートされており、JavaScript ライブラリの力を借りなくても、データの圧縮と展開を行うことができます。今回は、Compression Streams API を使ったデータの圧縮と展開の方法について解説します。
Compression Streams API とは
通信データの圧縮と展開は HTTP 通信でも行われており、ウェブブラウザはデータの圧縮と展開の機能を内部的に備えています。この内部機能を JavaScript からでも利用できるようにしたのが Compression Streams API です。詳細は記事「GZIP/ZLIB/DEFLATE データをダウンロードして Compression Streams API で解凍する」をご覧ください。
Compression Streams API では、CompressionStream
インタフェースと DecompressionStream
インタフェースが圧縮と展開を担います。しかし、これらは名前の通り、ストリームを前提としたインタフェースです。そのため、これらは単独でデータの圧縮と展開はできません。fetch
などで得られたストリーム (ReadableStream
オブジェクト) と一緒に使う必要があります。ここでは、少しだけ fetch
で得られた圧縮データを展開する手順をおさらいしておきましょう。
以下のコードは、fetch
で得られた GZIP ファイルのデータをストリームの状態で展開しようとしています。response.body
が ReadableStream
オブジェクトです。そして、その pipeThrough()
メソッドを使って、DecompressionStream
を連結しています。
// GZIP ファイルを fetch でダウンロード開始
const response = await fetch('text.gzip');
// fetch の ReadableStream (response.body) に
// GZIP の DecompressionStream を連結
const rstream = response.body.pipeThrough(
new DecompressionStream('gzip')
);
以降は ReadableStream
オブジェクトである変数 rstream
を通して、展開されたデータをストリームとして取り出します。この流れを応用して、データ圧縮と展開を通信を伴わずに行います。
Blob オブジェクトを使って ReadableStream
オブジェクトを生成
前述のコードをご覧になって分かるように、展開を担う DecompressionStream
インタフェースを使うためには、圧縮データを ReadableStream
オブジェクトという形で用意する必要があります。逆に圧縮を担う CompressionStream
インタフェースを使う場合も、元となるデータを ReadableStream
オブジェクトという形で用意する必要があります。
そこで便利なのが Blob
オブジェクトです。まずはデータの圧縮の手順を見ていきましょう。
例えば、テキストデータの Blob
オブジェクトは次のようにして生成できます。そして、Blob
オブジェクトの stream()
メソッドで、Blob
データの ReadableStream
オブジェクトが得られます。
const blob = new Blob(, { type: 'text/plain; charset=utf-8' });
const blob_stream = blob.stream();ream = txt_blob.stream();
これで元となるテキストデータをストリームという形にすることができました。
CompressionStream
インタフェースを使ってデータを圧縮
元のデータの ReadableStream
オブジェクトが得られたら、pipeThrough()
メソッドを使って読み取りストリームに CompressionStream
オブジェクトを連結します。ここでは圧縮方式に GZIP を表す "gzip"
を引数に与えています。
const comp_stream = blob_stream.pipeThrough(
new CompressionStream('gzip')
);
これによって新たに生成された ReadableStream
オブジェクト (変数 gzip_stream
) は、元のテキストデータを GZIP で圧縮した状態でデータをストリームすることになります。
ストリームではなく一括で結果のデータを得る
GZIP で圧縮したデータをストリームする ReadableStream
オブジェクトができたといえ、実際にはストリームでデータを得たいわけではなく、圧縮済みのデータを一括で欲しいのではないでしょうか。その場合は、Response
オブジェクトを使います。
const response = new Response(comp_stream);
const arybuf = await response.arrayBuffer();
Response()
コンストラクタに ReadableStream
オブジェクト (変数 gzip_stream
) を引き渡すことで、Response
オブジェクト (変数 response
) を生成しています。Response
オブジェクトは、fetch
の HTTP レスポンスを表すオブジェクトと同じです。そのオブジェクトを手動で生成したと考えてください。
Response
オブジェクトの arrayBuffer()
メソッドは非同期で ArrayBuffer
オブジェクトを返します。これが、元のテキストデータを GZIP で圧縮したバイナリデータになります。
データ圧縮のおさらい
以上を踏まえて、テキストデータの圧縮手順をまとめましょう。次のコードは一連のテキストデータの圧縮処理を関数として書いたものです。
async function compressText(text) {
// 元のテキストデータを Blob オブジェクトにして ReadableStream オブジェクトを生成
const blob = new Blob(, { type: 'text/plain; charset=utf-8' });
const blob_stream = blob.stream();
// ReadableStream オブジェクトに GZIP 圧縮を行うストリームをアタッチ
const comp_stream = blob_stream.pipeThrough(
new CompressionStream('gzip')
);
// GZIP 圧縮を行うストリームからまとめて圧縮データを ArrayBuffer として取り出す
const response = new Response(comp_stream);
const arybuf = await response.arrayBuffer();
return arybuf;
}
今回はテキストデータの圧縮を例に挙げましたが、もちろん、バイナリデータも扱えます。とにかく元となるデータを Blob
オブジェクトにさえできれば、あとの処理は同じです。
圧縮データの展開
圧縮データを展開は、前述のデータ圧縮の手順とほとんど同じです。まず圧縮データを表す ArrayBuffer
オブジェクトから Blob
オブジェクトを経てストリームを作ります。そのストリームに展開を担う変換ストリーム (DecompressionStream
) をアタッチし、最後に Response
オブジェクトで一括でデータを取得します。
async function decompressText(arybuf) {
// GZIP 圧縮データを表す ArrayBuffer を Blob オブジェクトにして
// ReadableStream オブジェクトを生成
const blob = new Blob([arybuf]);
const blob_stream = blob.stream();
// ReadableStream オブジェクトに GZIP 解凍を行うストリームをアタッチ
const decomp_stream = blob_stream.pipeThrough(
new DecompressionStream('gzip')
);
// GZIP 解凍を行うストリームからまとめてテキストを取り出す
const response = new Response(decomp_stream);
const text = await response.text();
return text;
}
今回はテキストデータを扱ったため、展開の際、Response
オブジェクトの text()
メソッドを呼び出しましたが、もしバイナリデータを扱っているなら Response
オブジェクトから arrayBuffer()
メソッドや blob()
メソッドを呼び出してください。
以上の圧縮と展開の関数を実行して、期待通りに動作しているかを確かめてみましょう。
(async () => {
const arybuf = await compressText('こんにちは');
const text = await decompressText(arybuf);
console.log(text);
})();
期待通りに「こんにちは」と出力されるはずです。
圧縮方式
CompressionStream()
コンストラクタと DecompressionStream()
コンストラクタには圧縮方式を表すキーワードを引き渡しますが、指定できる値は次の通りです。
キーワード | 圧縮方式 |
---|---|
deflate | ZLIB |
deflate-raw | DEFLATE |
gzip | GZIP |
Compression Streams API はデータの圧縮と展開を扱いますが、ファイルは扱いません。圧縮方式に GZIP を指定できるとはいえ、圧縮されたファイルの情報(ファイル名など)は扱えませんので注意してください。
そういう意味では、利用できない情報が追加されている GZIP はその分だけサイズが増えてしまいますので、ウェブアプリがデータを圧縮する必要がある場合には、ZLIB や DEFLATE のほうが良いかもしれません。
まとめ
ウェブアプリに求められる機能が高度化するに伴い、データ圧縮は必要不可欠になっているのではないでしょうか。例えばサイズが限られた Web Storage に多くのデータを保存したい場合、クラウドとのデータ通信量を削減 (コスト削減) したい場合 など、圧縮が求められるケースは増えていくばかりでしょう。
今回は以上で終わりです。最後まで読んでくださりありがとうございました。それでは次回の記事までごきげんよう。