概要

このブログは、マークダウンをHTMLへパースしてくれるMarked.jsを使用して全てマークダウンで記述しています。

しかしやはりブログでコードを表示するとなると、シンタックスハイライトを効かせたいですよね!
ということで、highlight.jsを導入してみました。

例えばApache Groovyなら、

assert 60 == (1..10).findAll {
    it % 2 == 0
}.collect {
    it * 2
}.inject {l, r ->
    l + r
}

// コメントだよ
class Test {
    main (args) {
        println('Hello Apache Groovy')
    }
}

と言ったように、シンタックスハイライトされるだけでなく、背景なども含め思った以上にスッキリした感じでコードが見えるようになりました。
大満足です!

導入方法

highlight.jsの導入は非常に簡単です。
デフォルトだと23のプログラミング言語がサポートされているものをCDN経由で利用できます。
なので、

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/darcula.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>

<head>タグに指定しておいて、

hljs.initHighlightingOnLoad()

というメソッドを叩いてあげればそれだけでOKです。

デフォルトだと、highlight.jsが<pre><code></pre></code>というDOMを上記のJavaScriptコードを実行したページ内から全部探しだしてくれて、どの言語用のシンタックスハイライトを適用すべきか、ということまで自動で行ってくれます。

Marked.jsはコードを

```
なんかマークダウン。
```

という風に3つのバッククォーテーション(もしくはチルダ)で囲んでおけば、

<pre>
  <code>なんかマークダウン</pre>
</code>

というHTMLに変換されるので、hightlight.jsとの相性も抜群です。

カスタマイズ

さて、highlight.jsはコードがどのプログラミング言語なのかを自動でチェックしてくれてさらにMarked.jsとの相性も抜群、ではあるのですが、やはり自動での言語チェックは完璧ではありませでした。
特にPythonコードは結構なパターンでSQLと認識されてSQL用のシンタックスハイライトが適用されてしまいました。

自分で言語を指定する

hightlight.jsはページ全体から<pre><code></pre></code>という部分を探しだしてくれて、このコードがどのプログラミング言語のものなのかを判断してから<pre><code class="hljs sql"></pre></code>という風に書き換えます。
ということはつまり、<code>タグのクラスの指定を自分でしてあげればOKということですね。

ただし、Marked.jsは当然マークダウンから<pre><code></pre></code>を自動的に生成してくれているので、直接上記のようにクラスを指定することは出来ません。

そこで、以下のようにMarked.jsのレンダリング時の挙動をカスタマイズしてあげました。

let renderer = new marked.Renderer()
// codeタグにHighlight.js用のクラスを付与する。
// 先頭行に !!!言語!!! というフォーマットがあればそれを利用する
renderer.code = function (code) {
  const lf = `\n`
  const mdLines = code.replace(/\r\n/g, lf).replace(/\r/, lf).split(lf)
  const syntaxInfo = mdLines[0]
  const matched = syntaxInfo.match(/^!!!(.+)!!!$/)
  if (matched) {
    const extension = matched[1]
    // 先頭行は言語の指定なので省く
    const text = mdLines.slice(1).join(lf)
    return `<pre><code class="hljs ${extension}">${text}</code></pre>`
  } else {
    // 自分で言語を指定していない場合は自動検出を無効化する
    return `<pre><code class="nohighlight">${code}</code></pre>`
  }
}
return renderer

私の場合、マークダウンでコードを書く際に、先頭行に
!!!python!!!
という感じで、3つのビックリマークで囲まれた部分に適用させたいhightlight.jsのクラス名(言語名)を記述するようにして、その文字列をそのままcodeタグのクラスに渡してあげるようにしました。

hightlight.jsを実行

さて、このブログはNuxt.jsを利用していますので、マークダウンがサーバサイドでHTMLに変換されることも有りますし、ブラウザ側で変換されることも有ります。
これに対応するために、Vue.jsでは以下のようにしてブラウザ側でhightlight.jsを実行するようにしてあげました。

export default {
  async asyncData ({params}) {
    // 関係ないので省略
  },
  mounted: function () {
    /* eslint-disable */
    // Highlight.jsを実行
    hljs.initHighlighting()
    /* eslint-enable */
  }

initHighlightingOnLoadの代わりにinitHighlightingを実行するように変更しています。
コレでSSRのタイミングでもブラウザのタイミングでも問題なく自分で指定した言語でシンタックスハイライトが効くようにようになりました。