Dartでブログをビルドする
背景
このブログの生成に jekyll を使ってきたが、以下の理由から別の方法に移行したくなった。
- バージョンアップ時にトラブルが起きて時間を食うことがある
- サイト改修をしたくなる度にドキュメントを読み直す必要がある
jekyll も歴史が長くなり、色んな機能が盛り込まれていて自分には不要なものも多い。例えば最近は使う必要がない sass 周りで native compile がうまくいかず消耗した...。
Porting my website to Swift – robb.is を読んでて、手慣れた言語でイチからサイト生成ツールを実装するのは面白そうだと思った。自分のサイト構築に要件を絞れば必要な機能は少ないし、長い目ではツール学習コストよりメンテナンスのコストの方が受け入れやすそうだ。
Dart と静的サイト生成
静的サイト生成ツールを自作する上で以下をゴールとした。
- ちょっとしたサイト改修やページ追加を気軽にできるよう、使い慣れた言語を使う
- なるべく言語の標準機能を使ってメンテナンスしやすくする
最近は仕事で Dart を使っているので試しにこれを使ってみることにした。
Dart には Ruby における erb のような機能がないため、静的サイト生成の要になるテンプレートエンジンも自作するか 3rd party に依存する必要がある。残念ながら pub.dev をざっと見ても数が少なく、活発にメンテナンスされていないものが多い。
今回は長期的なメンテナンス性確保の意図もあるので、3rd party への依存はなるべく減らしテンプレートエンジンも使わないことにした。Flutter Web も面白そうだったけど、jekyll と同様に SSG でのサイト構築をしたい場合は活用が難しそうで検討しなかった。
dart:html
Dart の標準パッケージである dart:html では DOM を構築して HTML 文字列を吐き出すような機能が備わっている。しかしこれは dart2js compiler で使うことを想定されている。今回作る静的サイトジェネレータのように、dart コマンドで起動する CLI アプリケーションとして実行する場合は使用できない。
しかし dint-dev/universal_html という黒魔術的なパッケージによって、CLI やサーバサイドのアプリケーションでも dart:html の API を呼び出すことができる。割と活発にメンテされており Null Safety 対応も入っているようだった。
長期的にメンテしやすい事が目的なのにこうしたキワモノを使うのはどうなんだと思いつつ、あくまでこのライブラリは dart:html との橋渡しであることと、単純に面白そうなのもあり使ってみることにした。
これを使えば以下のような感覚で HTML 要素を組み立てられて、Flutter で Widget を組み立てる時の感覚に近い。
HtmlElement _buildEntryList() { return DivElement() ..className = 'posts' ..children = [ for (final post in posts) DivElement() ..className = 'post' ..children = [ Element.p() ..className = 'post-meta' ..innerText = post.publishedDate, Element.a() ..attributes = {'href': '/entry/${post.pathName}'} ..className = 'post-link' ..children = [ HeadingElement.h3() ..className = 'post-title' ..innerText = post.title ] ] ]; }
最終的なソースコードは horimislime/horimisli.me at feature/dart から確認できる。
半ば面白半分で試してみたのもあるが、自分が運用してる個人サイトくらいシンプルな HTML ならギリギリ生成可能というレベルで、もっとボリューム感のある凝ったサイト構築にこれはツラい感じ。運用の観点では、Dart の標準機能に組み込まれているものが使えるというのはメリットだった(とはいえ、今回はそれを強引に使うライブラリを挟んでいるんだけど)。
感想
静的サイト生成ツールを自作し、例えば「月別のアーカイブページを作りたい」みたいなちょっとした改修がすぐ行えるようになった。サイトジェネレータに最低限必要な要件として、 記事のコンパイル・preview 用サーバ起動・ファイルの変更を検知して再リビルド、などの機能を CLI ツールのコマンドとして実装した。普段業務で Dart を使っていて触れる機会がなかった部分を知れたのも良かった。
今回依存パッケージはなるべく https://github.com/dart-lang 管理下のものだけに限定した。おかげで今後 Dart のバージョンアップが必要になった時も結構楽できそうだ。
一方でやってみて辛かった部分もある。やっぱりテンプレートエンジンなしというのは大変で、また dart:html で DOM を構築するのも決して体験が良くはない。今後もっと良いアプローチを見つけられることに期待しつつ運用してみたい。