Gatsby + microCMS なブログでシンタックスハイライトを行う

前回記事にしたサイトですが、あれから色々手直しをしています。

https://qiita.com/tsuchinoko0402/items/e47b2e05c47773130b11

手始めに、[microCMS](https://microcms.io/) のリッチエディタで作成したコンテンツをブログページで表示すると、コードブロックがそのままベタ書きになっていて見にくかったので、シンタックスハイライトをするようにしました[^1]。

[^1]: Markdown で書いた記事の部分は [gatsby-remark-prismjs](https://www.gatsbyjs.com/plugins/gatsby-remark-prismjs/) を利用すれば簡単に適用できたのですが、 microCMS で作成したものを表示している箇所ではこれは適用されませんでした。

![名称未設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/753251/21c0921e-a2d8-9b96-a766-8b5f8d99cba3.png)

# 参考にした方法

- microCMS の公式ブログで記事になっていた方法を参考にしました：
    - こちらの記事では、 Nuxt.js の実装例が書かれていますので、 Gatsby に読み替えて実装します。
    - Nuxt.js は SSR であるため、高速化のため、サーバーサイドで処理することを想定して書かれていますが、 Gatsby は SSG なので、そのあたりは気にせずやります。

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

# ほぼ結論

以下のような方法でブログ記事のコード部分にシンタックスハイライトを行います：

- [cheerio](https://www.npmjs.com/package/cheerio) を用いて、該当のソースコード部分を抜き出す
- 抜き出した部分にクラス `hljs` を付与し、 [highlight.js](https://www.npmjs.com/package/highlight.js) が適用されるようにする

# 実装方法
## microCMS で作成した記事の表示方法とコード箇所

- microCMS のリッチエディタで作成された記事の内容は以下の GraphQL クエリで取り出せます：

```
query {
  allMicrocmsPost {
    nodes {
      content
    }
  }
}
```

- GraphQL のクエリ結果は以下のような形です：

```
{
  "allMicrocmsPost": {
    "nodes": [
      {
        "content":"...<pre><code>$ firebase init\n ...</code></pre>"
      }
    ]
  }
}
```

- クエリで取り出した `content` の内容を React の [dangerouslySetInnerHTML](https://ja.reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml) に渡して表示しています。

```
return (
  <div dangerouslySetInnerHTML={{ __html: content }} />
)
```

- `content` の中で、コードブロックは以下のような HTML で入っています：

```html
<pre>
  <code>
    コードの中身
  </code>
</pre>
```

- つまり、**`pre` 要素内の `code` 要素を抽出**してスタイルを適用すれば良いことになります。

## 要素を取り出してシンタックスハイライトを適用するための実装

- 上記の microCMS 公式ブログの記事で紹介されている記事が以下のとおりです：
    - `cheerio` と `highlight.js` のライブラリを読み込みます
    - リッチエディタ部分から `pre code` な要素を取り出します
    - 取り出した部分が `highlight.js` の処理対象となるように、クラス `hljs` を付与します
    - 最終的には、クラスを付与して書き換えた記事部分を dangerouslySetInnerHTML に渡すことになります

```javascript
import cheerio from 'cheerio';
import hljs from 'highlight.js'

// 略

const $ = cheerio.load(data.body);    // data.bodyはmicroCMSから返されるリッチエディタ部分
$('pre code').each((_, elm) => {
  const result = hljs.highlightAuto($(elm).text());
  $(elm).html(result.value);
  $(elm).addClass('hljs');
});

console.log($.html());    // ハイライト済みのHTML
```

## Gatsby での実装方法

- ↑を参考に進めます。
- まずは、必要なライブラリをインストールします

```shell
$ yarn add cheerio
$ yarn add highlight.js
```

- ブログ記事部分のテンプレートを以下のように書き換えます（シンタックスハイライトに関係ない箇所は省略しています）：

```typescript
import React from "react"
import { graphql, useStaticQuery } from "gatsby"
import cheerio from "cheerio"
import hljs from "highlight.js"
import "highlight.js/styles/solarized-dark.css"

const PostContent: React.FC= () => {
  const data = useStaticQuery(
    graphql`
      query {
        allMicrocmsPost(filter: {title: {eq: "hoge"}}) {
          nodes {
            content
          }
        }
      }
    `
  )

  const contentBody = cheerio.load(data.allMicrocmsPost.nodes.content)
  contentBody("pre code").each((_, elm) => {
    const result = hljs.highlightAuto(contentBody(elm).text())
    contentBody(elm).html(result.value)
    contentBody(elm).addClass("hljs")
  })

  return (
    <article>
      <div dangerouslySetInnerHTML={{ __html: contentBody.html() }} />
    </article>
  )
}

export default PostContent
```

- これでビルドすると、コードブロック部分にシンタックスハイライトが行われています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/753251/a043e72b-4ca7-9369-092e-86fa8b5796da.png)


# おまけ：その他の方法

- microCMS のブログ記事のコンテンツ部分のデータ形式気を工夫することで、コードブロックに特定のクラス名を付与して、シンタックスハイライトを行う、という方法もあります。

https://dev.eringiv3.com/blog/post/release_blog_gatsby_and_microcms

- たぶん、この方が自由度があって良い気がしますが、リッチエディタだけで済ませようと思うとこの記事で紹介した方法になるのではないかと思います。