MarkdownでもWordPressでも使えるGatsbyJSでの構文ハイライト

MarkdownでもWordPressでも使えるGatsbyJSでの構文ハイライト
カテゴリ
技術
タグ
GatsbyJS
React
syntax highlight

概要

GatsbyJS での汎用的な構文ハイライト(syntax highlight)の実装方法を紹介します。
本ブログは Markdown ファイルと WordPress の両方のコンテンツを Gatsby で表示していて、その場合に使えるやり方です。もちろんどちらかのみの場合でも OK です。
react-syntax-highlighterというプラグインを使って、それに上手くコンテンツデータを渡すということをやってます。

参考にしたサイトはこちら。
https://dimitri.codes/adding-syntax-highlighting-wordpress-gatsby/

また、実コードが見たい方は、実際の変更コミットはこちらを見てください。
https://github.com/nisioka/sun0range.com/commit/7a469363fc9b309f720cf7903ba1da5ce39f9895

前提・準備

Gatsby でのコンテンツを表示に、dangerouslySetInnerHtml={{__html=content}} を使用していることを想定していて、それを書き換えていきます。 また、TypeScript も使用しているので以降のコードも.ts もしくは.tsx です。

まずは依存関係のインストールを行ってください。(TypeScript なので、開発のしやすさのために@types も dev に入れてます。)

1npm install --save html-react-parser react-syntax-highlighter
2npm install --save-dev @types/react-syntax-highlighter
3

実装

まず、修正前の実装イメージを示します。post.contentがコンテンツデータで、それを dangerouslySetInnerHTML に渡して表示しているとします。

1import * as React from "react"
2
3const BlogPostTemplate = () => {
4  // ノイズになるため省略。ここでpostのデータを取得したりしている。
5
6  return (
7    <>
8      // ノイズになるため省略。実際は他にもレイアウトしている。
9      <section dangerouslySetInnerHTML={{ __html: post.content }} />
10    </>
11  )
12}
13

修正後のコードが下記です。

1import * as React from "react"
2import { Link, graphql } from "gatsby"
3
4import parse, { domToReact } from "html-react-parser"
5import SyntaxHighlighter from "react-syntax-highlighter"
6import { androidstudio } from "react-syntax-highlighter/dist/cjs/styles/hljs"
7
8const BlogPostTemplate = () => {
9  // ノイズになるため省略。ここでpostのデータを取得したりしている。
10
11  return (
12    <>
13      // ノイズになるため省略。実際は他にもレイアウトしている。
14      <section>{parse(post.content, { replace: replaceCode })}</section>
15    </>
16  )
17}
18
19const replaceCode = (node: any) => {
20  if (!node) return node
21  if (node.name === "pre") {
22    const dom = domToReact(getCode(node))
23    let result = ""
24    switch (typeof dom) {
25      case "string":
26        result = dom as string
27        break
28      case "object":
29        if (Array.isArray(dom)) {
30          // React.JSX.Element[]
31          const elmArr = dom as React.JSX.Element[]
32          elmArr.map(elm => {
33            if (elm.props && elm.props.children) {
34              result += elm.props.children as string
35            }
36          })
37        } else {
38          // React.JSX.Element
39          const elm = dom as React.JSX.Element
40          if (elm.props && elm.props.children) {
41            result = elm.props.children as string
42          }
43        }
44        break
45    }
46
47    return (
48      node.children.length > 0 && (
49        <SyntaxHighlighter
50          style={androidstudio}
51          language={getLanguage(node)}
52          showLineNumbers={true}
53        >
54          {result}
55        </SyntaxHighlighter>
56      )
57    )
58  }
59}
60
61const getLanguage = (node: any) => {
62  function getClassInLanguage(className: string) {
63    let result = ""
64    className.split(/\s+/).forEach(s => {
65      if (s.startsWith("language-")) {
66        result = s.replace("language-", "")
67        break
68      }
69    })
70    return result
71  }
72
73  if (node.attribs.class && node.attribs.class !== "wp-block-code") {
74    return getClassInLanguage(node.attribs.class as string)
75  } else if (node.children[0]?.attribs?.class) {
76    return getClassInLanguage(node.children[0].attribs.class as string)
77  }
78  return "java" // default
79}
80
81const getCode = (node: any) => {
82  if (node.children.length > 0 && node.children[0].name === "code") {
83    return node.children[0].children
84  } else {
85    return node.children
86  }
87}
88

上記コードについて説明します。

  1. 元々dangerouslySetInnerHTMLを使っていたものを、html-react-parserライブラリのparse関数を使うように変更します。
  2. parse関数は HTML を React コンポーネントに変換し、後述する自作のreplaceCode関数を使用して<code>ブロックを検出します。それ以外はそのままです。
  3. replaceCode関数はコードブロックをハイライト表示するための処理を行います。
    1. parse関数が DOM 要素を node として一つづつ渡してくるので、node が「<pre>タグ内に<code>の子を持つ要素」以外の要素の場合は、置き換えせずそのままです。
    2. <pre>タグ内に<code>の子を持つ要素」である場合、その中のコードを取得し、react-syntax-highlighterライブラリのSyntaxHighlighterコンポーネントで整形します。
    3. SyntaxHighlighterコンポーネントでは、言語やスタイルが選べて、
      1. languageにはここにある言語を指定します。その言語の指定の仕方はメタ情報として付与されている html の class 名を使って自作のgetLanguage関数で判定しています。
        1. Markdown では、 ```javascriptというようにコードブロックの先頭に指定します。
        2. WordPress では,「高度な設定」の「追加 CSS クラス」にlanguage-XXXというように XXX に言語を指定します。(※1)
      2. styleにはここにある好きなスタイルを指定します。色々と試して好きな見た目を選んでください。

※1: WordPress の「高度な設定」の例。 wordpressExample

終わりに

以上で、Gatsby アプリケーションを実行し、構文ハイライトされたコードを確認できるはずです。本記事内のコードも構文ハイライトされて見えているはずです。 これで、WordPress と Gatsby で構文ハイライトを実装しました。完全な例に興味がある場合は、このブログのソースコードを 以下の GitHub で確認できます。 https://github.com/nisioka/sun0range.com/blob/master/src/templates/blog-post.tsx



関連記事

  1. 静的サイトでの動的なサイト内検索機能の実装を紹介

    featured/search.webp

    はじめに 本ブログはGatsbyJS…

  2. AWSにデプロイしていたWordPressサイトをGatsbyJSによるGitHub Pagesでの静的サイトへ移行

    featured/wordpress2gatsby.webp

    はじめに 本ブログは WordPress を AWS にデプロイして運用していましたが、GatsbyJS を使って GitHub Pages…