WordPress でブログを運用していたとき、記事中のソースコードのシンタックスハイライトは Code Syntax Block というプラグインを利用していました。このプラグインは、Prism を WordPress のブロックエディタで利用するためのラッパーのようなものです。表示できる言語も豊富で、テーマカラーにお気に入りの one dark もあったので使っていました。

エディタ側の書き味が非常に良いので、Next.js に以降したからと言ってこの書き味は捨てたくありません。そのまま利用できないものか中身を探ってみました。JavaScript ライブラリのラッパーなので、おそらく PHP で HTML を描画した後に何かを実行しているはずなので、そのまま利用できるはず、という予測です。

Next.js を導入する前の WordPress の HTML ソースを覗いてみると、Prism に関連しそうなソースコードを3個所発見することができました。

<link rel='stylesheet' id='mkaz-code-syntax-prism-css-css'  href='https://wp.katsushi-ougi.com/wp-content/plugins/code-syntax-block/assets/prism-onedark.css?ver=1652679827' type='text/css' media='all' />

<script type='text/javascript' id='mkaz-code-syntax-prism-js-js-extra'>
/* <![CDATA[ */
var prism_settings = {"pluginUrl":"https:\/\/wp.katsushi-ougi.com\/wp-content\/plugins\/code-syntax-block\/"};
/* ]]> */
</script>

<script type='text/javascript' src='https://wp.katsushi-ougi.com/wp-content/plugins/code-syntax-block/assets/prism/prism.js?ver=1652679827' id='mkaz-code-syntax-prism-js-js'></script>

どうやらこれらを Next.js に貼り付けるだけで上手くいきそうです。まずは _document.tsx の Head タグに CSS を貼ります。

import Document, { Html, Head, Main, NextScript } from "next/document";

class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head>
          <link
            href="https://fonts.googleapis.com/css2?family=Roboto+Condensed%3Awght%40400%3B700&#038;display=swap&#038;ver=v1.0.0"
            rel="stylesheet"
          />
          <link
            rel="stylesheet"
            id="mkaz-code-syntax-prism-css-css"
            href="https://wp.katsushi-ougi.com/wp-content/plugins/code-syntax-block/assets/prism-onedark.css"
            type="text/css"
            media="all"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

次に Layout コンポーネントに グローバルスクリプトを追加します。

import Script from "next/script";

const Layout: FC<LayoutProps> = ({ children }) => {
  ...

  return (
    <>
      <div className="flex h-[100%] flex-col content-between">
        ...
        <GlobalFooter />
        <Script
          id="mkaz-code-syntax-prism-js-js-extra"
          dangerouslySetInnerHTML={{
            __html: `
var prism_settings = {"pluginUrl":"https:\/\/wp.katsushi-ougi.com\/wp-content\/plugins\/code-syntax-block\/"};
  `,
          }}
        />
      </div>
    </>
  );
};

export default Layout;

外部スクリプトは、Script コンポーネントを利用して、dangerouslySetInnerHTML の中に書くのがポイントですね。

prism.js もここでロードしたら? と思うと思われますが、そうしてしまうと、ページ訪問時にしかコードシンタックスが効きません!

おそらく、prism.js は js がロードされたタイミングでしか初期化を実行していないのでしょう。window オブジェクトに何かインスタンスが生えてないかな?とか調べてたのですが、もっと簡単な方法で解決しました。

ページ表示時に、prism.js をロードして、ページ離脱時に prism.js を削除しました。

該当ページのスクリプトに以下のように追記しました。

  useEffect(() => {
    const s = document.querySelector("#prism");
    if (s) {
      document.head.removeChild(s);
    }

    importScript("/prism.js", "prism");
  }, [router.asPath]);

  if (!post) return null;

importScript メソッドの中身です。

const importScript = (url: string, id: string) => {
  return new Promise((resolve, reject) => {
    let s = document.createElement("script");
    s.id = id;
    s.onload = () => {
      resolve("success");
    };
    s.onerror = () => {
      reject();
    };
    s.src = url;
    document.head.append(s);
  });
};

export { importScript };

動的にスクリプトファイルをロードする、というのは React 以前もよく使っていたので、知識が役立ちました。