はじめに
前回まで、5回にわたり Web Worker の使い方について、説明を行ってきました。
その中で、Web Worker が「重たい処理や同期的な API 」を担うことで、UI への影響を防ぐことができるものであるということも説明してきました。
「重たい処理や同期的な API 」といった時に、昨今、それらの処理を一から作るというよりは、圧倒的に外部ライブラリを利用することが多いと思います。
ここで、1つ疑問がわきます。
Web Worker は、スクリプトしか存在せず、Window や document を持たないスレッドです。
つまり、HTML を持たないので、外部ライブラリを読み込みたい時に <script> タグが使用できません。
では、どーやって、外部モジュールを読み込むのでしょうか?
今回は、その点を深堀して説明していきたいと思います。
外部モジュールの形式について
現在、JavaScriptで主に使われる外部モジュール形式の配布では、下記の異なるタイプのものを平行して配布するという形が主流になっています。
- UMD 配布版
- ESM 配布版
UMD 配布版は、ES6 以前から存在するモジュールシステムに対応したモジュール形式で、ESM 配布版は、ES6 で追加された ES モジュールに対応したモジュール形態になります。
■ UMD(Universal Module Definition)とは?
複数のモジュールシステムに対応させるための、JavaScript の書き方のパターンです。ライブラリの仕組みというより、配布形式の工夫という方が正しいです。
2010年代前半、JavaScript には複数のモジュール読み込み方式が乱立していました。
- CommonJS(Node.js)
例:require() / module.exports
- AMD(RequireJSなど)
例:define()
- グローバル変数(
<script>タグ)
例:window.JSZip = ...
ライブラリ作者はこの全部に対応したいというニーズがあり、その解決策として生まれたのが UMD です。
典型的な構造
javascript(function (root, factory) {
if (typeof define === "function" && define.amd) {
// AMD環境
define([], factory);
} else if (typeof module === "object" && module.exports) {
// CommonJS環境
module.exports = factory();
} else {
// どちらでもない → グローバル変数に代入
root.JSZip = factory();
}
})(typeof self !== "undefined" ? self : this, function () {
// ライブラリ本体
return JSZip;
});
実行時に typeof define や typeof module で環境を判定し、それぞれに合った方法でエクスポートします。
※ UMD の3つの分岐のうち、AMD環境については、 そのメインローダーであった RequireJS の開発が既に開発終了となってしまっている関係から、今ではほぼ互換性のために残されています。
■ ESM ( ES モジュール)とは?
ES6 で追加されたモジュール形式です。export で公開されたモジュールは、type='module' として読み込まれた JavsScript の中から import で読み込むことが可能です。
import / export の詳細については、過去記事、「モダンJavaScript入門/importとexportの基本」を参照してください。
■ なぜ、UMD 配布版と ESM 配布版 の2種類が併存するのか?
import / export は静的解析を前提とした構文です。
一方、UMD は実行して初めて、どのモジュールシステムかを判定する表記方法です。
その為、「実行時にtypeof defineなどで分岐する」という UMD 配布版の動的な仕組みと、静的解析を前提とする ESM の根本的な設計思想の違いが、両者を1つにできない技術的な理由となっています。
Web Workerで外部ライブラリを使う方法
ここまでで、JavaScript で取り扱われているモジュールには、複数のタイプが存在し、大きく UMD 配布版と ESM 配布版の2つにわけられるという話をしてきました。
現在の Web Worker も、これらの2つのモジュール形式の読み込みに対応して2つのタイプの Web Worker に分けられます。
正確には、起動方法により、Web Worker は異なるタイプとして起動されるので、それぞれにおいて、使える読み込み方法が異なるというのが実態です。
-
classic worker
- Web Worker 追加当時より使用されてきた Web Worker タイプ。
- UMD 版モジュールの読み込み可能。
-
module worker
- ES6 による ES モジュール追加以降に追加された Web Worker タイプ。
- ESM 版モジュールの読み込み可能。
module worker の方が比較的新しいことや classic worker という名前から、古い廃れた技術のように感じられるかもしれませんが、それは間違いです。
これらは、読み込みたいライブラリの配布形式によって使い分けられており、どちらも現役の技術です。
※ 注意すべきは、classic worker での ESM 配布版モジュールの読み込みや、module worker での UMD 配布版モジュールの読み込みはエラーとなることです。1つの Web Worker 内で複数の外部ライブラリを読み込みたい場合、すべてのモジュール形式をどちらかに統一することが重要です。
classic worker
Web Worker が追加された当時から使用できる標準の Web Worker のタイプです。
前回までの説明で使用した、
const worker = new Worker("worker.js");
で、起動した Web Worker は全て、classic worker として起動されます。
外部ライブラリの読み込みには、 importScripts() 関数が使用できます。
■ importScripts() とは?
importScripts() 関数は、Web Worker 専用のグローバル関数で、外部の JavaScript ファイルを同期的に読み込む機能を提供します。
importScripts("https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js");
self.onmessage = async (e) => {
const zip = new JSZip(); // グローバルに生えたJSZipがそのまま使える
// ...
};
複数ファイルを一度に渡すこともできます。
importScripts("file1.js", "file2.js", "file3.js");
// 書いた順番通りに、1つずつ同期的に読み込まれる
イメージとしては、HTML上で、<script> タグで読み込む感覚に近い方法です。
importScripts() 関数で読み込まれたコードは、<script>タグでの読み込みと同様に「グローバルスコープに変数を生やす」形式を前提としています。
その為、多くのライブラリの CDN 配布版は、実行環境を判定してグローバル変数に代入する分岐を持っているため、importScripts() 関数との組み合わせがそのまま機能します。
// UMD形式のライブラリ内部(イメージ)
(function (root, factory) {
if (typeof module === "object" && module.exports) {
module.exports = factory(); // CommonJS
:
} else {
root.JSZip = factory(); // グローバル変数(importScriptsはここを通る)
}
})(self, function () { /* ライブラリ本体 */ });
👉 ライブラリ本体は、グローバル変数に登録されます。
■ 特徴
- classic worker 内でしか使えない(
<script>タグの Web Worker 版というイメージ) - 呼び出した瞬間、読み込みと実行が完了するまで処理をブロックする(同期的)
- 読み込みに失敗すると
NetworkError例外が即座にスローされる - Web Worker 誕生時(2009年)の最初期の仕様から存在する、歴史の長いAPI
module worker
ES6 での ES モジュール追加後、ブラウザ側の実装を経て使えるようになった Web Worker です。
(実際にブラウザが対応し始めたのは、Chrome / Edge は2020年、Safari は2021年、Firefox は2023年と、ES6 の標準化から数年後のことです。)
起動時に { type: "module" } をパラメータとして指定することで、生成されます。
// メインスレッド側
const worker = new Worker("worker.js", { type: "module" }); // ここがポイント
外部ライブラリの読み込みには、 import 文が使用できます。
※ スクリプトとしては、<script type="module"> で読み込まれたモジュールスクリプトと同一の挙動となり、CORS 制約も受けます。詳細につては、「モダンJavaScript入門/モジュールスクリプトの基本」を参照してください。
// worker.js(モジュールワーカー)
import JSZip from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
self.onmessage = async (e) => {
const zip = new JSZip();
// ...
};
■ 特徴
- 通常の
<script type="module">と同じ感覚でimport文が書ける - 静的解析可能で、ビルドツールとの相性も良い
- Chrome / Edge は2020年(バージョン80)から、Safari はバージョン15から、Firefox はバージョン114(2023年)からと対応時期にばらつきがあった
- 現在(2026年時点)は主要ブラウザすべてで安定してサポートされている
どちらを選ぶべきか
classic worker と module worker の外部ライブラリの読み込みの違いは下記のとおりです。
| 観点 | classic worker | module worker |
|---|---|---|
| 対象ライブラリ | UMD 配布版 | ESM 配布版 |
| Web Worker の起動 | シンプル、追加設定不要 | { type: "module" } パラメータの指定が必須 |
| 読み込み構文 | importScripts() 関数 |
import 文 |
| 読み込み | 同期的(ブロッキング) | 非同期 |
| ブラウザ対応 | 非常に古くから安定 | 現在はほぼ問題なし(以前はFirefoxが遅れていた) |
👉 判断基準はシンプルで、使いたいライブラリがどちらの形式で配布されているかで決まります。
CDN 配布の UMD 版ライブラリ( jszip.min.js など)なら classic worker 、ESM 専用パッケージなら module worker、という選び方になります。
まとめ
- Web Worker 内での外部コード読み込みは、Web Worker の起動方法によるモジュールタイプにより決まる。
- classic worker の場合は、
importScripts()関数で、UMD 配布版の外部モジュールを読み込む。 - module worker の場合は、
import文で、ESM 配布版の外部モジュールを読み込む。 - classic worker と module worker のどちらを使用するか迷った場合、読み込みたいライブラリの配布形式( UMD 配布版 or ESM 配布版)を確認する。
- バンドラーを使わないシンプルな構成であれば、Web Worker を通常の classic worker として起動し、UMD 配布版のライブラリを
importScripts()で読み込むのが最も手軽