github octcat icon
twitter bird icon
rss icon

PrismJSでSyntax Highlight

2022-01-30

code editor

Photo Credit: Christopher Robin Ebbinghaus

最初にブログを作成した際にも、gatsby-remark-prismjsを参考にPrismJSを入れようとしたのですがうまくいかず、この時点で、tokenizeができていないんだなー。ということだけは分かっていたのですが、どうしたら良いのかわからずとりあえずリリースを優先させるために最低限で凌いでいました。 しかし、やはりエディタのようなSyntax Highlightが効いていないとコードが読みにくいよね。ということで、PrismJSを入れ直しました。

gatsby-node.js

// ./gatsby-node.js
import { createFilePath } from "gatsby-source-filesystem";
import path from "path";

export const onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions;

  if (node.internal.type === "Mdx") {
    const value = createFilePath({ node, getNode });

    createNodeField({
      name: "slug",
      node,
      value: `${value}`,
    });
  }
};

export const createPages = async ({ graphql, actions, reporter }) => {
  const { createPage } = actions;

  const result = await graphql(`
    query {
      allMdx {
        edges {
          node {
            id
            fields {
              slug
            }
          }
        }
      }
    }
  `);

  if (result.errors) {
    reporter.panicOnBuild('ERROR: Loading "createPages" query');
  }

  const posts = result.data.allMdx.edges;

  posts.forEach(({ node }) => {
    createPage({
      path: node.fields.slug.replace("/blog/", ""),
      component: path.resolve(`./src/templates/post.tsx`),
      context: { id: node.id },
    });
  });
};

まずは今回の肝になる gatsby-node.jsについてです。 gatsby-node.js内に書いたコードはビルドが走る際に1度だけ実行されます。 今回の例だと、onCreateNodeで.mdファイルをFile nodesとしてdata layerに追加して、それらを、createPagesで./src/pages/{mdx.slug}.tsxに渡しています。

マークダウンファイルをそのままHTMLファイルに変換するだけであれば、"gatsby-plugin-mdx"プラグインを使えばgatsby-node.jsファイルを作成しないでも、./src/pages/配下に{mdx.slug}.tsxのようなファイルを作成して動的にページを生成すればいいだなので話は簡単です。

しかし、シンタックスハイライトを効かせるためにはコード内の単語や記号1つずつそれぞれに対して適切なCSSを効かせる必要があります。

試しに、私のブログでdevtoolsを開いてコードブロック内のCSSを確認してみていただくと確認できますが、コード内の単語が1つづつ<span>タグで切られて、

<span class="token comment">// ./gatsby-node.js</span>
<span class="token keyword">import</span>
<span class="token punctuation">{</span>

のような形になっているかと思います。

実現する流れとしては、以下の順番で処理していく必要があるのですが、この役割をPrismJSが担っているわけです。

  1. マークダウンをtokenに変換
  2. それぞれのtokenに対して適切なクラスを設定
  3. HTMLに変換

gatsby-remark-prismjsプラグインのソースコードまで潜って確認できていないので、どの時点でtokenizeがなされているのか定かではないのですが、おそらく、createPageでページを生成した際にgatsby-remark-prismjsプラグインが走るようになっているのではないかなー。と想像しています。

そのため、createPageをしないで、{mdx.slug}.tsx内で直接MdxProviderMdxRendererを使ってマークダウンファイルからHTMLファイルへの変換を行った場合にはtokenizeがなされず、シンタックスハイライトのCSSを効かせることができませんでした。

という事情があり、今回は./gatsby-node.jsファイルで動的にGatsby Node APIを使いつつ動的にページを生成して、そのタイミングでPrismJSを動かすことでtokenizeも行うことができるようにしています。(それぞれのタイミングはしっかり理解できていないので、どこかでちゃんと調査したいところです。)

./src/templates/post.tsxの作成

createPageでせっかくページを生成するようにしたので、./src/pages/{mdx.slug}.tsxはやめて、./src/templatesディレクトリ配下に生成したデータをラップするためのコンポーネントを作成していきます。

フォルダ構成はGatsby Project Structureを参考にしました。

ファイルの中身とやっていることは何も変えておらず、gatsby-node.jsファイルで生成したidを./templates/post.tsxファイルに渡して動的にクエリを生成しているだけです。

./src/pages/{mdx.slug}.tsxファイルでは、GraphQLクエリの中で自動的にidが付与されており活用することができていましたが、./templates/配下では自動的にidが使える状態にあるわけではないようで、contextで渡してやる必要がありました。

gatsby-config.js

{
  resolve: "gatsby-plugin-mdx",
  options: {
    gatsbyRemarkPlugins: [
      {
        resolve: `gatsby-remark-prismjs`,
      },
    ],
    extensions: [`.md`, `.mdx`],
  },
},

gatsby-remark-prismjsを参考に、上記のような設定をgatsby-config.jsに入れます。 これは最低限の設定なので、任意でオプションは追加していけばいいと思います。

cssの設定

ここまできたら、あとは./gatsby-browser.jsでprismjsのcssテンプレートを読み込めばOKです。 prism-themesがたくさんあるので、この中から好きなものを選んで、以下のようにインポートすればシンタックスハイライトが効くようになっているはずです。

//./gatsby-browser.js
import "prismjs/themes/prism-solarizedlight.css"

私はシンタックスハイライトの色を少し変えたかったので、./src/styles/prism-vsc-dark.cssファイルを作成して、一部cssを書き換えて活用することにしました。


シンタックスハイライトも効くようになって、やっとブログっぽくなってきました。 次回は目次を追加していきたいと思います。