pixelart-scaler 制作の過程で並列処理を利用することになり、そのときにかなりハマってたので書きます。
ドット絵スケーラー (PixelArt-Scaler) は、主なピクセルアート用フィルターによる小さい画像のスケーリング拡大がWeb上で出来ます。遊んでみてください。
本題
javascript 上で並列処理をする場合 WebWorkers API が利用できます。 メインで使うスクリプトとは別ファイルの js を読み込んで、その内容を別スレッドで実行することができます。const worker = new Worker('foo.js');
で読み込むことができます。
フレームワークを利用しない場合は、上記ドキュメントの方法で利用できます。 しかしフレームワークを利用している場合、直接ディレクトリの js を利用しようとすると static ファイルしか拾えなくなります。そこで import を利用するのですが、 lint や TypeScript では型定義あたりでエラーを吐き出します。
目次
フレームワーク上で利用する場合は worker-loader モジュールを利用すると良いでしょう。 このように、readme に沿って設定をすれば使えるようになりました。
使えるようになったんですが…並列処理側に書いた js の処理が行われません。 コンソールで見てみると、スレッドは立ち上がっているものの、その内容が実行されていないようでした。 Worker 内部では window でなく、DedicatedWorkerGlobalScope というグローバルコンテキストが動きます。それによって しかしながら、フレームワーク利用時は グローバルコンテキストのエラーは解決したものの、まだ内容が実行されない問題が残っています。
worker-loader の readme 通りでは動かないため、import で読み込むファイル文字列を Typescript 用に書いた declare 定義のものにせず、直接ファイル名を利用すると内容が実行されるようになりました。 しかし新たな問題。declare の定義を外したため、default export の定義が無いエラーが発生します。開発環境では動くものの、ビルド時はエラーが置きているのでビルド出来ません…。 readme の declare で定義していたクラス宣言を default export に直接追加します。このときのコンストラクタは継承元の super() にします。これで import 時のエラーは発生しなくなりました。よって declare を定義した .ts ファイルも必要がなくなるので削除して問題ありません。 今回は nuxtjs + Typescript の環境の問題であったため、他の環境では worker-loader の readme どおりで動く場合もあるでしょう。他のフレームワークでも同じような問題があった場合は今回の解決法も役立つかもしれません。worker-loader を利用する
// nuxt.config
{
build: {
extend(config) {
config.module.rules.push({
test: /\.worker\.ts$/,
use: {
loader: 'worker-loader',
},
})
}
}
// custom.d.ts
declare module "worker-loader!*" {
class WebpackWorker extends Worker {
constructor();
}
export default WebpackWorker;
}
// App.vue
<script lang="ts">
import Vue form 'vue'
import Worker from "worker-loader!./Worker";
const worker = new Worker();
export default Vue.extend ({
// ...
methods: {
work() {
worker.postMessage({ a: 1 });
worker.onmessage = (event) => {};
}
}
)}
</script>
// Worker.ts
const ctx: Worker = self as any;
// Post data to parent thread
ctx.postMessage({ foo: "foo" });
// Respond to message from parent thread
ctx.addEventListener("message", (event) => console.log(event));
グローバルコンテキストのエラー
postMessage
や onMessage
をそのまま利用できます。
Worker 内部では自身を表すのは this ではなく、DedicatedWorkerGlobalScope の self
を利用します。self
も定義されていないというエラーが起こるかと思います。
これについては ビルドオプションに config.output.globalObject = 'this'
を追加することで解決します。
注意したいのは、設定は this、利用は self です。// nuxt.config
{
build: {
extend(config) {
config.module.rules.push({
test: /\.worker\.ts$/,
use: {
loader: 'worker-loader',
},
})
config.output.globalObject = 'this' // 追加
}
}
内容が実行されない問題
default export 問題の解決
// Worker.ts
const ctx: Worker = self as any;
// Post data to parent thread
ctx.postMessage({ foo: "foo" });
// Respond to message from parent thread
ctx.addEventListener("message", (event) => console.log(event));
export default class WebpackWorker extends Worker {
constructor() {
super('')
}
}
扱いの注意
new Worker()
するたびにスレッドが増えてしまうので、Worker を利用する場合は、グローバルに変数を宣言し、mounted
でインスタンス化して destroyed
で削除するのが良いでしょう。// Test.vue
<script lang="ts">
import Vue form 'vue'
import Worker from "./Worker"
const worker: Worker
export default Vue.extend ({
// ...
mounted() {
worker = new Worker()
},
destroyed() {
worker.terminate()
},
methods: {
work() {
worker.postMessage({ a: 1 })
worker.onmessage = (event) => {}
}
}
)}
</script>
おわりに