負担のかかる処理、例えば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/"
},
この中のfilename
とpublicPath
を設定したらうまくいった。
後は、
ワーカーを呼び出す側のコード
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));
それぞれonmessage
やonerror
はワーカー側から送信されてくるデータおよびエラー(例外)です。
ワーカーを開始するには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");
計算結果を呼び出し元へ送るといいですが今回はサンプルのため手抜きです。
負荷のかかる処理をワーカー側へ実行することで呼び出し元(ブラウザ)がフリーズすることを防げます。