はじめに

前回まで2回にわたり、Zip ファイルの内部構造について説明してきました。

この Zip ファイルを Web クライアントで扱えるライブラリには、数多くのものがあります。

今回は、代表的なライブラリとして JSZip ライブラリ について、基本的な使い方を中心に詳しくみていきたいと思います。


JSZip とは

JSZip(公式サイト) は、ブラウザや Node.js で Zip ファイルを生成・読み込みできる JavaScript ライブラリです。

Stuart Knightley 氏によって、2009 年に開発が開始されました。

2013 年に v1.0.0 がリリースされ、2016 年の v3.0.0 では、従来の同期 API の多くが非同期 API に変更されました。generateAsync()loadAsync() など、現在の JSZip で使用される主要な非同期 API も、このバージョンで導入されています。

現在は TypeScript 対応・Streams 対応・安全性強化などを経て、npm で数百万〜数千万規模のダウンロードを持つ実績のあるライブラリです。

特徴

  • シンプルで直感的な API
  • Promise ベースの非同期 API
  • ブラウザと Node.js の両方で利用可能
  • TypeScript 用の型定義が提供されている

ライセンス

  • MIT または GPLv3 のデュアルライセンス

※ このライブラリのおかげで、Web クライアントプログラムでも、簡単に Zip ファイルを取り扱うことが可能となっています。当サイトのサンプルプログラムでも、利用させていただいております。開発者の方、関係者の方には、この場をお借りして、深くお礼を申し上げたいと思います。


インストール方法は?

代表的なインストール方法を下記に示します。

① CDN で読み込む(最も簡単)

<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>

② npm でインストール(本格的なプロジェクト向け)

npm install jszip

③ ダウンロード

  • JSZip(公式サイト) から Zip ファイルをダウンロード後に展開し、展開したフォルダ配下の dist/jszip.js または、 dist/jszip.min.js を利用する。

JSZipダウンロード画面

例えば、ダウンロードした Zip ファイルが、Stuk-jszip-v3.10.1-3-g643714a.zip の場合、エクスプローラーの右クリックメニューの「すべて展開」を選択すると、ダウンロードフォルダに下記のように展開されます。

※ 本記事ではJSZip 3.10.1を使用しています。

C:\User\test\Downloads\Stuk-jszip-v3.10.1-3-g643714a
└─Stuk-jszip-643714a
    │    :
    │  LICENSE.markdown
    │  README.markdown
    │    :
    │
    ├─.github
    │
    ├─dist
    │  jszip.js
    │  jszip.min.js
    │
    ├─docs
    ├─documentation
    ├─lib
    ├─test
    └─vendor

※ 内容は一部省略しています。

dist フォルダには、ブラウザなどから利用するためにビルドされた JSZip の配布ファイルが入っています。どちらも同じ機能を持っており、どちらを使用してもかまいませんが、jszip.min.js は jszip.js を minify(圧縮)して、ファイルサイズを小さくしたものです。

※ 本記事では、この中の jszip.min.js を使用します。

尚、配布などを行う場合は、LICENSE.markdown, README.markdown も一緒に配布する必要があります。

※ 実際のインストール方法は、これら以外にも存在します。詳しくは、公式サイトを参照してください。


基本的な Zip の作り方

それでは、基本的な Zip ファイルの作成方法をみていきたいと思います。

JSZip ライブラリでは、シンプルな操作で Zip ファイルが作成できるようになっています。

最低限やらないといけない手順は、

  • JSZip オブジェクトの生成
  • file() を呼び出して含めるファイルを追加
  • generateAsync() を呼び出して Zip 生成

です。

例えば、Zip ファイルを作成する場合、

const zip = new JSZip();

zip.file("hello.txt", "Hello World");

zip.generateAsync({ type: "blob" }).then((content) => {
  console.log(content);
});

generateAsync() の戻り値として、生成された Zip ファイルを表す Blob が得られます。

複数のファイルを同一 Zip に含めたい場合は、file() メソッドを含めたいファイル分実行し、最後に generateAsync() メソッドを呼び出すだけです。

※ 実際に生成した Zip ファイルをダウンロードするには、Blob から Object URL を作成し、<a> 要素を利用する方法があります。


Zip にフォルダを含める場合

これには、2 つの方法が用意されています。

  • 意図的にフォルダを追加する方法
  • フォルダが含まれるパス名を受け渡す方法

前回までの Zip ファイルの内部構造でご説明したとおり、Zip ファイルは、起点からのパス名で保管されています。その為、どちらの方法をとっても、ある1点を除けば、JSZip ライブラリが同じように Zip ファイルを作成してくれます。

では、何が違うのでしょうか?

それは、空のフォルダが作成できるかどうかです。

「フォルダが含まれるパス名を受け渡す方法」では、ファイルが存在しないとフォルダを指定することができませんが、文字通り「意図的にフォルダを追加する方法」では、空のフォルダを Zip 内に作成することができます。

それでは、それぞれの方法をみていきたいと思います。


意図的にフォルダを追加する方法

意図的にフォルダを追加するには、folder() メソッドを利用します。

例えば、ファイル選択ダイアログで選択したファイルを data というフォルダ配下に配置して Zip ファイルを作成する場合、

const zip = new JSZip();

zip.folder("data").file("hello.txt", "Hello World");

zip.generateAsync({ type: "blob" }).then((content) => {
  console.log(content);
});

generateAsync() の戻り値として、生成された Zip ファイルを表す Blob が得られます。

Zip ファイルは、

Zip 内部は、次のような構造になります。

└─data
      hello.txt

となります。

もし、file()メソッドによるファイル追加を行わなければフォルダーだけが作成されます。

例:

const zip = new JSZip();

zip.folder("data");

zip.generateAsync({ type: "blob" }).then((content) => {
  console.log(content);
});

この Zip ファイルには、data フォルダだけが含まれます。

Zip 内部は、次のような構造になります。

└─data


フォルダが含まれるパス名を受け渡す方法

フォルダが含まれるパス名を受け渡す場合、file() メソッドの第一引数にパス名を、第二引数にファイル本体を受け渡します。

例えば、data というフォルダ配下に配置する場合、

const zip = new JSZip();

zip.file("data/hello.txt", "Hello World");

zip.generateAsync({ type: "blob" }).then((content) => {
  console.log(content);
});

generateAsync() の戻り値として、生成された Zip ファイルを表す Blob が得られます。

Zip ファイルは、

Zip 内部は、次のような構造になります。

└─data
      hello.txt

となります。

この方法では、パス名を渡すことにより、副次的にフォルダが存在することになります。その為、フォルダのみを作成することはできません。


Zip ファイルの読み込み

Zip ファイルの読み込みも作成時同様にシンプルな操作で、できるようになっています。

手順としては、

  • JSZip.loadAsync() を呼び出して Zip ファイルを読み込む
  • async() を呼び出して、必要なファイルの内容を読み込む

です。

例えば、既存のZipを読み込む場合、

const loadedZip = await JSZip.loadAsync(file);

for (const zipObject of Object.values(loadedZip.files)) {
  if (zipObject.dir) {
    // フォルダー
  } else {
    // ファイル
    const content = await zipObject.async("uint8array");
  }
}

loadAsync() の戻り値であるオブジェクトの files には、Zip ファイルに格納されているファイルのパス名をキーとして、対応する JSZipObject が格納されています。

また、JSZipObject の async() メソッドでは、指定した型で該当ファイルの中身を取り出すことが可能です。

※ Zip 内のファイルをユーザーのローカルファイルシステムへ展開する場合は、File System Access API や OPFS などを利用して、展開先を別途用意する必要があります。OPFS は、ブラウザ専用のファイルシステムです。詳細については、過去記事「データ保存技術/OPFSの概要と基本的な使い方」を参照してください。


まとめ

  • JSZip ライブラリでは、シンプルな操作で Zip ファイルの読み書きができる。
  • 2016 年の v3.0.0 では、従来の同期 API が非同期 API へ大きく変更され、generateAsync()loadAsync() など、Promise を利用する API が提供されています。ただし、圧縮・展開処理そのものがメインスレッドで実行される場合、大きなファイルでは画面の応答性に影響する可能性があります。そのような場合は Web Worker で処理することを検討します。Web Worker は、メインスレッドとは別のスレッドでJavaScriptを実行するための機能です。詳細については、過去記事「モダンJavaScript入門/Web Worker の基本」を参照してください。
  • JSZip は Zip 内のフォルダー構造を扱えますが、ユーザーのローカルファイルシステムからフォルダーを選択し、その配下のファイルを自動的に収集して Zip 化したり、Zip のフォルダー構造をそのままローカルファイルシステムへ展開したりする機能はありません。その部分は File System Access API などを利用して個別に実装する必要があります。

次回予告

今回は、わかりやすさ優先で、基本的な使い方のみに焦点をあてて説明してきました。次回以降では、JSZipのオブジェクトと各メソッドについてもう少し深堀していきたいと思います。