1. 導入
Webアプリケーション開発において、「複数のブラウザタブやウィンドウで同じ状態を同期したい」という要件は頻繁に発生します。LocalStorageのstorageイベントやBroadcast Channel APIで解決できるケースもありますが、これらは「データの永続化」や「単純なメッセージ送信」には向いていても、複雑なロジックの共有や計算結果のキャッシュには不向きです。SharedWorkerを活用すれば、バックグラウンドで独立したスクリプトを動かし、複数のタブ間でメモリ状態を共有・同期することが可能になります。これにより、リソースの無駄な消費を抑え、アプリのパフォーマンスを劇的に向上させることができます。
2. 基礎知識
SharedWorkerは、Web Workerの一種です。通常のWorkerが「1つのタブにつき1つのWorker」と紐付くのに対し、SharedWorkerは「同じオリジン(ドメイン・プロトコル・ポート)であれば、複数のタブから同じWorkerインスタンスに接続」できます。
仕組みとしては、Workerがメインスレッドとは別のプロセスで動作し、各タブからの接続(ポート)を管理します。メッセージングを通じてデータのやり取りを行うため、メインスレッドのUIレンダリングをブロックすることなく、重い計算やWebSocket接続の管理などを一箇所に集約できるのが最大の強みです。
3. 実装/解決策
SharedWorkerを実装するには、専用のJSファイルを用意し、メインスレッド側でインスタンス化してポート接続を確立します。
手順は以下の通りです。
1. SharedWorker用のスクリプトを作成する。
2. onconnectイベントで各タブからの接続を待ち受け、ポートを保持する。
3. ポートを通じてメッセージを相互送受信する。
4. サンプルプログラム
まずはSharedWorker用のファイル(shared-worker.js)を作成します。
// shared-worker.js
// 接続されたポートのリストを保持
const ports = [];
self.onconnect = (e) => {
const port = e.ports[0];
ports.push(port);
port.onmessage = (event) => {
// 全ての接続済みタブに対してメッセージをブロードキャストする
ports.forEach((p) => {
p.postMessage({
sender: ‘SharedWorker’,
data: `受信したデータ: ${event.data.message}`,
timestamp: new Date().toLocaleTimeString()
});
});
};
port.start();
};
次に、メインスレッド(index.js)で呼び出します。
// index.js
const worker = new SharedWorker(‘shared-worker.js’);
// 接続の確立
worker.port.start();
// メッセージの送信
worker.port.postMessage({ message: ‘こんにちは、SharedWorker!’ });
// メッセージの受信
worker.port.onmessage = (e) => {
console.log(‘Workerからの応答:’, e.data);
};
5. 応用・注意点
SharedWorkerを実務で扱う際の注意点がいくつかあります。
まず、デバッグの難しさです。Chromeであれば「chrome://inspect/#workers」からデバッグ画面を開く必要がありますが、複数タブが絡むため挙動の追跡が複雑になりがちです。
次に、ブラウザサポートです。Safariでのサポートは限定的であり、現時点ではFirefoxでもデフォルトで無効化されている場合があります。重要なロジックをSharedWorkerだけに依存させるのではなく、フォールバック(Broadcast Channel APIなど)を併用するか、ブラウザの対応状況を確認することが不可欠です。
また、SharedWorkerはリロードしても存続する場合があるため、状態のリセット処理を適切に実装しないと、古いデータが残り続けるバグが発生しやすくなります。接続が切れた際のクリーンアップ処理をしっかり書くことが、堅牢なフロントエンド構築の鍵となります。