Rehype プラグインで Prism.js によるシンタックスハイライトを自動適用する

コードのシンタックスハイライトというと highlight.js をよく使っていたが、同じくらい人気のライブラリとして Prism.js というモノもあるらしい。

通常、Prism.js を単体で使う時は、CSS と JS を読み込んだ上で、次のようにコーディングすれば、シンタックスハイライトが有効になる。

<pre><code class="language-css">p { color: red }</code></pre>

precode をネストして、code 要素の方に language-【言語名】 と書くだけ。

今回はこの Prism.js を Rehype プラグインとして利用できる、rehype-prism を紹介する。

目次

rehype-prism をインストールする

rehype-prism は、HTML を出力する前に Prism.js によるシンタックスハイライトを効かせるための Rehype プラグイン。元のデータは Markdown でも、HTML でも使える。

以降の例では、Markdown から HTML に変換する際に Prism.js を適用させる例で説明する。

# Markdown を HTML へと変換するために必要な最低限の Unified・Remark・Rehype 関連パッケージ
$ npm install --save-dev unified remark-parse remark-rehype rehype-stringify

# Prism.js を適用する Rehype プラグイン
$ npm install --save-dev @mapbox/rehype-prism

サンプルコード

Markdown の方には、次のようにコードブロックを書く。

# サンプルのため、バッククォートを全角で記述しています

実際は半角のバッククォートで記述してください。

```css
p { color: red }
```

↑コードブロック。

そして次のようにパースする。

const fs = require('fs').promises;

const unified = require('unified');
const remarkParse = require('remark-parse');
const remarkRehype = require('remark-rehype');
const rehypePrism = require('@mapbox/rehype-prism');
const rehypeStringify = require('rehype-stringify');

(async () => {
  const inputMarkdown = await fs.readFile('./example.md', 'utf-8');
  
  const processor = unified()
    .use(remarkParse)
    .use(remarkRehype)
    .use(rehypePrism, {
      ignoreMissing: true  // 存在しない言語名を書いていた時に無視する
    })
    .use(rehypeStringify);
  const result = await processor.process(inputMarkdown);
  
  const outputHtml = result.contents;
  await fs.writeFile('./result.html', outputHtml, 'utf-8');
})();

すると次のような HTML が生成される。

<h1>サンプルのため、バッククォートを全角で記述しています</h1>
<p>実際は半角のバッククォートで記述してください。</p>

<pre class="language-css"><code class="language-css"><span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token color">red</span> <span class="token punctuation">}</span>
</code></pre>

<p>↑コードブロック。</p>

少々分かりにくいが、pre および code 要素に language-css クラスが付与され、キーワードごとに span.token が付与されている。

この CSS クラスに応じてカラーリングするには、Prism.js 公式サイトで配布されている CSS ファイルを取得・適用する必要がある。別途 rehype-document などを使って HTML 化する際に外部 CSS ファイルを読み込む link 要素を付け足しておくと良いだろう。

なお、自分は以下にある Monokai テーマを利用していたりする。

言語名にエイリアスを当てたい

コレは自分の癖なのだが、はてなブログでは sh という言語名で、シェルスクリプトがシンタックスハイライトされる。しかし Prism.js には sh という言語名の定義がなく、shell もしくは bash と書く必要がある。

Prism.js 本体には、言語名のエイリアスを割り当てられる機能があるのだが、@mapbox/rehype-prism パッケージが Prism.js 本体を隠蔽しているため、割り当てられない。

現状なんとかするには、かなり荒業だが、node_modules/ 配下のスクリプトを直接書き換えてしまうことで、強引にエイリアスを追加することはできる。

本稿執筆時点の最新版である、@mapbox/rehype-prism@0.5.0 を前提とする。

// 前略…
const refractor = require('refractor');

module.exports = (options) => {
  // …中略…
  function visitor(node, index, parent) {
    // …中略…

    let result;
    try {
      parent.properties.className = (parent.properties.className || []).concat(
        'language-' + lang
      );
      // ↑既存の行
      
      // ココにエイリアスを追加するコードを挿入する
      // 以下で、「sh」と打てば「bash」相当のシンタックスハイライトが有効になる
      refractor.alias('bash', 'sh');
      
      // ↓既存の行
      result = refractor.highlight(nodeToString(node), lang);
    } catch (err) {
    // 以下略…
  }
};

こんな感じで、@mapbox/rehype-prism のメインファイルに

このように行を追加すれば良い。

このぐらいなら Fork して作れそうだな…。