hamakou108 blog

rehype-starry-night でシンタックスハイライト

Cover Image for rehype-starry-night でシンタックスハイライト

月初めになると、個人開発したソフトウェアのリポジトリに仕込んだ Renovate のプルリクエストを確認するようにしている。このブログもその例に漏れないが、3ヶ月前から一部のプルリクエストで CI が失敗していた。どうやら typescript-eslint 8.x へのバージョンアップと関係があるようだった。

このブログでは next-mdx-remote を使って、 MDX (Markdown) 形式で書かれた記事を HTML に変換している。コードブロックのシンタックスハイライトには remark-prism というパッケージを利用していた。新しいルールで動いたリンターは、そのパッケージが require() を使ってインポートされていることを違反と判断したようだ。

remark-prism の最終リリースはすでに3年前のようだった。良い機会なので、シンタックスハイライトのパッケージを変更することに決めた。最終的に rehype-starry-night を導入した。

環境

  • Next.js 14.2.15
  • yarn 4.5.0
  • next-mdx-remote 5.0.0
  • rehype-starry-night 2.2.0

ツールの選定

シンタックスハイライトのパッケージはどのように選定すれば良いのか。ありがたいことに MDX の公式サイトにはシンタックスハイライトに関するガイドがある。

Syntax highlighting | MDX

このガイドによると、シンタックスハイライトの実現方法には (a) コンパイル時に処理する、 (b) ランタイムで処理する の2種類があるようだ。これまで使っていた remark-prism は (a) に該当したため、引き続きこのタイプのプラグインを利用することにした。

サイト上では rehype-starry-night / rehype-highlight / rehype-prism の3つが挙げられていた。おや、 rehype とは何だ。 remark-prism の remark とは何か違うのか。

調べてみると、 rehype と remark はいずれも unified というエコシステムに含まれる、言わば親戚同士のツールらしい。 rehype は HTML の変換ツールで、 remark は markdown の変換ツールだ。 rehype-* や remark-* はこれら変換ツールのプラグインだったというわけだ。 remark-prism を何年も使ってきて何も知らないのは恥ずかしい限りだ。

next-mdx-remote は rehype と remark の両方に対応しているので、 rehype のプラグインに乗り換えても特に問題なさそうだ。 rehype と remark のどちらでシンタックスハイライトを付けた方が良いのか正直分からなかったが、 HTML と markdown のどちらの AST に対して処理するかどうかの差異であれば、大した影響はないだろう。この選択によって何だか恥を重ね塗りしたような気もするが、とりあえずツール選定を続ける。

まず rehype-prism は既に public archive になっていたので早々に除外した。残りの2つはシンタックスハイライトそのものを実現するのに用いているツールに違いがある。 rehype-starry-night では starry-night が、 rehype-highlight では lowlight が用いられている。スター数からすると rehype-highlight の方が手堅そうだったが、今回は目新しさから rehype-starry-night の方を導入することにした。もし壊れても、せいぜいビルドに失敗するか、シンタックスハイライトが効かない程度で済むだろう。

ツールの導入

まず依存関係から remark-prism や関連プラグインを削除した。そして @wooorm/starry-night と rehype-starry-night を追加した。 @wooorm/starry-night は CSS ファイルのインポートのために追加する。

$ yarn remove prismjs remark remark-html remark-prism
$ yarn add @wooorm/starry-night rehype-starry-night

コードの変更点は2箇所ある。まず pages/_app.tsx で @wooorm/starry-night の CSS ファイルをインポートする。 .css を付けるとインポートできないので注意が必要だ。

- import 'prismjs/themes/prism.css'
+ import '@wooorm/starry-night/style/light'

そして next-mdx-remote を使って markdown テキストをシリアライズする際のパラメータ mdxOptions を変更した。

  import { serialize } from 'next-mdx-remote/serialize'
+ import rehypeStarryNight from 'rehype-starry-night'

  const mdxSource = await serialize(post.content, {
    mdxOptions: {
-     remarkPlugins: [[require('remark-prism')]],
+     rehypePlugins: [rehypeStarryNight],
    },
  })

yarn を PnP から non-PnP モードへ切り替え

この状態で Next.js の開発用サーバーを立ち上げると次のようなエラーが発生した。

[Error: [next-mdx-remote] error compiling MDX:
Cannot find package 'vscode-oniguruma' imported from ...

More information: https://mdxjs.com/docs/troubleshooting-mdx] {
  page: '/posts/change-syntax-highlighting-plugin'
}

調べたところ、 get-oniguruma.fs.js での import.meta.resolve を使った依存関係の解決にどうやら失敗しているようだった。このブログでは依存関係の管理に yarn を使っているが、依存関係のインストール戦略を PnP から non-PnP (node_modules による管理) に変更したらエラーは解消した。正直、なぜ PnP だと上記コードの実行に失敗するのかはよく分かっていない。

終わりに

というわけでこのブログでは rehype-starry-night によってシンタックスハイライトが適用されるようになった。前述のコードの diff の部分にシンタックスハイライトが当たっているだろう。

他の部分にハイライトが当たってないぞという声が聞こえるような気がする。これは今回の変更前から変わっていないため、 rehype-starry-night 以外の部分の問題だと考えている。いずれにしても締まらない幕切れであることは変わらないのであり、普段から継続的にメンテナンスしておくことの大切さを思い知らされるのだった。