JavaScriptでZIPファイルを操作する方法：zip.jsを使ってみよう

ZIPファイルは、複数のファイルやディレクトリを一つの圧縮ファイルにまとめる便利な形式です。今回は、JavaScriptを使ってZIPファイルを操作する方法を紹介します。具体的には、`zip.js`というライブラリを使って、ZIPファイルの内容をリスト表示し、ユーザーが選択したファイルを抽出する方法について解説します。また、ZIP64仕様にも対応できることを説明します。

# zip.jsとは？

`zip.js`は、JavaScriptでZIPファイルを読み書きするためのライブラリです。ブラウザ上でZIPファイルの操作ができるため、サーバー側の処理なしでクライアントサイドで完結できます。特に、ZIP64仕様にも対応しているため、大きなファイルやディレクトリの圧縮・解凍にも適しています。

`zip.js`の公式ドキュメントはこちらからご覧いただけます: [zip.js公式サイト](https://jsr.io/@zip-js/zip-js)

# 準備

まず、`zip.js`のライブラリをプロジェクトに追加します。以下のようにCDNから読み込むことができます。

```html
<script src="https://cdn.jsdelivr.net/npm/@zip.js/zip.js/dist/zip.min.js"></script>
```

次に、ZIPファイルを読み込んで内容をリスト表示し、選択したファイルを抽出するための基本的なHTMLとJavaScriptのコードを用意します。

## HTML

以下のHTMLでは、ファイル選択ボタンと、ZIPファイルの内容をリスト表示するための要素を配置しています。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ZIP File Test</title>
    <script src="https://cdn.jsdelivr.net/npm/@zip.js/zip.js/dist/zip.min.js"></script>
    <script src="script.js" defer></script>
</head>
<body>
    <h1>ZIP File Test</h1>
    <input type="file" id="fileSelect">
    <span id="fileName"></span>
    <button onclick="listFiles()">List Files</button>
    <div id="loading" style="display: none;">Loading...</div>
    <ul id="fileList"></ul>
    <button onclick="importSelectedFile()">Import Selected File</button>
    <pre id="output"></pre>
</body>
</html>
```

## JavaScript

次に、`zip.js`を使ってZIPファイルを読み込み、内容をリスト表示し、選択されたファイルを抽出するためのJavaScriptコードを記述します。

```javascript
let zipEntries = [];

async function listFiles() {
  const fileList = document.getElementById("fileList");
  const loading = document.getElementById("loading");
  const output = document.getElementById("output");
  output.textContent = "";
  fileList.innerHTML = "";
  loading.style.display = "block";

  try {
    // ファイル選択
    const file = document.querySelector("#fileSelect").files[0];
    if (!file) {
      alert("ファイルが選択されていません。");
      loading.style.display = "none";
      return;
    }

    // zip.jsを使用してZIPファイルを読み込み
    const reader = new zip.ZipReader(new zip.BlobReader(file));
    const entries = await reader.getEntries();
    zipEntries = entries;

    if (entries.length === 0) {
      fileList.innerHTML = "<li>ZIPファイルにファイルが含まれていません。</li>";
    } else {
      entries.forEach((entry, index) => {
        const listItem = document.createElement("li");
        listItem.textContent = entry.filename;
        const radioButton = document.createElement("input");
        radioButton.type = "radio";
        radioButton.name = "file";
        radioButton.value = index;
        listItem.prepend(radioButton);
        fileList.appendChild(listItem);
      });
    }

    await reader.close();
  } catch (ex) {
    console.error("Error listing ZIP file entries:", ex);
    output.textContent =
      "ZIPファイルのリスト表示中にエラーが発生しました。コンソールログを確認してください。\n" +
      ex;
  } finally {
    loading.style.display = "none";
  }
}

async function importSelectedFile() {
  const output = document.getElementById("output");
  const loading = document.getElementById("loading");
  const selectedFileIndex = document.querySelector('input[name="file"]:checked');
  output.textContent = "";
  loading.style.display = "block";

  if (!selectedFileIndex) {
    alert("ファイルが選択されていません。");
    loading.style.display = "none";
    return;
  }

  try {
    const entry = zipEntries[selectedFileIndex.value];
    if (entry) {
      const text = await entry.getData(new zip.TextWriter());
      output.textContent = text;
    } else {
      output.textContent = "選択されたファイルがZIP内に見つかりませんでした。";
    }
  } catch (ex) {
    console.error("Error extracting selected file from ZIP:", ex);
    output.textContent =
      "ZIPファイルから選択されたファイルの抽出中にエラーが発生しました。コンソールログを確認してください。\n" +
      ex;
  } finally {
    loading.style.display = "none";
  }
}

document.querySelector("#fileSelect").addEventListener("change", (event) => {
  const fileName = document.getElementById("fileName");
  const file = event.target.files[0];
  if (file) {
    fileName.textContent = `Selected file: ${file.name}`;
  } else {
    fileName.textContent = "";
  }
});
```

## ZIP64対応

`zip.js`ライブラリはZIP64形式に対応しています。これは、標準のZIP形式の制約（ファイルサイズが4GBを超えない、ファイル数が65535を超えないなど）を超える場合に使用されます。`zip.js`は、特に追加の設定を行うことなくZIP64形式のファイルを扱うことができます。

# まとめ

今回は、`zip.js`ライブラリを使ってZIPファイルの内容をリスト表示し、ユーザーが選択したファイルを抽出する方法を紹介しました。また、`zip.js`がZIP64形式にも対応していることを説明しました。これにより、大規模なファイルや多くのファイルを含むZIPファイルも簡単に扱うことができます。ぜひ、プロジェクトで試してみてください！