rehype-katex と rehype-mathjax の比較

まえがき

先日、数式を使ったブログ記事を書いた際、数式のレンダリングには MathJax の CDN を用いた。 Lighthouse でパフォーマンスを調べたところ、 「Reduce unused JavaScript」 の項目で不要なもののうち、MathJax に由来するものが 160 kB ほどあるということがわかった。 他にも不要なフォントファイルもあり、それらがパフォーマンススコアを下げる原因になっていた。 ビルド時に数式をレンダリングする処理を施すことができれば JavaScript は不要になり、 パフォーマンススコアが上がるだろうと推測される。

そこで CDN を使わずに数式をレンダリングする方法を調べたところ、 remark-math と、 rehype-katex もしくは rehype-mathjax を使う方法を知った。 それらの挙動を調べてみた。

なお、今回使用したコードは GitHub に保存してある。

rehype-katexrehype-mathjax を試してみる

TeX 関連の rehype プラグインは rehype-katexrehype-mathjax があるのだが、それらの違いを調べてみる。 下記マークダウンファイルをパースしてみる。

# Mathematical Document

## Formula

### Text Style

円周と直径の比を円周率と言い、 $\pi$ と表す。

### Display Style

$$
\zeta(s) = \sum^{\infty}_{n = 1} \frac{1}{n ^ s}
$$

コード全体は GitHub にある。 ここでは本質的な箇所のみ記載する。

remark-math も rehype-katex, rehype-mathjax も使わない場合

const contentNoMath = await unified()
  .use(remarkParse)
  .use(remarkRehype)
  .use(rehypeFormat)
  .use(rehypeStringify)
  .process(await read(inputFileName));

生成される HTMLは下記。

No Math

シンプルにマークダウンが HTML に変換されている。

remark-math のみ使った場合

const contentSimpleMath = await unified()
  .use(remarkParse)
  .use(remarkMath)
  .use(remarkRehype)
  .use(rehypeStringify)
  .process(await read(inputFileName));

生成される HTMLは下記。

Simple Math

class="math math-inline" のような属性が加えられている。

rehype-katex を使った場合

const contentKatex = await unified()
  .use(remarkParse)
  .use(remarkMath)
  .use(remarkRehype)
  .use(rehypeKatex)
  .use(rehypeStringify)
  .use(rehypeDocument, {
    css: 'https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css',
  })
  .process(await read(inputFileName));

生成される HTMLは下記。

KaTeX

<mi>, <mo> など細かくタグが作られている。

なお、remark-math を使わないと、きちんと生成されない。

KaTeX(NG)

rehype-mathjax を使った場合

const contentMathjax = await unified()
  .use(remarkParse)
  .use(remarkMath)
  .use(remarkRehype)
  .use(rehypeMathjax)
  .use(rehypeStringify)
  .process(await read(inputFileName));

生成される HTMLは下記。

MathJax

SVG が生成されている。

rehype-katex を使った場合と rehype-mathjax を使った場合の差異は下記のとおり。

KaTeXMathJax
生成される要素HTMLSVG
ファイルサイズ8 kB12 kB

Astro への KaTeX の導入方法

HTML が生成されることと、ファイルサイズが小さいことから、KaTeX を使うことにした。 Astro への KaTeX の導入方法について記しておく。

Astro に KaTeX を導入するには rehype-katex, remark-math を使う。

パッケージをインストールする。

npm install --save rehype-katex remark-math

設定ファイル astro.config.mjs を編集する。

import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";

export default defineConfig({
  markdown: {
    remarkPlugins: [remarkMath],
    rehypePlugins: [rehypeKatex],
  },
});

CSS については KaTeX を使うページに下記を含めておく。

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css" integrity="sha384-Xi8rHCmBmhbuyyhbI88391ZKP2dmfnOl4rT9ZfRI7mLTdk1wblIUnrIq35nqwEvC" crossorigin="anonymous">

これで KaTeX を使ってレンダリングされるようになる。

unified, remark, rehype

ここまでのサンプルコードは下記のパターンになっている。

const content = await unified()
  .use(remarkXXX)
  .use(rehypeXXX)
  .process(await read(inputFileName));

ここで出てくる unified, remark, rehype について整理しておく。

  • unified: 各言語を AST として扱うライブラリ(e.g. remark, rehype)を統合して使えるようにする
  • remark: Markdwon を AST として扱う
  • rehype: HTML を AST として扱う

remark, rehype の他に、自然言語を扱う retext というものもある。

remarkXXX は Markdown の AST を加工するもの、 rehypeXXX は HTML の AST を加工するものである。

Astro のコードunified を使っている。

まとめ

rehype-katexrehype-mathjax を使って数式をレンダリングしてみた。 rehype-katex は HTML と CSS で数式を表示し、 rehype-mathjax は SVG で数式を表示する。 また、生成された HTML のファイルサイズは rehype-katex により生成されたものの方が rehype-mathjax により生成されたものより 30 % ほど小さい。 これらの比較に加え、Astro に KaTeX を導入する方法についても述べた。

あとがき

設定ファイルを少し編集するだけで数式を表示させられることを知り、Astro の便利さをさらに実感した。 Astro を使わずとも unified().use().process() で Markdown, HTML 間の変換ができるという 他でも使える知識を得られてよかった。 また、Computer Science がこういったところで役に立つことを知り、勉強になった。