JavaScript/Compression Living Standard

Compression Living Standard

はじめに

Web開発の世界では、データの圧縮と展開は重要な役割を果たしています。特にクライアントサイドでのデータ処理能力が向上するにつれて、ブラウザ内でのデータ圧縮・展開処理の需要が高まっています。Compression Living Standardは、Webブラウザでのデータ圧縮と展開を標準化し、開発者がJavaScriptを使って効率的にデータを処理できるようにする仕様です。

Compression Streamsの基本

Compression Living Standardの中核となるのが、CompressionStreamDecompressionStreamという2つのクラスです。これらは、ストリーミング処理を通じてデータを圧縮・展開することができます。

// 圧縮ストリームの作成
const compressedStream = new CompressionStream('gzip');

// 展開ストリームの作成
const decompressedStream = new DecompressionStream('gzip');

これらのクラスはTransformStreamインターフェースを実装しており、Streams APIと完全に連携することができます。

対応フォーマット

現在の仕様では、以下の圧縮フォーマットがサポートされています:

フォーマット 説明 用途
gzip 広く使用されている圧縮形式 汎用的なデータ圧縮、HTTPレスポンスの圧縮
deflate zlib圧縮アルゴリズム レガシーシステムとの互換性
deflate-raw ヘッダーとフッターのないdeflate 特定の状況での効率的な圧縮

実践的な使用例

テキストデータの圧縮と展開

以下の例では、テキストデータを圧縮し、その後展開する方法を示しています:

async function compressAndDecompress(text) {
  // テキストをUint8Arrayに変換
  const encoder = new TextEncoder();
  const data = encoder.encode(text);
  
  // データを圧縮
  const compressedStream = new CompressionStream('gzip');
  const writer = compressedStream.writable.getWriter();
  writer.write(data);
  writer.close();
  
  // 圧縮されたデータを収集
  const reader = compressedStream.readable.getReader();
  const chunks = [];
  
  for (;;) {
    const { done, value } = await reader.read();
    if (done) break;
    chunks.push(value);
  }
  
  // 圧縮されたデータをマージ
  const compressedData = new Uint8Array(
    chunks.reduce((acc, chunk) => acc + chunk.length, 0)
  );
  
  let offset = 0;
  for (const chunk of chunks) {
    compressedData.set(chunk, offset);
    offset += chunk.length;
  }
  
  console.log(`元のサイズ: ${data.length} バイト`);
  console.log(`圧縮後のサイズ: ${compressedData.length} バイト`);
  
  // 圧縮されたデータを展開
  const decompressedStream = new DecompressionStream('gzip');
  const decompressWriter = decompressedStream.writable.getWriter();
  decompressWriter.write(compressedData);
  decompressWriter.close();
  
  // 展開されたデータを収集
  const decompressReader = decompressedStream.readable.getReader();
  const decompressedChunks = [];
  
  for (;;) {
    const { done, value } = await decompressReader.read();
    if (done) break;
    decompressedChunks.push(value);
  }
  
  // 展開されたデータをマージして返却
  const decompressedData = new Uint8Array(
    decompressedChunks.reduce((acc, chunk) => acc + chunk.length, 0)
  );
  
  let decompressOffset = 0;
  for (const chunk of decompressedChunks) {
    decompressedData.set(chunk, decompressOffset);
    decompressOffset += chunk.length;
  }
  
  // テキストに戻す
  const decoder = new TextDecoder();
  return decoder.decode(decompressedData);
}

// 使用例
const originalText = "これはCompression APIのテストです。同じデータが繰り返されると圧縮率が高くなります。" +
                   "これはCompression APIのテストです。同じデータが繰り返されると圧縮率が高くなります。" +
                   "これはCompression APIのテストです。同じデータが繰り返されると圧縮率が高くなります。";

compressAndDecompress(originalText).then(result => {
  console.log("元のテキストと一致:", result === originalText);
});

ファイルの圧縮

以下の例では、ユーザーがアップロードしたファイルをブラウザ側で圧縮する方法を示しています:

document.getElementById('fileInput').addEventListener('change', async (event) => {
  const file = event.target.files[0];
  if (!file) return;
  
  // ファイルをArrayBufferとして読み込む
  const arrayBuffer = await file.arrayBuffer();
  const fileData = new Uint8Array(arrayBuffer);
  
  // データを圧縮
  const compressedStream = new CompressionStream('gzip');
  const compressWriter = compressedStream.writable.getWriter();
  compressWriter.write(fileData);
  compressWriter.close();
  
  // 圧縮されたデータを取得
  const reader = compressedStream.readable.getReader();
  const chunks = [];
  
  for (;;) {
    const { done, value } = await reader.read();
    if (done) break;
    chunks.push(value);
  }
  
  // 圧縮されたデータを結合
  let totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
  const compressedData = new Uint8Array(totalLength);
  
  let offset = 0;
  for (const chunk of chunks) {
    compressedData.set(chunk, offset);
    offset += chunk.length;
  }
  
  // 圧縮されたファイルを保存
  const compressedBlob = new Blob([compressedData], { type: 'application/gzip' });
  const downloadLink = document.createElement('a');
  downloadLink.href = URL.createObjectURL(compressedBlob);
  downloadLink.download = file.name + '.gz';
  downloadLink.click();
  
  // 圧縮率を表示
  const compressionRatio = ((1 - compressedData.length / fileData.length) * 100).toFixed(2);
  document.getElementById('compressionInfo').textContent = 
    `元のサイズ: ${fileData.length} バイト, 圧縮後: ${compressedData.length} バイト (${compressionRatio}% 削減)`;
});

対応するHTML:

<input type="file" id="fileInput">
<div id="compressionInfo"></div>

フェッチAPIとの統合

Compression APIはフェッチAPIと組み合わせることで、ネットワーク経由で受け取ったデータをクライアント側で展開することができます:

async function fetchAndDecompress(url) {
  // gzip圧縮データを取得
  const response = await fetch(url);
  const compressedData = await response.arrayBuffer();
  
  // データを展開
  const decompressStream = new DecompressionStream('gzip');
  const decompressedStream = new Response(
    new Blob([compressedData]).stream().pipeThrough(decompressStream)
  ).body;
  
  // 展開されたデータを読み込む
  const reader = decompressedStream.getReader();
  const chunks = [];
  
  for (;;) {
    const { done, value } = await reader.read();
    if (done) break;
    chunks.push(value);
  }
  
  // 展開されたデータを結合
  const decompressedData = new Uint8Array(
    chunks.reduce((acc, chunk) => acc + chunk.length, 0)
  );
  
  let offset = 0;
  for (const chunk of chunks) {
    decompressedData.set(chunk, offset);
    offset += chunk.length;
  }
  
  return decompressedData;
}

// 使用例
fetchAndDecompress('https://example.com/data.gz')
  .then(data => {
    // 展開されたデータを処理
    const decoder = new TextDecoder();
    const text = decoder.decode(data);
    console.log('展開されたデータ:', text);
  })
  .catch(error => {
    console.error('エラー:', error);
  });

ブラウザ互換性と対応状況

現在のWebブラウザにおけるCompression Standard対応状況は、主要なブラウザでサポートが広がっています:

ブラウザ サポート開始
Chrome 80+
Firefox 102+
Safari 15+
Edge 80+

エラー処理とベストプラクティス

圧縮・展開処理を実装する際には、適切なエラー処理が重要です:

async function safeCompression(data, format = 'gzip') {
  try {
    const compressStream = new CompressionStream(format);
    const writer = compressStream.writable.getWriter();
    
    writer.write(data);
    writer.close();
    
    const reader = compressStream.readable.getReader();
    const chunks = [];
    
    for (;;) {
      const { done, value } = await reader.read();
      if (done) break;
      chunks.push(value);
    }
    
    // 結果を結合
    let totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
    const result = new Uint8Array(totalLength);
    
    let offset = 0;
    for (const chunk of chunks) {
      result.set(chunk, offset);
      offset += chunk.length;
    }
    
    return {
      success: true,
      data: result,
      originalSize: data.length,
      compressedSize: result.length,
      ratio: ((1 - result.length / data.length) * 100).toFixed(2) + '%'
    };
    
  } catch (error) {
    console.error(`圧縮エラー (${format}):`, error);
    return {
      success: false,
      error: error.message,
      format: format
    };
  }
}

パフォーマンスの考慮点

圧縮処理は計算負荷の高い操作です。以下の点に注意することで、効率的に利用できます:

// 大きなファイルを処理する場合のチャンク処理の例
async function compressLargeFile(file, chunkSize = 1024 * 1024) {
  const fileSize = file.size;
  const compressStream = new CompressionStream('gzip');
  const writer = compressStream.writable.getWriter();
  
  // ファイルを小さなチャンクに分割して処理
  for (let position = 0; position < fileSize; position += chunkSize) {
    const chunk = file.slice(position, position + chunkSize);
    const arrayBuffer = await chunk.arrayBuffer();
    writer.write(new Uint8Array(arrayBuffer));
    
    // 進捗表示(オプション)
    const progress = Math.min(100, Math.round((position + chunkSize) / fileSize * 100));
    console.log(`処理中: ${progress}%`);
  }
  
  // ストリームを閉じる
  writer.close();
  
  // 圧縮されたデータを収集
  const reader = compressStream.readable.getReader();
  const chunks = [];
  
  for (;;) {
    const { done, value } = await reader.read();
    if (done) break;
    chunks.push(value);
  }
  
  // 結果を結合
  const compressedSize = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
  const compressedData = new Uint8Array(compressedSize);
  
  let offset = 0;
  for (const chunk of chunks) {
    compressedData.set(chunk, offset);
    offset += chunk.length;
  }
  
  return new Blob([compressedData], { type: 'application/gzip' });
}

まとめ

Compression Living Standardは、Webアプリケーションにおけるデータ圧縮・展開処理をシンプルかつ効率的に行うための強力なAPIです。ストリームベースの設計により、大きなデータセットであっても効率的に処理することができ、モダンなWeb開発において重要な役割を果たしています。この標準を活用することで、データ転送量の削減、処理速度の向上、そしてユーザーエクスペリエンスの改善を実現できます。

カテゴリ:JavaScript
カテゴリ:JavaScript