• 作成:

highlight.jsを全ての言語に対応させる

このブログのシンタックスハイライトにはhighlight.jsを使っている.

膨大な言語に対応し, また多くのスタイルに対応しながらも軽量なフレームワークである.

このライブラリをセットアップするときに多少ハマったので, 問題点と手順をメモしておこうと思う.

最終的な方法だけ知りたい人はbrowserifyの項まで飛ばして良い.

CDNは少数の言語にしか対応していない

おそらく大半の人は, Getting highlight.jsに載っているように,

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

と書けばそれで満足できるはずだ.

しかし, highlight.jsのcdnでの配信はHow to use highlight.jsに書かれている通り,

The CDN-hosted package doesn't have all the languages. Otherwise it'd be too big.

common languageにしか対応しておらず, その数はわずか22個である.

その中には当然haskellは入っておらず, 1番好きで1番書く言語がhaskellである私にとっては, 当然満足できるものではない.

個別に

<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.4.0/languages/go.min.js"></script>

のように読み込み言語を追加していく方法もあるが, それをやるとscriptタグが100個以上になりネットワークの効率が非常によくない.

custom packageは面倒

当然highlight.js側でもそれには対策をとっていて, custom packageという形で使う言語だけを選んでカスタムしたパッケージをインストールできる.

しかし, この解決法には以下の問題がある

  • 私はそこそこの言語マニアであり, 当然全ての言語をサポートして欲しいので, 全部のチェックボックスをチェックするのは面倒
  • アップデートした時にダウンロードし直すのは面倒

なので, 他の解決法を探すことにした.

npm installするだけではブラウザでは動かない

npm install highlight.js

すれば, 全ての言語に対応したhighlight.jsがワンコマンドで手に入ると思い, npm installして, node_modulesのhighlight.jsにパスを通したが, 動かずハマって数十分悩んでいた.

よく読んでみるとこのhighlight.jsは

on the server through the API.

の通りサーバーサイド用のhighlight.jsであり, requireを使っているindex.jsを実行して, サーバーサイドでハイライト変換を行うためのインストール方法であった.

つまり, それをブラウザでそのまま動かしても, 当然動かないのである.

サーバーサイド用のライブラリなので, サーバーサイドで変換すれば問題ないわけだが, クライアントサイドで動かすことしか考えていなかったので, その案は没になった.

ソースからビルドする

もう色々面倒になってきたので,

githubからソースを撮ってきて, ビルドして突っ込むことにした.

node tools/build.js -t browser

するとブラウザ向けにビルドできるので, ビルドされたhighlight.pack.jsを適当なディレクトリに突っ込む.

速度のために非同期で読み込むので, 以下のようにhljsが定義されるまで待ってinitHighlighting.

declare namespace hljs {
    export function initHighlighting(): void;
}

try {
    let hljsWaitLoad = () => {
        if (typeof hljs !== "undefined") {
            hljs.initHighlighting();
        } else {
            setTimeout(hljsWaitLoad, 100);
        }
    };
    hljsWaitLoad();
} catch (e) {
    console.error(e);
}

この記事を書くまでは, この方法で配信していた.

しかし, これは全部のチェックボックスをチェックするのは面倒は解決できていても, アップデートした時にダウンロードし直すのは面倒は解決できていない.

なので, npm経由での管理にもう一度切り替えることにした.

サーバーサイドでの変換

せっかくhakyllを使って静的サイトにしているのだから, クライアントが軽くなることを期待してサーバサイドでシンタックスハイライトしたいところだが, 3時間ぐらい格闘して無理だったのでやめた.

node.jsでの動作を謳っていて, 実際対応してるみたいなのだが, node-jsdomなどを使っても内部のdocument参照を切り替える方法がわからなかった.

格闘記録はコミットに残っている. 気力が復帰したらやるかもしれない.

browserify

browserifyを使ってrequireを1ファイルにまとめる.

browserifyを使うと3時間使ってnode.jsでサーバーサイドシンタックスハイライトができなかった人でも

browserify -r highlight.js

するだけで標準出力に全部のscriptがまとめて出してくれるようになります, 最高ですね.

しかし, これだと肝心の初期化がされないため, 少し困ったことになります. モジュールにきちんとまとまってくれているため, 元々あったtypescriptでの非同期初期化も当然使えなくなります.

せっかくbrowserifyを使っているのだから, 1つのファイルにまとめてしまうことにします. そうすれば同期を気にする必要はありません.

highlight.jsの@typesはなぜか公開されていませんが, browserifyの@typesは普通に公開されています.

npm i @types/browserify --save

で型定義をインストールすれば, 普通にtypescriptでbrowserifyのrequireを使うことができます.

typescriptとbrowserifyのビルド連携は Integrating with Build Tools · TypeScript に載っているように, tsifyを使えば簡単にできます.

後はpackage.jsonにビルドコマンドを書いて終わり.

  "scripts": {
    "default.js": "browserify default.ts -p [ tsify ]"
  }

補足

当初はpandocは<code>ではなく<pre>の方に言語設定のためのclassを設定し, highlight.jsは<code>にclassを設定するのを推奨していたため, <pre>から<code>にclassを移すためのコードを書いていました.

しかし, highlight.jsはparentNodeのclassも言語判定に利用するため, そういうコードを書く必要はありません.

pandocのデフォルトの出力のままで問題ありません.

追記

今はyarn add @types/highlight.jsして以下のコードで良いです.

import * as hljs from "highlight.js";
hljs.initHighlighting();