Articles

モジュールワーカー

を使用してwebをスレッド化するJavaScriptはシングルスレッドであるため、一度に一つの操作しか実行できません。 これは直感的で、web上の多くのケースでうまく機能しますが、データ処理、解析、計算、分析などの重い作業を行う必要がある場合に問題になる可能性があ Web上でより複雑なアプリケーションが配信されるにつれて、マルチスレッド処理の必要性が高まっています。

webプラットフォームでは、スレッドと並列処理の主なプリミティブはWebワーカー APIです。 ワーカーは、スレッド間通信のためのメッセージパッシングAPIを公開するオペレーティングシステムのスレッドの上にある軽量な抽象化です。 これは、コストのかかる計算を実行したり、大規模なデータセットで操作したりする場合に非常に便利で、メインスレッドをスムーズに実行しながら、1つ以上のバックグラウンドスレッドでコストのかかる操作を実行することができます。

ワーカースクリプトがメインスレッドからのメッセージをリッスンし、独自のメッセージを送り返すことによって応答するワーカー使用の典型的な例は次のとおりです。

ページ。js:

const worker = new Worker('worker.js');
worker.addEventListener(e => {
console.log(e.data);
});
worker.postMessage('hello');

ワーカー。js:

addEventListener('message', e => {
if (e.data === 'hello') {
postMessage('world');
}
});

Web Worker APIは、十年以上にわたってほとんどのブラウザで利用可能でした。 これは、ワーカーが優れたブラウザサポートを持ち、適切に最適化されていることを意味しますが、JavaScriptモジュールよりも長い間前に存在していたことを意 ワーカーが設計されたときにはモジュールシステムがなかったため、ワーカーにコードをロードしてスクリプトを構成するためのAPIは、2009年に一般的な同期スクリプトローディングアプローチと同様のままであった。

History:classic worker#

ワーカーコンストラクタは、ドキュメントURLに相対的なclassicスクリプトURLを取ります。 新しいワーカーインスタンスへの参照をすぐに返し、メッセージングインターフェイスと、ワーカーをすぐに停止して破棄するterminate()メソッドを公開します。

const worker = new Worker('worker.js');

importScripts()関数は、追加のコードをロードするためにwebワーカー内で利用可能ですが、各スクリプトをフェッチして評価するためにワーカー また、古典的な<script>タグのようなグローバルスコープでスクリプトを実行します。

労働者。js:

importScripts('greet.js');
// ^ could block for seconds
addEventListener('message', e => {
postMessage(sayHello());
});

挨拶します。js:

// global to the whole worker
function sayHello() {
return 'world';
}

このため、webワーカーは歴史的にアプリケーションのアーキテクチャに大きな影響を与えてきました。 開発者は、最新の開発プラクティスをあきらめずにwebワーカーを使用できるようにするために、巧妙なツールと回避策を作成する必要がありました。 例として、webpackのようなバンドラは、コードのロードにimportScripts()を使用する生成されたコードに小さなモジュールローダーの実装を埋め込みますが、変数の衝突を避け、依存性のインポートとエクスポートをシミュレートするためにモジュールを関数でラップします。

Enter module worker#

JavaScriptモジュールの人間工学とパフォーマンスの利点を持つwebワーカーのための新しいモードは、module workerと呼ばれるChrome80で出荷されています。 Worker{type:"module"}<script type="module">に一致するように変更

const worker = new Worker('worker.js', {
type: 'module'
});

モジュールワーカーは標準のJavaScriptモジュールなので、importステートメントとexportステートメントを使用できます。 すべてのJavaScriptモジュールと同様に、依存関係は特定のコンテキスト(メインスレッド、ワーカーなど)で一度だけ実行されます。)、および将来のすべてのインポートは、既に実行されたモジュールインスタンスを参照します。 JavaScriptモジュールの読み込みと実行は、ブラウザによっても最適化されています。 モジュールの依存関係は、モジュールが実行される前にロードすることができ、モジュールツリー全体を並列にロードすることができます。 つまり、メインスレッドで使用され、ワーカーで使用されるモジュールは一度だけ解析する必要があります。

JavaScriptモジュールに移動すると、ワーカーの実行をブロックすることなく、コードを遅延ロードするための動的インポートの使用も可能になります。 動的インポートは、グローバル変数に依存するのではなく、インポートされたモジュールのエクスポートが返されるため、依存関係をロードするためにimportScripts()

労働者。js:

import { sayHello } from './greet.js';
addEventListener('message', e => {
postMessage(sayHello());
});

挨拶します。js:

import greetings from './data.js';
export function sayHello() {
return greetings.hello;
}

優れたパフォーマンスを確保するために、古いimportScripts()メソッドはモジュールワーカー内では使用できません。 ワーカーをJavaScriptモジュールを使用するように切り替えると、すべてのコードがstrictモードでロードされます。 もう1つの注目すべき変更点は、JavaScriptモジュールのトップレベルスコープのthisundefinedであるのに対し、classicワーカーでは値がワーカーのグローバルスコープであることです。 幸いなことに、グローバルスコープへの参照を提供するselfglobalが常にありました。 DOMだけでなく、サービスワーカーを含むすべてのタイプのワーカーで利用できます。

モジュールワーカーは、HTMLスタイルのコメントのサポートも削除します。 WebワーカースクリプトでHTMLコメントを使用できることを知っていましたか?

Modulepreload#を使用したワーカーのプリロード

モジュールワーカーに伴うパフォーマンスの大幅な改善の一つは、ワーカーとその依存関係をプリロードする機能です。 つまり、modulepreload:

<!-- preloads worker.js and its dependencies: -->
<link rel="modulepreload" href="worker.js">
<script>
addEventListener('load', () => {
// our worker code is likely already parsed and ready to execute!
const worker = new Worker('worker.js', { type: 'module' });
});
</script>

プリロードされたモジュールは、メインスレッドとモジュールワーカーの両方で使用することもできます。 これは、両方のコンテキストでインポートされるモジュールや、モジュールがメインスレッドで使用されるのかワーカーで使用されるのかを事前に知ることができない場合に便利です。

以前は、webワーカースクリプトのプリロードに使用できるオプションは限られており、必ずしも信頼できるとは限りませんでした。 クラシックワーカーにはプリロード用の独自の”ワーカー”リソースタイプがありましたが、<link rel="preload" as="worker">を実装したブラウザはありません。 その結果、webワーカーのプリロードに使用できる主な手法は、HTTPキャッシュに完全に依存する<link rel="prefetch">modulepreloadとは異なり、この手法は依存関係のプリロードや事前解析をサポートしていませんでした。

シェアードワーカーはどうですか? #

共有ワーカーは、Chrome83の時点でJavaScriptモジュールをサポートして更新されました。 専用ワーカーと同様に、{type:"module"}オプションを使用して共有ワーカーを構築すると、従来のスクリプトではなくモジュールとしてワーカースクリプトがロードされるようになりました。

const worker = new SharedWorker('/worker.js', {
type: 'module'
});

JavaScriptモジュールがサポートされる前は、SharedWorker()コンストラクタはURLとオプションのみを必要としましたnameoptionsnamenameオプションなど、専用のワーカーのオプションと同じです。

サービスワーカーはどうですか? #

service workerの仕様は、Module workerと同じ{type:"module"}オプションを使用して、JavaScriptモジュールをエントリポイントとして受け入れることをサポートするように既に更新されていますが、この変更はブラウザではまだ実装されていません。 それが起こると、次のコードを使用してJavaScriptモジュールを使用してservice workerをインスタンス化することができます。

navigator.serviceWorker.register('/sw.js', {
type: 'module'
});

仕様が更新されたので、ブラウ Javascriptモジュールをservice workerに持ち込むことに関連するいくつかの余分な複雑さがあるため、これには時間がかかります。 Service workerの登録は、更新をトリガーするかどうかを決定するときに、インポートされたスクリプトを以前のキャッシュされたバージョンと比較する必要があり、Service workerに使用する場合はJavaScriptモジュールに実装する必要があります。 また、サービスワーカーは、更新をチェックするときに、特定のケースでスクリプトのキャッシュをバイパスできる必要があります。