Markdown をその場で試せる /preview を付けた #

ヘッダー右の preview リンクから /preview に行ける。textarea に書いた Markdown が、すぐ右にこのサイトの記事と同じ形で描かれる。保存はしない(issue #71)。この記事自体を貼り付ければ、見出しのアンカーもアラートも表もそのまま出る。

欲しかった理由 #

tinymarkdown とブログの renderer がどこまで出来ているかを、ビルドやデプロイをせずに確認したかった。issue を立てたのはスマホからだった。LLM に生成させた Markdown を仮当てして、どういう見え方になるか試す用途が最初から頭にあった。スマホだとクリップボードの制限で長文を貼るのが難しいので、ファイル入力で .md をそのまま読み込めるようにもしてある。

描画はサイト自身にやらせた #

ロマンを目指して、記事を描くのと同じ renderer をブラウザーに持っていった。site パッケージが記事用に持っているレンダリング(front matter からタイトルを取る、見出しに id と permalink、alert、```svg、コピーボタン)を丸ごと呼べる関数 render(src, files) として export して、wasm-gc で preview.wasm にした(use-js-builtin-string、experiments/browser-render の spike と同じ載せ方)。

最初は Wasm を instantiate できなかったとき用の JS ビルド(.mjs)も作っていたのだけど、Wasm の動作確認が取れたので丸ごと捨てた。「ロマンより動く機能」の免罪符で残すより、1 経路だけを面倒見る方が良い。

リンクは posts/preview.md を書いているつもりで解決 #

プレビュー内の相対リンクは、「あなたが posts/preview.md という記事を書いている」つもりで解決する。../TODO.md ならこのサイトの /docs/ のページへ、存在しないパスは GitHub の blob URL へ飛ぶ。飛び先を検証するため、ページにはサイトが持っているファイル一覧を仕込んである。

副産物と落とし穴 #

  • code-copy.js はページロード時だけ pre を飾っていたので、プレビューで挿入される pre にコピーボタンが付かなかった。MutationObserver で後から足されたノードも飾るように直した。
  • Devin Review が「ファイル名に </script> があれば <script type="text/plain"> を抜けて HTML を差し込める」と指摘した。ファイル一覧は <script> の生テキストに置くのをやめて、属性として書けば必ずエスケープされる data-wasm / data-files に移した。
  • ```svg ブロックは生 HTML を通す仕様なので、他人が用意した Markdown を貼ると被害者のブラウザーでスクリプトが動きうる。記事と同じ見え方が目的なのでここは変えず、サニタイズは Future work に記録した。

確認 #

いつも通り make check と make compare(Go サーバーと Workers 用 wasm のバイト一致、今回から /preview も比較対象)。最後に実際の Chrome で Wasm が instantiate されるところまで見た。ページのファイル入力の横には小さく rendered by wasm と出るので、どの経路で動いているかも分かる。