XLSX-Renderer and Comlink

これの続きです。
備忘録です。


JSONデータをPHPで作成し(PHPじゃなくてもいいんだけど。。。)、クライアント・ブラウザ側のJavaScriptでエクセルファイルを作成するまではできました。
ただ、問題なのは、取得するJSONデータが結構大きいサイズ(例えば10万件とか)になると、それはそれで厄介です。

なんせ、JSONデータをfetchするだけでそれだけで時間がかかりますし、そのデータをエクセルファイルに差し込んでエクセルファイルを作成する時にも結構な時間がかかります。
その間、「データを取得中・・・エクセルファイルを生成中・・・」とかでUIを止めてもいいのですが・・・それはイマドキのトレンドではありませんよね。

せっかくブラウザがワーカースレッドを使えるようにしてくれているので、別スレッドにそういう処理は任してしまいましょう。
そう! 我々にはグーグル様が書いてくれたComlink がある!(笑)

今回は、サンプルとして↓のように、郵便番号のデータベース(SQLite)から、リクエストに応じてJSONデータを返すPHPで書いたウェブAPIを使用し、そのデータをエクセルファイルに変換するまでを書いてみる。


まずは、ウェブAPIですが・・・これはサクッと書いてしまいましょう。
パラメータ”q”に郵便番号の先頭数文字を指定して前方一致で検索してJSONとしてレスポンスを返します。
郵便番号辞書の元になるデータは日本郵便サイトからCSVファイルが公開されているのでそれを使用しSQLiteに変換して使用しました。

次に、エクセルファイルを作成する部分のJavaScriptコードです。
以前にも書きましたが、この部分はnode.jsとnpm, babel.jsとかが必要です。この部分が全く分からなければこの先見てもワケワカンネー状態になるので、とっととブラウザを閉じてこのサイトの事は忘れてくださいm(__)m

作業ディレクトリを一個作って、以下のpackage.jsonとrenderer.jsを作成。

>> tree .
.
├── package.json
└── renderer.js

※ 2022/02/06 rendererをDedicatedWorkerGlobalScopeに追加するようにrenderer.js を若干修正しました。

で、下記を実行して renderer.pack.min.js が生成されるので、このファイルを workerスレッド内からimportScripts関数でロードします。

>> npm i
>> npm run bundle

これで準備完了です。
ここからは、実際に画面(HTML)とメインスレッドのjs、ワーカースレッドのjsを書いていきます。
例によって Web Workerを利用する際には、Comlink を使ってラクします。

HTML等はデモ画面を参照。

まずは、取得したJSONデータをExcelに差し込むための、テンプレートとなるエクセルファイルを作成します。
フツーにエクセルで作成していきます。Microsoft Officeを持っていない場合は、LibreとかオープンソースのOfficeアプリとかでも構いません。
.xlsx形式のファイルを出力できる表計算アプリであれば何でもいいと思います。

こんな感じです。テンプレート XLSXファイル

これらの書式等は、XLSX-Rendererのドキュメント参照。

次にメインスレッド側のJavaScriptコードです。
特筆すべき点はありません。見ればだいたい分かるでせう。
ワーカースレッドのやり取りに Comlink を使ってます。

このファイルをロードする際は、type属性を text/javascript 等ではなく、module にしないとなんかエラーになるみたいです。
そしてボタンのクリックハンドラを設定します。(ここだけjQueryを使用しています)

最後はワーカースレッド側のJavaScriptコードです。
Comlinkを使って、メインスレッド側に関数を公開しています。

※ 2022/02/06 若干修正

このファイル内で、上記で準備した renderer.pack.min.js をロードします。


上記デモページ


たぶんこの程度の処理なら、わざわざワーカースレッドを起こさなくても、いいかとも思います。実際問題、複雑になるだけですし。
メインスレッドはUI専用にして、雑多な処理を全部 workerスレッドに投げるようにすれば、ちょっとハッピーになれる・・・(かもしれない)。

SharedWorkerとかも興味があるんだけど、スマートフォンであまり対応していないんだよね。やっぱバッテリーの電気を食うからなんだろうか・・・。

Web WorkerとComlinkとスレッドと非同期と

ブラウザで画像ファイルのリサイズ処理の続きです。


※勘違い&間違ってるところがあろうかと思います。注意喚起

Webブラウザではよく非同期処理を行いますよね。
ほらよくJavaScriptってシングルスレッドだよ、っていうじゃないですか。
つい最近まで非同期処理はスレッドを起こしているんだ!って勘違いしてまして・・・お恥ずかしゅうございます。

ブラウザのJavaScriptでもWeb Workerを使って、手軽にマルチスレッドできるじゃないですか?スレッドを使うのはC#と同じで超メンドクサイっていうイメージがあるので、敢えて避けていたんですが・・・流石にそうもいってられなくて手を付けてみよう!と思ったんですぅ。

ま、でもいきなり実戦投入するのは流石に怖いので・・・ ちょっと前にこのブログにも書いた「ブラウザで画像ファイルのリサイズ処理」のWeb Worker版を書くことにしました。まぁ、勉強のつもり。

以前の記事の、resize.image.js を書き換えて、画像の読み込み、リサイズ処理を Web Workerを使って別スレッドに処理を投げよう、とこういうわけです。

注意しないといけないのは、Web WorkerからDOMに関係するオブジェクトへのアクセスは一切できないところ。メインスレッドでのグローバルスコープとしてwindowが登録?されていますが、worker内ではそもそもwindowオブジェクトがありませんし。そのかわりwindow.consoleとかwindow.setTimeoutとかのいくつかの関数は worker内のグローバルスコープに登録されています。

ですが、実験機能として、Canvasオブジェクトに transferControlToOffscreenというメソッドChrome/Edgeのみに実装されてまして、このオフスクリーンキャンバスはWeb Worker内でアレヤコレヤできるみたいです。(OffscreenCanvasはメモリ内のみに存在できる表示できないCanvasみたいな感じ?)
特定ブラウザに依存してしまうのは不本意ですが、いずれほかのブラウザFireFox,Safari等へ実装されることを期待しましょう😅

Web Worker自体は window.postMessageメソッド/onMessageイベントと同じような仕組みでデータをやりとりします。が、こういうめんどくさい処理を代行してメインスレッドから透過的に非同期処理と同じような感覚でWeb Workerを扱えるようにしてくれる、Comlink というライブラリがGoogleの開発者によって公開されています。たぶん、Web Worker を使う場合は実質的にこのComlinkというライブラリを使っていくのがスタンダードなやり方になるんだろうと思います。


Comlink
Comlink makes WebWorkers enjoyable. Comlink is a tiny library (1.1kB), that removes the mental barrier of thinking about postMessage and hides the fact that you are working with workers.


Comlinkは・・・worker側でオブジェクトやクラスや関数をメインスレッド側に公開 Comlink.expose(x) し、メインスレッド側で new Workerされた Workerオブジェクトを Comlink.wrap(w) することで、worker側で公開されたオブジェクトやクラスや関数をメインスレッド側から利用できるようにするライブラリで、postMessageメソッド/onMessageイベントなどを意識しないコーディングができるので非常に便利です。
通常のWorkerのほかにもSharedWorker/ServiceWorkerにも対応しています。使い方も上記 githubにあるサンプルコードを読めば書き方は分かると思います。

githubからzipファイルをダウンロードするのもいいのですが、nodejs/npmでインストールするのが簡単なので・・・

>> npm i comlink

comlink/dist/{esm,umd} 以下に ロードするファイルがありますのでこれらを import文で読み込む場合は dist/esm以下のディレクトリ、また、Web Workerに処理させる worker.js内では dist/umd 以下のファイルをimportScripts関数でロードするようです。この辺、ちゃんと勉強していないので、正直よくわからん。

【↓の説明のデモ】 chrome/edgeのみ
※説明では upload.php で送信するようになってますが、このデモでは選択したローカルの画像は、一切送信されないように若干修正しています。

まずは、resize.image.js を以下のように修正します。
このファイルの大部分は worker.js に移動させ、このresize.image.js自体は Web Workerに処理を投げるためだけのプロキシー的な役割に変更しました。要は、DOM要素のCanvasオブジェクトを作って、このオブジェクトからOffScreenCanvasを取得し、[input type=”file”] から得られる Fileオブジェクトともに画像リサイズ処理を担う worker.js に渡します。
この際、Fileオブジェクトはそのまま渡せるのですが、OffscreenCanvasはComlink.transferメソッドでラップして渡します。そのまま渡すとエラーになるみたい。

Web Worker側に値を渡す場合、すべてディープコピーされて渡されます。https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage や、https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm を参照。

この他にも Transferableインターフェイスを実装されているオブジェクトも渡せるようです。このあたりは、MDNサイトのドキュメントを参照

Comlinkは、こういった postMessage/onMessageイベントでのやりとりを、通常のメソッドをコールするような感じでworker側に処理を行わせる事ができるようにしてくれていますので、手順さえ踏めば、非常に簡単に Web Workerを扱えるようになります。

そして、肝心の worker.js です。
前に書いたリサイズの処理は web worker では使えません。以前は new ImageとしてDOMのImageオブジェクトを作成して Canvas APIでゴニョゴニョしてましたが、Web Worker ではそもそも DOMのImageオブジェクトが使えません。
どうするかというと、createImageBitmap関数を使います。これは DOM要素のwindowオブジェクトにもありますし、Web Workerでの グローバルスコープにも登録されているので、この関数に Fileオブジェクトをそのまま放り込んでOffscreenCanvasで使用できる画像を生成します。

これらを以前と同じようにスクリプトから上記のresize.image.js をimportして使用します。

【デモページ】 chrome/edgeのみ
※このデモでは選択したローカルの画像は、一切送信されないように修正しています。

F12キーで開発ツールを表示させ、デバッグすると、確かに スレッドが作られています。

画像一つのリサイズ処理であればあまりメリットを感じませんが、複数の画像処理が走りつつ、メインスレッドでUI処理が走っている場合にメリットを感じるケースもあるかと思います。以前書いた、JSONデータをダウンロードしてエクセル出力する、という一連のフローを Web Workerでスレッド化することも可能かも、まだ試していませんが・・・。

ウェブ上のゲームとか、重い計算処理をメインスレッド以外のスレッドでやればメリットは大きいのではないかと思います。エントリPCであっても CPUコアが4つ以上詰んでるのはザラにありますしね・・・。