本記事では以下の 2 種類の方法を書きます:
- Compression Streams API を用いる方法
- ライブラリ pako を用いる方法
1. 予備知識
1.1. Deflate 圧縮のデータ形式について
Deflate 圧縮のデータ形式は複数あり、主に以下のものが使用されています:
- 生の deflate 形式 (RFC 1951)
- zlib 形式 (RFC 1950)
- 生の deflate 形式のデータにヘッダとフッタを付け加えた形式
- ※単に「deflate 形式」と言う場合は「生の deflate 形式」でなく「zlib 形式」を指す場合があるため注意
- gzip 形式 (RFC 1952)
- 生の deflate 形式のデータにヘッダとフッタを付け加えた形式
参考「RFC 1950: ZLIB Compressed Data Format Specification version 3.3」
参考「RFC 1951: DEFLATE Compressed Data Format Specification version 1.3」
参考「RFC 1952: GZIP file format specification version 4.3」
1.2. Compression Streams API について
Compression Streams API は deflate アルゴリズムでデータの圧縮や解凍を行える API で、この API が使用できる環境であれば別途ライブラリ等を利用しなくても圧縮および解凍できます。
ただし、2023-03-15 現在は以下のような一部の WEB ブラウザや環境によっては利用できないため、注意が必要です:
- Safari はベータ版 16.4 でサポート
- Firefox は非サポート
- Node.js は「生の deflate 形式」(
deflate-raw
) は非サポート
参考「Compression Streams API - Web API | MDN」
1.3. ライブラリ pako について
ライブラリ pako は deflate アルゴリズムでデータの圧縮や解凍を行う機能を提供します。
ライブラリ pako 自体は MIT License で利用できます。
(※ pako のビルド前のソースコードの一部は zlib License なため、ビルド前のソースコードを利用する場合は注意。)
参考「GitHub - nodeca/pako: high speed zlib port to javascript, works in browser & node.js」
参考「pako 2.1.0 API documentation」
2. Deflate 圧縮および解凍
2.1. 生の Deflate 形式
Compression Streams API を用いる場合はデータをストリームでやり取りするため、データとストリームの相互変換を行う必要があります。
ReadableStream
とデータの相互変換については別記事にしました。
参考「[JavaScript] ReadableStream とその他の型の相互変換 - Qiita」
ReadableStream.prototype.pipeThrough
メソッドを使用することでデータを変換するストリームを得られます。
CompressionStream
や DecompressionStream
を transformStream
として指定することでデータを圧縮および解凍するストリームを取得できます (TransformStream
を継承していなくても指定可能です) 。
参考「ReadableStream.pipeThrough() - Web API | MDN」
参考「CompressionStream - Web API | MDN」
参考「DecompressionStream - Web API | MDN」
Compression Streams API で生の Deflate 形式を扱うには format
に deflate-raw
を指定します。
参考「CompressionStream() - Web API | MDN」
参考「DecompressionStream() - Web API | MDN」
const deflateRaw = async data => {
const readableStream = new Blob([data]).stream();
const compressedStream = readableStream.pipeThrough(
// メモ: 毎回インスタンス化する必要がある
new CompressionStream('deflate-raw'),
);
const arrayBuffer = await new Response(compressedStream).arrayBuffer();
return arrayBuffer;
};
const inflateRaw = async data => {
const readableStream = new Blob([data]).stream();
const decompressedStream = readableStream.pipeThrough(
// メモ: 毎回インスタンス化する必要がある
new DecompressionStream('deflate-raw'),
);
const arrayBuffer = await new Response(decompressedStream).arrayBuffer();
return arrayBuffer;
};
ライブラリ pako を用いる場合、pako.deflateRaw
関数及び pako.inflateRaw
関数を使用することで生の Deflate 形式で圧縮および解凍できます。
参考「deflateRaw - pako 2.1.0 API documentation」
参考「inflateRaw - pako 2.1.0 API documentation」
const deflateRaw = data => pako.deflateRaw(data).buffer;
const inflateRaw = data => pako.inflateRaw(data).buffer;
上記の方法で定義した deflateRaw
関数および inflateRaw
関数で以下のようにして圧縮および解凍できます。
const textA = '犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。';
console.log(textA);
const textUint8ArrayA = new TextEncoder().encode(textA);
console.log(textUint8ArrayA.length);
// 圧縮
// メモ: deflateRaw 関数に直接 textA を渡すことも可
const arrayBufferA = await deflateRaw(textUint8ArrayA);
console.log(arrayBufferA.byteLength);
// 解凍
// TODO: 実際は例外処理が必要
const arrayBufferB = await inflateRaw(arrayBufferA);
console.log(arrayBufferB.byteLength);
const textB = new TextDecoder().decode(arrayBufferB);
console.log(textB);
犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。
120
84
120
犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。
2.2. zlib 形式
同様にして zlib 形式で圧縮および解凍することができます。
Compression Streams API で zlib 形式を扱うには format
に deflate
を指定します。
参考「CompressionStream() - Web API | MDN」
参考「DecompressionStream() - Web API | MDN」
const deflate = async data => {
const readableStream = new Blob([data]).stream();
const compressedStream = readableStream.pipeThrough(
// メモ: 毎回インスタンス化する必要がある
new CompressionStream('deflate'),
);
const arrayBuffer = await new Response(compressedStream).arrayBuffer();
return arrayBuffer;
};
const inflate = async data => {
const readableStream = new Blob([data]).stream();
const decompressedStream = readableStream.pipeThrough(
// メモ: 毎回インスタンス化する必要がある
new DecompressionStream('deflate'),
);
const arrayBuffer = await new Response(decompressedStream).arrayBuffer();
return arrayBuffer;
};
ライブラリ pako を用いる場合、pako.deflate
関数及び pako.inflate
関数を使用することで zlib 形式で圧縮および解凍できます。
参考「deflate - pako 2.1.0 API documentation」
参考「inflate - pako 2.1.0 API documentation」
const deflate = data => pako.deflate(data).buffer;
const inflate = data => pako.inflate(data).buffer;
上記の方法で定義した deflate
関数および inflate
関数で以下のようにして圧縮および解凍できます。
const textA = '犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。';
console.log(textA);
const textUint8ArrayA = new TextEncoder().encode(textA);
console.log(textUint8ArrayA.length);
// 圧縮
// メモ: deflate 関数に直接 textA を渡すことも可
const arrayBufferA = await deflate(textUint8ArrayA);
console.log(arrayBufferA.byteLength);
// 解凍
// TODO: 実際は例外処理が必要
const arrayBufferB = await inflate(arrayBufferA);
console.log(arrayBufferB.byteLength);
const textB = new TextDecoder().decode(arrayBufferB);
console.log(textB);
犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。
120
90
120
犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。
zlib 形式は生の deflate 形式のデータにヘッダとフッタを付け加えるため、生の deflate 形式より圧縮後のサイズが大きくなります (6 バイトまたは 10 バイト) 。
参考「Page 4 - RFC 1950: ZLIB Compressed Data Format Specification version 3.3」(「2.2. Data format」を参照)
2.3. gzip 形式
同様にして gzip 形式で圧縮および解凍することができます。
Compression Streams API で gzip 形式を扱うには format
に gzip
を指定します。
参考「CompressionStream() - Web API | MDN」
参考「DecompressionStream() - Web API | MDN」
const gzip = async data => {
const readableStream = new Blob([data]).stream();
const compressedStream = readableStream.pipeThrough(
// メモ: 毎回インスタンス化する必要がある
new CompressionStream('gzip'),
);
const arrayBuffer = await new Response(compressedStream).arrayBuffer();
return arrayBuffer;
};
const ungzip = async data => {
const readableStream = new Blob([data]).stream();
const decompressedStream = readableStream.pipeThrough(
// メモ: 毎回インスタンス化する必要がある
new DecompressionStream('gzip'),
);
const arrayBuffer = await new Response(decompressedStream).arrayBuffer();
return arrayBuffer;
};
ライブラリ pako を用いる場合、pako.gzip
関数及び pako.ungzip
関数を使用することで gzip 形式で圧縮および解凍できます。
参考「gzip - pako 2.1.0 API documentation」
参考「ungzip - pako 2.1.0 API documentation」
const gzip = data => pako.gzip(data).buffer;
const ungzip = data => pako.ungzip(data).buffer;
上記の方法で定義した gzip
関数および ungzip
関数で以下のようにして圧縮および解凍できます。
const textA = '犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。';
console.log(textA);
const textUint8ArrayA = new TextEncoder().encode(textA);
console.log(textUint8ArrayA.length);
// 圧縮
// メモ: gzip 関数に直接 textA を渡すことも可
const arrayBufferA = await gzip(textUint8ArrayA);
console.log(arrayBufferA.byteLength);
// 解凍
// TODO: 実際は例外処理が必要
const arrayBufferB = await ungzip(arrayBufferA);
console.log(arrayBufferB.byteLength);
const textB = new TextDecoder().decode(arrayBufferB);
console.log(textB);
犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。
120
102
120
犬や猫は肉球を持つが、兎は肉球を持たない。犬と猫は食肉目だが、兎は重歯目である。
gzip 形式は生の deflate 形式のデータにヘッダとフッタを付け加えるため、生の deflate 形式より圧縮後のサイズが大きくなります (18 バイト以上) 。
参考「Page 5 - RFC 1952: GZIP file format specification version 4.3」(「2.3. Member format」を参照)