【JavaScript】データを GZIP/ZLIB/DEFLATE で圧縮そして展開する

ウェブブラウザの 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.bodyReadableStream オブジェクトです。そして、その 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() コンストラクタには圧縮方式を表すキーワードを引き渡しますが、指定できる値は次の通りです。

キーワード圧縮方式
deflateZLIB
deflate-rawDEFLATE
gzipGZIP

Compression Streams API はデータの圧縮と展開を扱いますが、ファイルは扱いません。圧縮方式に GZIP を指定できるとはいえ、圧縮されたファイルの情報(ファイル名など)は扱えませんので注意してください。

そういう意味では、利用できない情報が追加されている GZIP はその分だけサイズが増えてしまいますので、ウェブアプリがデータを圧縮する必要がある場合には、ZLIB や DEFLATE のほうが良いかもしれません。

まとめ

ウェブアプリに求められる機能が高度化するに伴い、データ圧縮は必要不可欠になっているのではないでしょうか。例えばサイズが限られた Web Storage に多くのデータを保存したい場合、クラウドとのデータ通信量を削減 (コスト削減) したい場合 など、圧縮が求められるケースは増えていくばかりでしょう。

今回は以上で終わりです。最後まで読んでくださりありがとうございました。それでは次回の記事までごきげんよう。

Share