このサイトのツールは、デスクトップ版も配布しています。以前は Electron で作っていたものを Tauri に移したとき、150本ほどをスクリプトで一括して書き換えました。Web版は毎回動作を確かめていたので、同じコードから作るデスクトップ版も大丈夫だろうと考えていましたが、デスクトップ版を1本ずつ実際に開いて確かめたところ、起動した時点で機能が止まっているものが9本ありました。どれも Web版は正常です。機械的な書き換えを通った配布物は、元のコードとは別物になっていると考えたほうが安全です。この記事では、そのとき使った確かめ方を、見つかる問題の種類ごとに順に紹介します。上から順に当てていくと、どこで壊れているかを当て推量なしで絞り込めます。
1. 全ページを開いて、読み込み時のエラーを集める
最初の網は、配布する全ページを実際にブラウザで開き、読み込みの時点で出る例外を集めることです。デスクトップ版の画面は HTML なので、配布フォルダをローカルのサーバーで配信し、ヘッドレスのブラウザで1本ずつ開いて、ページ内で捕まえられなかった例外(Playwright なら pageerror イベント)とコンソールのエラーを記録します。デスクトップ固有の API はページを開く前に空の代役を差し込んでおけば、呼び出しで止まらずに先へ進めます。150本でも数分で終わり、この段階で4本が読み込みの途中でスクリプトごと止まっていることが分かりました。画面の見た目は崩れていないので、開いて眺めるだけでは気づけません。ボタンを押しても何も起きない、という形でしか表に出ないからです。
2. 置換で壊れる型①:文字列の中にも差し込まれる
止まっていた4本のうち2本は、移行用の小さなスクリプトを head の閉じタグの直前に差し込む一括置換が原因でした。置換は閉じタグの文字列を探して、その前に script 要素を入れるものです。ところが、このツールには編集内容を HTML として書き出す機能があり、書き出し用の HTML がテンプレート文字列としてスクリプトの中に書かれていました。その中の閉じタグにも script 要素が差し込まれ、差し込んだ側の閉じタグで、本体の script 要素がそこで終わってしまったのです。HTML の解析器は JavaScript の文字列かどうかを見ずに、script の閉じタグが来た位置で要素を閉じます。置換のあとは、差し込んだ件数がファイルあたり1件になっているかを数えると、この事故はすぐ分かります。2件以上あれば、どこか余計な場所に入っています。
3. 置換で壊れる型②:置換文字列の $ が展開される
残りの2本は、YAML を読むライブラリの途中に HTML 文書が丸ごと入り込んでいました。ライブラリのコードを JavaScript の replace の置換文字列として渡して埋め込んだのが原因です。replace は、置換文字列の中の $` を「一致した箇所より前の全文」に、$' を「一致した箇所より後の全文」に展開します。小さくまとめられたライブラリのコードには、この並びが偶然含まれていることがあり、その位置に HTML の前半がそのまま書き込まれました。エラーも警告も出ず、置換は成功したように見えます。コードを埋め込むときは、置換文字列ではなく関数を渡して(replace(目印, () => コード) の形)展開を止めます。書き換える前と後のファイルを Diff Viewer で比べると、意図した行数を大きく超える差分が出るので、これも気づく手がかりになります。
この手順で使うツール
Diff Viewer
テキストの差分をリアルタイムで可視化
4. 開いた時点では動き、押すと落ちる
読み込みが通っても、ボタンを押した瞬間に例外が出るものがあります。Base64 の変換ツールは、Node.js の Buffer を使って変換していました。Electron では使えても、Tauri の画面はブラウザと同じ環境なので Buffer は存在せず、変換ボタンを押すと「Buffer is not defined」で止まります。こうした問題は、読み込みのチェックだけでは見つかりません。開いたページのボタンを機械的にすべて押し、そのあいだに出た例外を集める、という粗い操作テストを足すと拾えます。入力が空でも押せるボタンがほとんどなので、正しい結果を確かめるところまでは行かなくても、「押したら落ちる」は網羅できます。直すときは、ブラウザに標準で備わっている TextEncoder や atob などに置き換えます。
この手順で使うツール
Base64 Studio
Base64 エンコード & デコード
5. 参照しているファイルが配布物にない
開発中のフォルダでは動くのに、配布物では読み込めないファイルがあります。図を描くツールは、ライブラリを ./node_modules/ から読み込んでいました。開発中は手元にあるので動きますが、配布フォルダには node_modules が含まれません。メモ帳のツールは、画面の動きを書いた renderer.js が配布フォルダに入っていませんでした。確かめ方は単純で、配布する HTML の src と href をすべて抜き出し、それぞれのファイルが配布フォルダの中に実在するかを調べます。ついでに、コンテンツセキュリティポリシー(CSP)でインラインのスクリプトを禁止している画面に、インラインで差し込んだスクリプトがないかも見ておきます。CSP に止められたスクリプトは、コンソールに1行出るだけで、画面には何も表示されません。
6. エラーを出さずに、間違った結果や空の結果を返す
いちばん見つけにくいのが、例外を出さずに間違った結果を返すものです。読み取りに使うライブラリを同梱し忘れたツールは、「この機能は利用できません」と表示するだけで、エラーは出していませんでした。ハッシュのツールは、移行用の代役の関数が「(N/A)」という文字を返していたため、SHA の欄が常に (N/A) で、エラーにはなっていません。こうしたものは、答えが分かっている入力で結果を照合するしかありません。空の文字列の MD5 は d41d8cd98f00b204e9800998ecf8427e、abc の SHA-256 は ba7816bf で始まる、といった既知の値を Hash Studio などの別の実装で確かめ、そこと突き合わせます。あわせて、使っているライブラリの名前(jsQR や mermaid など)が、配布物のどこかで定義されているかを機械的に調べると、同梱し忘れを一度に洗い出せます。
この手順で使うツール
Hash Studio
MD5 / SHA ハッシュ生成 & 検証
7. 規格どおりの出力かは、別の実装で読み戻す
QR コードのツールは、画面に QR コードらしい模様を正しく描いていましたが、どの読み取りアプリでも読めませんでした。自前で書いた符号化の処理が、形式情報の位置やデータの分割の仕方で規格と食い違っていたのが原因です。見た目では正しい模様と区別がつかず、作った本人のテストも同じ思い込みの上に書かれているので通ってしまいます。こうした出力は、別の人が書いた実装で読み戻して、元の入力と一致するかを確かめます。QR コードなら、生成した画像を独立した読み取りライブラリで読み、入れた文字列が返ってくるかを見ます。この確かめ方は、規格に沿って作るあらゆる出力(ファイル形式、圧縮、文字コードの変換)に使えます。直したあとは、自前の符号化をやめて、広く使われているライブラリに置き換えました。
この手順で使うツール
QR コード
テキスト・URL を即座に QR コードへ変換
8. 検査は、壊れていた版で一度通してから使う
最後に、検査そのものの確かめ方です。検査を書いて流し、何も出なければ「問題なし」と読みたくなりますが、検査が問題を見つけられる作りになっているとは限りません。今回も、入力欄に攻撃用の文字列を入れて画面に混入しないかを調べる検査を書いたとき、そのままでは一部の入力欄で問題を見逃していました。そこで、直す前の版、つまり壊れていると分かっている版に先に検査を当て、きちんと引っかかることを確かめてから、直した版に使います。壊れた版で何も出ない検査は、直した版で何も出なくても何も保証しません。読み込みエラーの収集も同じで、わざとエラーを入れたページを1枚混ぜておくと、記録の仕組みが動いているかを確かめられます。
まとめ
一括移行のあとは「全ページを開いて読み込みエラーを集める → 差し込みの件数と置換前後の差分で置換事故を探す → ボタンを全部押して落ちる箇所を拾う → 参照先が配布物に実在するかを調べる → 既知の値で結果を照合する → 規格に沿った出力は別の実装で読み戻す」の順に当てると、見た目では分からない壊れ方まで拾えます。特に「HTML の解析器は文字列の中の script の閉じタグでも要素を閉じる」「replace の置換文字列では $` と $' が前後の全文に展開される」「エラーを出さない失敗は、答えの分かっている入力でしか見つからない」の3点は、知らないと動かないものをそのまま配布してしまいます。