• 作成日時:
  • 更新日時:

このHakyllサイトのシンタックスハイライターをhighlight.jsからPygmentsに移行しました

highlight.jsへの不満

このサイトではhighlight.jsをコードのシンタックスハイライトに利用していました.

これには以下の問題がありました.

  • Emacs Lispに対応していない
  • フロントエンドでの変換なのでクライアントがアクセスするたびに無駄なオーバーヘッドがかかる

正直パフォーマンスはどうでも良いのですが,Emacs Lispに対応していないのは致命的です.

Pygmentsへの移行

PygmentsはPythonで書かれたシンタックスハイライターで多くの言語をサポートしているのが特徴です.

これでサーバ側でシンタックスハイライトを行うようにします.

Pandoc自体のシンタックスハイライトを無効化する

Pandoc自体にjgm/skylighting: A Haskell syntax highlighting library with tokenizers derived from KDE syntax highlighting descriptionsというKateのXMLを利用したシンタックスハイライト機能があります.

これもそこそこの言語に対応していて,普通に実用的なのですが,Emacs Lispには対応していません.

今回は本物のPygmentsを利用するのでこの機能はwriterHighlightStyleをNothingにすることで無効化します.

HTML内部のコードを変換する方法がわからない

てっきりPygments自体にHTMLのpre内に入ったコードを変換するコマンドがあるものだと思っていましたが,無いようですね.

コードブロックだけを渡す必要があるようです.

ブロックだけを置換する

bottomUpMというPandocデータ型をたどってブロックを受けたわすPandocの関数と,pandocCompilerWithTransformMというPandoc型を変換関数にかけるHakyllの関数を使えば変換関数にブロックを渡せます.

あとは拡張子は適当に類推しておわり.

pandocCompilerCustom :: Compiler (Item String)
pandocCompilerCustom
  = let extensions =
          disableExtension Ext_smart $
          enableExtension Ext_ignore_line_breaks $
          readerExtensions defaultHakyllReaderOptions
        transform (CodeBlock (_identifier, classes, _keyValue) str)
          = let fileExtension = takeExtension (unwords classes)
                fileKind = if null fileExtension then unwords classes else tail fileExtension
            in RawBlock (Format "html") <$>
               unixFilter "pygmentize"
               (["-f", "html"] <>
               if null fileKind then [] else ["-l", fileKind])
               str
        transform x = return x
    in pandocCompilerWithTransformM
       defaultHakyllReaderOptions
       { readerExtensions = extensions
       }
       defaultHakyllWriterOptions
       { writerHTMLMathMethod = MathJax ""
       , writerSectionDivs = True
       , writerExtensions = extensions
       , writerHighlightStyle = Nothing
       }
       (bottomUpM transform)

JSXへの対応

PygmentsはデフォルトではJSXに対応していないようです.

fcurella/jsx-lexer: a JSX lexer for pygmentsを入れて解決.拡張可能なのは良いですね.

思わぬ副産物

Pygmentsは対応していない言語が渡された時はしっかりエラーを出してくれるのでserviceとか雑な言語名を与えていたものをしっかりエラーとして検出してくれました.

CSSを用意する

HTMLは出力できたので,PygmentsのSolarized Dark向けのCSSが必要です.

スタイルを探したら以下のリポジトリが引っかかりました.

gthank/solarized-dark-pygments: A Pygments style based on the dark background variant of Solarized

でもCSSを取得する方法が何処にも書いていません.困りました.

いろいろ調べたら,出力したいスタイル定義を読み込んだあと,以下を実行すれば取得できるようでした.

from pygments.formatters import HtmlFormatter
print(HtmlFormatter(style=Solarized256Style).get_style_defs())

しかしこのスタイルを適用してみましたがあまり好みのものではありませんでした.というかこれSolarizedなんですかね…?

と思ったら微妙だったのは256版を出力していたからでした.

print(HtmlFormatter(style=SolarizedStyle).get_style_defs())

で良いものが出てきました.

solarized-dark-pygmentsはMITライセンスなのでSCSSのコメントにリンクとライセンス文章を貼り付けて使うことにします.今回の場合コードの生成物が著作物になるのかはよくわかりませんが,まあ素直にリスペクトを示しておきましょう.

バグ報告求む

大体は正しくなることを確認しましたが,全てを確認したわけではないです.崩れているのを発見したら,情報提供をお願いします.