やっと社会人になって多少忙しくなったのですが、このブログは備忘録的に続けていきたいと思います。
以前KaTeXを使って数式をきれいに表示する方法を解説しました。
しばらく使ってみたところこのやり方だと色々と問題があることに気づいたので、別の方法を模索して実装し直しました。
新しい方法のメリット・デメリットも含め解説していきたいと思います。
環境
- Next.js: 12.1.5
- Katex: 0.16.2
- newt-client-js: 3.2.4
解説と実装
解説
前に書いた方法との大きな違いは、前はKaTeXのrendetToString
を使っていました。
この関数にはdelimiter(どこからどこまでが数式なのかの識別子)を判別したり、削除したりする機能がありませんでした。
そのため、正規表現とTypeScriptの文字列操作を使ってmicroCMSのAPIによって取得したHTMLタグ付き文字列から無理やり数式部分を抜き出していました。
一方、今回の方法ではKaTeXのauto-render extensionを使います。
この方法は開発者が特に何もしなくても生成したdomツリーから指定したdelimiterを元に勝手に数式部分を抜き出して数式をきれいにレンダリングしてくれます。
今回この方法を採用した背景には、ヘッドレスCMSを変更したことがあります(別の記事で書くかもしれませんが、microCMSからNewtというヘッドレスCMSにお引越ししました)。
それに伴って各CMSから送られてくるHTMLタグ付き文字列の内容に微妙な違いがあり、それが原因でKaTeXが動かなくなってしまいました。
そのため、この修正と合わせてKaTeXによる数式レンダリングの方法を今回改めて見直しています。
新手法のメリット・デメリット
この方法のメリットは以下のようになります。
- 複雑な文字列操作をしなくても数式部分を勝手に抜き出してくれるため、楽に数式をレンダリングできる。
- 導入が簡単
- 動作不良を起こしにくい
一方、この方法にはデメリットもあります
- DOMツリーを元に数式をレンダリングするため、DOMツリー内で別のノードとして認識されると数式表示できなくなる
- サーバーサイドで修飾したHTMLを受け取るわけではないのでパフォーマンス的には若干不利(?)
このうち、1つ目のデメリットの意味としては、例えば数式内に改行タグ(<br>
)等があるとうまく数式として認識されなかったりします。
そのため、数式を一行で書く必要があり、CMS上でLaTeX表現を書くのが多少不便だったりします。
とはいえ、今のところそんなに大量の数式を書く予定はないので、当面はこれで大丈夫でしょう。
実装
実装自体は前に書いた方法に比べかなり簡単です。
import { useEffect } from "react";
import renderMathInElement from "katex/contrib/auto-render";
import 'katex/dist/katex.min.css'
const PostBody = (props: Props) => {
useEffect(() => {
renderMathInElement(document.body, {
delimiters: [
{ left: '$$', right: '$$', display: true },
{ left: '$', right: '$', display: false },
{ left: "\\(", right: "\\)", display: false },
{ left: "\[", right: "\]", display: true }
],
ignoredTags: ["code"]
})
}, [])
return (
<div dangerouslySetInnerHTML={{ __html: props.blog.body }} />
)
}
これだけでHTMLタグ付き文字列から数式部分を取り出してレンダリングまでしてくれます。
ポイントはuseEffectを使っている点です。
useEffectの第2引数に空の配列を指定することで最初にレンダリングしたタイミングでのみrenderMathInElementを作用させます。
また、renderMathInElementの第2引数はオプションになっており、ここではdelimiters(識別子)とignoredTagsを指定しています。
ignoredTagsにcodeを指定しているのはcheerioなどのjQuery系のライブラリを使う際にドルマークを使ったりしますし、コードブロック内に数式を書く機会はないだろうという判断から無視するようしています。
まとめ
実はこの方法はchatGPTが提案してくれた方法です。
この方法にたどり着くまでにサーバー側でjsdomを動かして仮想DOMツリーを元に数式のレンダリングをしたあと、さらにHTMLタグ付き文字列を作り直すという回りくどい方法を試したりもしました。
やっぱりchatGPTからうまいこと知りたい答えを引き出すのって難しいですね。
とはいえ、それって人間に対する質問でも同じなので結局コミュニケーションが難しいってことなんでしょうか。。。