Open8

esm.shのworker対応について調べる

hashrockhashrock

esm.shに対象のスクリプトをworker化する、という機能がある。
Workerをまったく勉強していなかったのと、現在追っている不具合に関連しているかも、ということで少し使ってみる。

hashrockhashrock

一番簡単なスクリプトとして、math-sumをworker化してみる。

https://www.npmjs.com/package/math-sum

これをesm.shでbundleしたものは下記。

https://esm.sh/v135/math-sum@2.0.0/es2022/math-sum.bundle.mjs

これをworker化するとこうなる。

https://esm.sh/v135/math-sum@2.0.0/es2022/math-sum.bundle.mjs?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" });
}
hashrockhashrock

何故にわざわざラップしてblobにするのか。

似たようなことをすると思われるesbuildのpluginの説明文から。

https://github.com/mitschabaude/esbuild-plugin-inline-worker

便利なことに、ワーカーのJavaScriptファイルをサーバーの正しい場所に配置する必要はありません。代わりに、esbuildによって生成されたバンドルにワーカーのJSコードがインラインで埋め込まれます。このプラグインは、パフォーマンスの最適化のためにワーカーを使用したいJSライブラリの作者に最適です。別々のワーカーファイルが不便な状況で特に便利です。

どうやらCORSの迂回ができるという側面がある模様。

hashrockhashrock

もう一点は、イベントリスナの設定を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>
hashrockhashrock

workerのデバッグ方法。DevToolからは下記のように見える。ブレークポイントも置ける。

hashrockhashrock

esm.shのworker対応を使ったときは、Blobの内容を確認できる。

hashrockhashrock

ちなみに、下記のコメントにあるように、CSPのworker-srcではBlobの実行を * では許可できず、 blob: スキームを明示的に許可する必要がある。

https://stackoverflow.com/questions/63119217/csp-worker-src-blob-security#comment111627151_63119217

特定の一意のコンテンツを参照する特別なURLスキーム(例: "data:", "blob:"、"filesystem:")は、*のポリシーと一致しないように除外され、明示的にリストされる必要があります。ポリシーの作成者は、そのようなURLのコンテンツが通常、レスポンスボディから派生するか、ドキュメントコンテキストで実行される可能性があることに注意すべきです。特に、default-srcおよびscript-srcディレクティブに関して、"data:" URLを許可することはunsafe-inlineと同等であり、"blob:"や"filesystem:" URLを許可することはunsafe-evalと同等であることをポリシー作成者は認識すべきです。

hashrockhashrock

…というわけで、ここまでやってくれているesm.shと同等のことをやるには、

  • workerFactoryと同等のことをするスクリプトを同じoriginに設置(injectされたコードがあればそれもスクリプト内に追加)
  • そこから参照するスクリプトをesbuildでバンドル

…となる。またonerrorの設定も必要だろう。ここまででスタートライン。