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つ以上詰んでるのはザラにありますしね・・・。

今更ながら Visual Studio Code でデバッグ環境構築

2年ぐらいかかっていた業務用のウェブシステムのリプレース案件がこの夏ようやく完了。
なんせ仕様調査からシステム構築、デザイン、データベース設計、コーディングをたった一人でやって(web系が分かる人が僕しかいいない環境なので・・・)、前のシステムにあった訳の分からない仕様を撲滅しつつ、妥協できるところは妥協し、譲れないところは譲らず我を通したせいで文句を言われつつ、自分にとってなんとか納得のいくものができたつもり。
幸いなことに、使っているユーザーさんから、前より使いやすくなった、との声をもらえたのでそれだけでもよかったかな、と思います。
あとは営業さんがユーザーさんに簡単な講習会をやって終わり・・・という段階になってやっと強烈なストレスから解放されました。

前置きはともかく・・・

ちょっと時間が空いてきたので、前から興味があった Visual Studio Codeを触ってみよう、と思いいろいろ調べてある程度のデバッグ環境を構築できてきました。そもそもVisual Studio Codeを使ってみようと思ったきっかけは・・・

  • C#やC++で一つのファイルで完結してしまうような小さいコンソールプログラムをたまに書くけど・・・簡単なデバッグ環境が欲しい
  • msbuildとか要らん!そもそも XML は好かん。 nmakeで必要十分だし書くの慣れてる。
  • Visual Studio 2019のIDEを使えば手っ取り早いんだけど・・・おいらのPC環境のせいかどうか分からないが、エディタのフォント表示が異常に汚い(ガタガタ)
  • Visual Studio 2019のIDEを使うほど大したコードを書くわけじゃないし、何より動作が重いし、キーバインドが慣れない
  • 拡張機能でvimのキーバインドがほぼ完璧に再現できるみたい?

早速インストールし、必要そうな拡張機能を入れて使いだしたんだけど、問題が発生。
ターミナル>ビルドタスクの実行を行うと・・・cl.exe、csc.exe、link.exe などのツールへのPATH設定や環境変数などが定義されていないのでエラーに!

普段、c++やc#のコードをビルドするとき、Visual Studio 2019をインストールした時に入る、vcvars64.bat というBATファイルをコンソールウィンドウから実行することで Visual Studio 2019のビルドツールを使えるようにしてたんですが・・・はて・・・ターミナルの設定はどうすればいいのか・・・考えたあげく、下記のような コマンドファイルを作ってこれを setting.json の Terminal.Integrated.Profiles というキーに入れた。

REM ------------------
REM vs2019t.cmd
REM ------------------
@ECHO OFF
CALL "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"
%comspec% %*

visual studio 2019のコンソール環境用にterminalのprofileを一つ追加。

下記のように デフォルトのプロファイルとして設定した。

ホントは知らないだけで、もっとスマートな方法があるのかもしれん!
結構ググったんだけど見つけられなかった。。。

で、実際のデバッグ環境としてはかなり良くなった!

ターミナル>既定のビルドタスクの構成からtasks.jsonを作成してビルド方法を書くみたいだけど、一つのファイルをデバッグしたい場合は、cl.exe/csc.exeとかのコマンドを設定して、
複数のファイルがある場合は、nmakeかmsbuildとかにした方が効率がいいかもしれない。
tasks.jsonのテンプレートをいくつか用意しておいた方がいいかもしれん。

vscode/tasks.json テンプレ

.vscode/launch.json テンプレ

とりあえずsubversionで生きていく。

愚痴。

ゴールデンウィークですね。今年も引き籠りの年になりそうです。
来年はどうなるんですかね・・・

まぁ、そんなことはともかく。

ここ半年、gitのトレーニングをしようと、趣味用途のプログラムはほとんどgitを利用して管理してきました。
クラウドサービスでVPSのインスタンスを1個作ってリモートリポジトリとして使って勉強。

結局のところ、僕のような頭の硬いおっさんには、ちょっと肌に合わないな、と思い、結局のところ挫折してしました(-_-;)
根本的に git を理解できなかった。っていうか、gitとかsubversionでブランチを使用した開発の手法をよく理解していないのが根本的な理由かなぁ。

挫折したポイントとしては、ブランチの扱い方が Subversionとあまりに違い過ぎて、付いていけなくなった(理解できなかった)こと。
いや、issue毎、feature毎にブランチを切って、マージして、という、一連の流れは分かるんですよ。
それこそ、チュートリアル的な単純なプロジェクトだと普通に分かるんすよ。ある程度コツがつかめてきて、仕事で使おうとしてたんだけど・・・どういえばいいのかちょっと迷うんだけど・・・ブランチを切る粒度というか、それがイマイチ分からないんですよねぇ。。。

アプリのソースがあって・・・
1. ある不具合があって、masterから修正のためのブランチを切る。
2. そのブランチでバグ潰しているうちに、そのバグに関連して別のバグを発見!😭

この時、
A. masterブランチから別のブランチを切って、後々リベースすればいいのか???
B. そのブランチでついでにその関連したバグを修正すればいいのか???
C. 一旦コミットしたのち、またブランチを切りなおせばいいのか?
subversionだとそんなの構わず一気に修正して、コミットして、ログに修正項目を羅列してたんだけど・・・。要は作業記録して残してるだけ、みたいな。

さらに、客側から、新しい機能のリクエストがあって、それを featureブランチを切って・・・とかやってると、慣れない git のブランチの管理でパニック!

これがチームで開発してて、同じ git使いなら、「ねぇ、今、こっちの修正で時間かかってるんで、別のバグ修正しといてくれない?」って感じで頼めるんだろうけど、おひとり様開発なので、んなのメンドクサイから、ついでに直して一緒にコミットすればいいや、って風になってしまう。
こういうのが重なってくると git の良い所を自ら潰していってるようなもん。履歴なんて滅茶苦茶。

結局、git を仕事で使うのを断念して、Subversionで、バグがあるたびに、issueブランチを作って(コピーして)、修正したらtrunkにマージして・・・という風な感じで進めていった方が生産的じゃないか???と思いつつあります。。。

一人で開発してるのって、制約がないので自由にできるけど、逆に言えば、引き継ぎのハードルが異常に高くなってしまう。
んー、こんな煩わしいことに時間を割きたくないんだけどな・・・

JSONデータをExcelファイルに展開したい、ブラウザで。

2022年3月8日 一部記述削除


2021年7月31日 修正
babelの設定を書き忘れてた!package.jsonにbabelの設定を追加。


これの続編

サーバーで処理させるよりデータだけをWeb APIで引っ張ってきてブラウザ上のJavaScriptで処理しよう、っていうのがここ最近のトレンドだと思います。そのための環境は整ってきました。

ここ最近、業務系のウェブシステムでExcelをあーだこーだしろ、っていうのが急に増えてきてまして・・・たぶん昔動かしていたAccessアプリケーションを単純にWebへ・・・という流れなんでしょうかね?

で・・・Webサーバー(LAMP)でPHP-Excel(PHP-Spreadsheet)使ってエクセルファイルを処理させると滅茶苦茶メモリ食いませんか?
数年前に少し試してみたんですが、通常業務で利用するぐらいの行数(列数)でもメモリ不足でエラーでるわ、処理が遅くて使いもんにならんわ、で使っていくのを速攻で止めました。
(PHPじゃなくてJavaとかだと速いのかもしれんが、Javaは難易度が高すぎるし、そもそもLAMPを主戦場にしている仕事環境なので。)

さて、ちょっと前に Excelファイルを読み書きする SheetJS っていうのをネタに備忘録として書いたけど、SheetJSは Pro版じゃないと(有料)、スタイルなど文字の大きさとかフォントの指定だとかは扱えません。実質、既存のExcelのデータを読むだけになると思います。まぁPro版買えって話なんですけど。

で、良い感じにブラウザでカンタンにきれいなExcel帳票を作れないかなぁ・・・といろいろ githubとか漁ってたら、XLSX-Rendererっていうのを見つけました。簡単に言うと、WORDでいうところの差し込み印刷のように、テンプレートのExcelファイルにデータを差し込んでExcelファイルを作ってくれる。
XLSX-Renderer自体は ExcelJS というnodeモジュールを使用しているみたい。

XLSX-Renderer
https://github.com/Siemienik/XToolset/tree/master/packages/xlsx-renderer

まぁ、いつもの如く中身はよく分かんねーけど一度試してみました。今回もその備忘録です。
【動作デモはこちらから】

開発環境としてnodejsとnpmが使える環境が大前提です。
また、モダンなJavaScriptの知識( Promise,await/async,thenなど)と、必要であれば、BabelJSなどのカンタンな使い方を知っている人向けです。じゃないとワケワカメになると思います。ご了承のほど。

システムにbrowserify,uglify-jsをインストール

>> sudo npm install -g browserify
>> sudo npm install -g uglify-js

説明するまでもないとは思いますが、browserifyはnodejsモジュールをブラウザで利用できるようにワンパッケージにしてくれるコマンド。uglifyjsはminifyするため。詳しくは知らない。手順だけ知っておけばいい。理屈は後回し。フロントエンド周りに詳しい人は webpack とかいろいろ方法はあるとは思いますが、僕はこれしか知らないので💦

まず、下記のようなテンプレートとなるExcelファイルを作ります。(テンプレートExcelファイル

このExcelファイルに、データをぶち込みたいところへ、#から始まる命令コマンドをセルに埋め込んでいく、という感じ。自分でセルを埋め込むメソッドをチマチマコーディングするより圧倒的にラクできる感じ。
#! FOR_EACH 命令を使えば、配列をぶち込める。

要は MVCモデルの View の出力先を エクセルファイルにしている感じでしょうか。同様に、DOCX-Renderer とか PDF-Renderer ってのも作ってほしいよ(笑)

次に本題の処理する部分をコーディングします。

扱いやすいように、僕はグローバル関数を(windowオブジェクトのプロパティとして)定義してコールするようしました。このあたりはどういう処理を行わせるかによって実装方法は変わると思います。とりあえず関数として実装しました。
このままではブラウザでは動かないので、上記のコードをブラウザで利用できるようにするため、この xlsx-template.jsをbabelなどのツールを使ってビルドします。そのため、ディレクトリを一個作って以下のpackage.jsonを置き、

>> tree .
.
├── package.json
└── xlsx-template.js

そのディレクトリで以下を実行

>> npm install
>> npm run bundle

そうすると、bundle.min.jsが生成されるので、これをHTMLファイルのscriptタグからロードします。
HTMLファイルはこんな感じでしょうか。実際にはJSONデータはサーバーから取得することになりますが・・・。データ構造は簡単で、キーと値がそのままテンプレートのExcelファイルのセルに記述した “## キー名” に対応してます。このあたりは XLSX-Renderer のgithubページに記述の仕方が書かれているので参考に。比較的簡単です。

そうすると、下記のようにテンプレートとなるExcelファイルにデータを埋めてくれます。

【動作デモ】

エクセルのレイアウトが変わっても、テンプレートのExcelファイルを変更すればいいだけので、変更も簡単で、実行コードを修正する必要もない。

まだ試していませんが・・・データベースサーバーからウェブサーバー経由で1万件ぐらいのJSONデータを取ってきて、処理させてテストしてみて、処理速度が業務に耐えられるようであれば、本格的に利用していきたい、と思ってます。

・・・しかし・・・みんなExcel好きだよねぇ。。。