Next.js (SSG) + MicroCMS のコードブロックにシンタックスハイライトをつける

# はじめに

現在ブログ投稿サイトを Next.js の SSG で作成しています。コンテンツは Micro CMS から取得しています。

この時にコードブロックにシンタックスハイライトをつける方法を残しておきたいと思います。

# 今回使うもの

- Next.js (`14.2.14`)
- cheerio (`^1.0.0`)
- highlight.js (`^11.11.0`)

# cherrio とは

HTMLパーサーです。

パーサーとはパースをするもののことで、パースは英語で parse で解析するという意味です。

コンピュータの世界でも同じ意味であり、このHTMLパーサーは HTML を解析して、プログラムで扱いやすいデータに変換してくれます。

以下のような特徴があるみたいです。

- jQuery をベースに作られている
- 解析が速い
- 柔軟性がある（ブラウザ側でもサーバー側でも動く）

> https://github.com/cheeriojs/cheerio?tab=readme-ov-file#features
> 
> 
> **❤ Proven syntax:** Cheerio implements a subset of core jQuery. Cheerio removes all the DOM inconsistencies and browser cruft from the jQuery library, revealing its truly gorgeous API.
> 
> **ϟ Blazingly fast:** Cheerio works with a very simple, consistent DOM model. As a result parsing, manipulating, and rendering are incredibly efficient.
> 
> **❁ Incredibly flexible:** Cheerio wraps around [parse5](https://github.com/inikulin/parse5) for parsing HTML and can optionally use the forgiving [htmlparser2](https://github.com/fb55/htmlparser2/). Cheerio can parse nearly any HTML or XML document. Cheerio works in both browser and server environments.
> 

# highlight.js とは

シンタックスハイライトしてくれる JavaScript ライブラリです。

シンタックスハイライトとというのはソースコード等が見やすくなるように意味別で色をつけることを意味します。

# インストール

以下コマンドでインストールします

```bash
npm install cheerio highlight.js
```

# HTML のロード

`cheerio` は前述した通り、ブラウザでもサーバー側でも動作する設計になっているので `jQuery` とは違って、`HTML` を明示的に読み込んであげる必要があります。

```tsx
.....
import * as cheerio from 'cheerio';

export default async function StaticArticlePage({
  params: { articleId },
}: {
  params: { articleId: string };
}) {
  const article = await getArticle(articleId);
  const $ = cheerio.load(article.body); // ここで CMS から帰ってきた article.body (HTML) をロードする
  console.log('HTMLです', $.html()); // これで該当ページの HTML が取れていれば OK

  
  return (
    ........
  );
}
```

# 要素の抽出とハイライト

コードブロックは以下のような `HTML` となっています。

```tsx
<pre>
	<code>
		......
	<code/>
</pre>
```

この pre内の `code` を抽出して、highlight.js を用いてハイライトします。

https://highlightjs.org/demo から各 `theme` がどのようなハイライトの仕方をするのか確認することができます。

```tsx
import hljs from 'highlight.js';
import 'highlight.js/styles/vs2015.css'; // 使いたい theme を選んで指定します。

$('pre code').each((_, elem) => { // 各 pre code に対して以下コールバックを実行
	const result = hljs.highlightAuto($(elem).text());
	$(elem).html(result.value);
	$(elem).addClass('hljs');
})
```

これで、ハイライト自体は当たるようになります。

しかし、これでは highlight.js 側でどのような言語で書かれたコードなのかを推論して、ハイライトが当たることになります。

そのため、誤推論が発生し、意図したハイライトの当たり方にならない可能性があります。（実際に私もやってみたところ、一貫性のないハイライトの当たり方になっていました。）

以下のようにコードを変えてみます。

```tsx
  $('pre code').each((_, elem) => {
    const className = $(elem).attr('class'); // pre 内の code タグの class 属性を取得する
    console.log('className:', className); // className: language-typescript という値が取れます
    const language = className?.replace('language-', ''); // language- を空文字にして言語だけを取り除く

    let result;
    if (language) {
      try {
        result = hljs.highlight($(elem).text(), { language });
      } catch (error) {
        result = hljs.highlightAuto($(elem).text());
      }
    } else {
      result = hljs.highlightAuto($(elem).text());
    }
    $(elem).html(result.value);
    $(elem).addClass('hljs');
  });

  article.body = $.html();
```

CMS 内でコードブロックに言語を指定することができるので、そこで指定してあげると `code` タグの `class` に `language-{言語名}` が付与されます。

この `{言語名}` を用いて、どの言語で書かれているかを `highlight.js` に教えてあげることで正確にハイライトしてくれるようになります。

`catch` ブロックや `else` ブロックはうまくいかなかったときに `highlightAuto` で適当な色をつけてあげるために入れています。

# まとめ

言語の指定をしてあげないと一貫性のないハイライトの当たり方になってしまうところから抜け出すのにかなり時間がかかってしまいました。

同じような状態になってしまった人に少しでも役に立てば嬉しいです。

# 参考文献

https://blog.microcms.io/syntax-highlighting-on-server-side/

https://www.newt.so/docs/tutorials/customize-code-block-using-cheerio