動的に生成されたソースコードにhighlight.jsを荒業で導入する
Section: Technology

この記事は 広島大学ITエンジニア Advent Calendar 2020の6日目です。

昨日アドベンドカレンダーの5日目を書くためにサイトを作成したのですが、とてもじゃないほどデザインがひどく大変読みにくかったので、highlight.jsの導入を中心にデザイン変更の内容を書いていきます。

highlight.jsの導入

highlight.jsを導入することで、コードをハイライトすることができます。JavaScriptライブラリでは他にもGoogle Code PrettifyやPrism.jsなどがあったのですが、メンテナンスされていなかったり、導入が少し面倒であるなどの理由で、こちらを導入することにしました。

<link rel="stylesheet"
      href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.4.1/styles/default.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.4.1/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>

一般的には以上のコードをHTMLの<head>内に入れることで、簡単に導入することができます。最新のCDNはこちらにあります。

上手くいかない場合

原因

document.create_element("div").innerHTML = code;

もし以上のようにinnerHTMLを用いたりして動的にソースコードを挿入している場合、ページがロードされた後にしかハイライトをしないため、上手くいきません。

自分は前回の記事にあるように、どうしてもリアルタイムでMarkdownからHTMLに変換したいという謎願望により、YewでinnerHTMLを使用していました。そのためソースコードのハイライトが上手く反映されませんでした。

対処

highlight.jsのライブラリAPIにhighlightBlock(block)というhljsオブジェクトの関数が用意されています。これをDOMを生成した後に実行するとソースコードがハイライトされるようになります。

var pre_code_nodes = document.querySelectorAll("pre code");

for(var i = 0; i < pre_code_nodes.length; ++i){
    hljs.highlightBlock(pre_code_nodes[i]);
}

以上のようにDOMを生成した後に実行すると、<pre><code>内のソースコードがハイライトされます。

またまた問題点

自分はJavaScriptが苦手で使い方がわからず、苦戦し今回の原因を見つけることで精一杯で、アドベントカレンダーの公開予定日となり、記事を早急に書く必要がありました。(rustwasmの本にも書いてある通り、JavaScriptの知識があった方がいいらしいです...)

wasm_bindgen`を使ってFFI的なのをする方法をあまり調べられなかったので、どうしようかと思っていたら、JavaScriptで簡単に代用する方法を思いつき、今だけ代替え案として実装にすることにしました。

function initHLJS(){
    var pre_code_nodes = document.querySelectorAll("pre code");

    for(var i = 0; i < pre_code_nodes.length; ++i){
        hljs.highlightBlock(pre_code_nodes[i]);
    }
}

setInterval("initHLJS()", 3000);

どのタイミングでDOMが生成されるかわからないとき、以上のような荒業(非推奨)で簡単に実装できます。

終わりに

自分の場合Rustを使用しているため実装の観点からsyntectというRust製のシンタクスハイライトクレートを使用する手段もありました。しかしwasmファイルのデータが増大することを考慮して今回はhighlight.jsで対処することにしました。

皆さんが読んでいるころにはちゃんとwasm_bindgenを用い実装しているはずです。 (12/8追記:wasm_bindgenを用いた実装に変更しました)

焦ってはいいコードが書けず結局書き直すことになるので、アドベントカレンダーの記事は前以て完成させておくべきだということを学びました。次回はそうならないようにします...

最後までお読みいただき、ありがとうございました。