TypeScript + WebPack5 でWebWorkerを使う。

負担のかかる処理、例えば10秒ほどかかる処理を実行するとブラウザが固まってしまいます。
そこで負担のかかる処理を分散するマルチスレッドという機能がJavaやC#などには備わっているのですが・・・。
どうやらJavaScriptにはない、代わりにWebWorkerがあるのですがメモリ空間が共有出来ない?
どちらかというと別プロセスで動かす感じです。

サンプルを見ると

new Worker(worker.js);

といったように別ファイルを渡す感じになってます。

これだとTypeScriptの場合は、またWebPackで一つにバンドルする場合はどうすればいいのか戸惑います。
調べるとworker-loaderというのがあるようですがこれでまずハマります。
どうやらWebPack4までのやつでWebPack5からは別の方法があるようです。というかローダー不要なようです。

new Worker(new URL('./workers/test.worker.ts', import.meta.url));

WebPack5ではたったこれだけで実現できるようですが、

main.js:2 Uncaught Error: Automatic publicPath is not supported in this browser
    at main.js:2:137319
    at main.js:2:137461
    at main.js:2:163794

というエラーが出てきました。
これにものすごく手間がかかってしまいます。

ちょっとpublicPathの意味が分からない、と思って軽くググったら
デプロイしたときのファイルがあるURL?

いろいろ試していたら成功したので実際にコード書いていきます。

webpack.config.js

const path = require("path");
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");

const isProduction = process.env.NODE_ENV == "production";

const stylesHandler = "style-loader";

const config = {
  entry: "./src/app.ts",
  output: {
    path: path.resolve(__dirname, "out"),
    filename: "[name].js",
    publicPath: "http://127.0.0.1:5500/out/"
  },
  plugins: [
    // Add your plugins here
    // Learn more about plugins from https://webpack.js.org/configuration/plugins/
  ],
  module: {

    rules: [
      {
        test: /\.(ts|tsx)$/i,
        loader: "ts-loader",
        exclude: ["/node_modules/"],
      },
      {
        test: /\.scss$/i,
        use: [
          "style-loader",
          "css-loader",
          "sass-loader"
        ]
      },
      // 省略・・・

    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
};

module.exports = () => {
  if (isProduction) {
    config.mode = "production";

    config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
  } else {
    config.mode = "development";
  }
  return config;
};

この中の

  output: {
    path: path.resolve(__dirname, "out"),
    filename: "[name].js",
    publicPath: "http://127.0.0.1:5500/out/"
  },

この中のfilenamepublicPathを設定したらうまくいった。
後は、

ワーカーを呼び出す側のコード

const worker = new Worker(new URL('./workers/test.worker.ts', import.meta.url));

worker.onmessage = e =>
{
    console.log("onmessage: " + e.data);
}

worker.onerror = e =>
{
    console.log("ERR = " + e);
}

// ワーカー開始
worker.postMessage("Go Worker!");

ワーカーのインスタンスの作成。test.worker.tsがワーカーのファイル名だとすると

const worker = new Worker(new URL('./workers/test.worker.ts', import.meta.url));

それぞれonmessageonerrorはワーカー側から送信されてくるデータおよびエラー(例外)です。
ワーカーを開始するにはworker.postMessage()を実行します。
引数でワーカーへデータを渡せますが、オブジェクトを渡す場合は注意が必要です(メソッドなどは渡らない)。
解決方法があるのかどうかはわかりません。

ワーカー側のコード

ワーカーのファイルtest.worker.tsの中身

const ctx: Worker = self as any;

// 引数値のミリ秒、ループし続ける処理
function sleep(interval: number): void
{
    const s = new Date().getTime();
    while(true)
    {
        const ts = new Date().getTime() - s;

        if(ts >= interval)
        {
            return;
        }
    }
}

ctx.onmessage = e => {

    ctx.postMessage("WorkerStart: " + e.data);

    for(let i=0; i<10; i++)
    {
        sleep(1000);
        const progress = (i + 1) / 10 * 100;
        ctx.postMessage(`${progress} %`);
    }

    ctx.postMessage("WorkerEnd");
};

sleep(1000)メソッドで大体1秒ほどの負荷をかけます。
それを10回呼び出します。

ctx.postMessage()は呼び出し元のonmessageイベントで拾われます。
1秒ごとに進捗割合を呼び出し元へ送ってます。

最後に、

ctx.postMessage("WorkerEnd");

計算結果を呼び出し元へ送るといいですが今回はサンプルのため手抜きです。

負荷のかかる処理をワーカー側へ実行することで呼び出し元(ブラウザ)がフリーズすることを防げます。

BlockEditor certificate css DataGrid Docker Gutenberg Hyper-V iframe MUI openssl PHP React ReduxToolkit REST ubuntu WordPress オレオレ認証局 フレームワーク