Next.js+microCMSのSSGでコードのシンタックスハイライトを効かせる

投稿日:2020/9/20 / 最終更新日:2020/9/20

技術系のメモが出来るブログにしたかったので、このブログにはシンタックスハイライトの機能を実装している。
その実装の際に困ったことをメモしておく。

どのnpmモジュールを選択するか

結論、このブログではPrism.jsを使っている。
最初は「javascript syntax highlight」でググって一番上に出てくるHighlight.jsを使おうと思っていた。
ただHighlight.jsが調べた限りではtsxに対応していなかった。(それっぽいIssueは立っているのでもう時期実装されるかもだが)
tsxは書きたい言語の1つだったので別のモジュールを探した結果、tsxに対応しているらしいPrism.jsに決定した。

Prism.jsでどのようにシンタックスハイライトをSSGするか

「Next.js Prism.js SSG」とかでググっても丸々パクれそうなコードが見つからなかった。
稚拙な実装ではあるが同じような人がいた時にググれるようにメモを残しておく。
結論、要点は2つ。

  1. Prism.jsが提供するAPIの内、ローレベルなhighlightメソッドを使用した
  2. microCMSのデータスキーマについて、コードとセットで言語種別を渡すように設定した

なぜそういう結論に至ったかを説明していく。

Prism.jsが提供するAPIの内、ローレベルなhighlightメソッドを使用した

これに関してはPrism.jsのドキュメントのUsage with Nodeのセクションに、highlightメソッドが使われていたのでそうした。
ちなみにPrism.jsのAPIドキュメントを見ると分かるが、highlightよりも上位のAPIとしてhighlightAllというメソッドが提供されている。

This is the most high-level function in Prism’s API. It fetches all the elements that have a .language-xxxx class and then calls Prism.highlightElement on each one of them.

とあるように、language-xxxというクラス名が付いているnodeに全てに対してハイライトをかける便利なメソッドである。
このようにhighlightAllがハイライトするトリガーはクラス名になる訳だが、
自分が使っているmicroCMSのリッチエディタではコードブロックのノードにクラス名を指定することが出来ない。(他のheadless CMSだと出来ることなのかもしれない。もし出来るならそのまま使えばOKです)
という訳で大人しくhighlightメソッドを使う方法を探すことにした。

microCMSのデータスキーマについて、コードとセットで言語種別を渡すように設定した

highlightメソッドの引数には、そのコードの言語が何かを示す文字列を渡す必要がある。(以下表のlanguage)
記事中画像
そのためmicroCMSのブログ(リッチエディタで入稿しつつ一部はHTMLで入稿する)を参考にして、
ソースコードと言語種別をセットで渡せるようなデータスキーマを設定した。

データスキーマはこんな感じ。
本文をリッチエディタにするのではなく、「リッチエディタ + 言語種別 + ソースコード」の繰り返しフィールドにすることで実現した。
記事中画像
記事中画像
実際に入稿する時はこんな感じになる。
記事中画像
こうすることでソースコードとセットで言語種別を取得できるので、highlightメソッドを実行する準備が整う。
SSG時にサーバーサイドでハイライトするソースコードの実装はこんな感じ。


  private highlightContent(
contentParts: { richEditor: string; language: string; sourceCode: string }[]
): string {
let content = "";
contentParts.forEach(({ richEditor, language, sourceCode }, index) => {
if (index > 0) {
content += "<br />";
}
content += richEditor;
if (!LANGUAGES.includes(language)) {
return;
}
content += "<br />";
content += `<pre class='language-${language}'><code class='language-${language}'>`;
sourceCode.split("\n").forEach((lineOfsourceCode) => {
content += highlight(lineOfsourceCode, languages[language], language);
content += "<br />";
});
content += "</code></pre>";
});
return content;
}

URL: https://github.com/ok8omk/iam.oke.tokyo/blob/master/lib/post.ts

繰り返しフィールドとして本文のフィールドを複数の値に分割しているので、
それらの値に対してハイライトをかけつつ1つの本文にまとめ直す処理をしている。

書きたかったことは以上で終わり。
これからくるりの京都音楽博覧会2020見るので楽しみだぁ。では。