esm.shのworker対応について調べる
esm.shに対象のスクリプトをworker化する、という機能がある。
Workerをまったく勉強していなかったのと、現在追っている不具合に関連しているかも、ということで少し使ってみる。
一番簡単なスクリプトとして、math-sumをworker化してみる。
これをesm.shでbundleしたものは下記。
これをworker化するとこうなる。
これにprettierをかけるとこうなる。
export default function workerFactory(inject) {
const blob = new Blob(
[
"/* esm.sh - esbuild bundle(math-sum@2.0.0) es2022 production */\nfunction u(...r){let t=Array.isArray(r[0])?r[0]:r,o=0;for(let n of t)o+=n;return o}export{u as default};\n",
typeof inject === "string" ? "\n// inject\n" + inject : "",
],
{ type: "application/javascript" }
);
return new Worker(URL.createObjectURL(blob), { type: "module" });
}
何故にわざわざラップしてblobにするのか。
似たようなことをすると思われるesbuildのpluginの説明文から。
便利なことに、ワーカーのJavaScriptファイルをサーバーの正しい場所に配置する必要はありません。代わりに、esbuildによって生成されたバンドルにワーカーのJSコードがインラインで埋め込まれます。このプラグインは、パフォーマンスの最適化のためにワーカーを使用したいJSライブラリの作者に最適です。別々のワーカーファイルが不便な状況で特に便利です。
どうやらCORSの迂回ができるという側面がある模様。
もう一点は、イベントリスナの設定をinjectできる機能があり、Worker向けに作られていないコードをWorkerにそのまま転用できるようだ。
例えばこういうコード:
<!DOCTYPE html>
<html lang="en">
<body>
<script type="module">
import workerFactory from "https://esm.sh/v135/math-sum@2.0.0/es2022/math-sum.bundle.mjs?worker";
const workerAddon = `
self.onmessage = function (e) {
const r = u(1, 1)
console.log(e.data)
self.postMessage(r)
}
self.onerror = function (e) {
console.error(e)
}
`;
const worker = workerFactory(workerAddon);
worker.postMessage("Hello, world");
worker.addEventListener("message", (e) => {
console.log("Workerから受け取ったデータは: ", e.data);
});
</script>
</body>
</html>
workerのデバッグ方法。DevToolからは下記のように見える。ブレークポイントも置ける。
esm.shのworker対応を使ったときは、Blobの内容を確認できる。
ちなみに、下記のコメントにあるように、CSPのworker-src
ではBlobの実行を *
では許可できず、 blob:
スキームを明示的に許可する必要がある。
特定の一意のコンテンツを参照する特別なURLスキーム(例: "data:", "blob:"、"filesystem:")は、*のポリシーと一致しないように除外され、明示的にリストされる必要があります。ポリシーの作成者は、そのようなURLのコンテンツが通常、レスポンスボディから派生するか、ドキュメントコンテキストで実行される可能性があることに注意すべきです。特に、default-srcおよびscript-srcディレクティブに関して、"data:" URLを許可することはunsafe-inlineと同等であり、"blob:"や"filesystem:" URLを許可することはunsafe-evalと同等であることをポリシー作成者は認識すべきです。
…というわけで、ここまでやってくれているesm.shと同等のことをやるには、
- workerFactoryと同等のことをするスクリプトを同じoriginに設置(injectされたコードがあればそれもスクリプト内に追加)
- そこから参照するスクリプトをesbuildでバンドル
…となる。またonerrorの設定も必要だろう。ここまででスタートライン。