「Make」を使った静的サイトジェネレーター(2022)
(karl.berlin)- 静的サイトはホスティングが簡単で高速、メンテナンスも少なくて済むため、簡単なサイトなら別のビルダーを学ばなくても Makefile 数行で十分に作れる
- 中核となる構成は、
sourceのファイル配置を維持したまま、HTML にはheader.htmlを付け、残りのファイルはbuildにそのままコピーする方式 make buildはfind source -type fとpatsubstで入力・出力ファイルを対応させ、.htmlとその他のファイルに異なる make ルールを適用する- 現在ページの強調表示、Markdown 変換、ローカルサーバー、変更検知による再ビルド、GitHub Pages へのデプロイは、必要に応じて追加ターゲットとして付け足せる
- 依存関係が少なく、修正すべき箇所が明確なため、要件の小さいサイトでは汎用ジェネレーターよりも自作のビルドフローのほうが速い場合がある
Makefile だけで作る基本的な静的サイト
- 静的サイトジェネレーターは、成果物をホスティングしやすく高速で、メンテナンスの負担も非常に小さい
- 簡単なサイトなら、既存のサイトビルダーを学んで合わせるより、自分でスクリプトを書くほうが速く、満足度も高い場合がある
- 自動更新タイムスタンプや RSS フィードのない普通のサイトなら、ブログ用スクリプトよりさらに単純な構成で十分
-
基本要件
- すべての入力ファイルは
sourceディレクトリに置き、出力でも同じディレクトリ構造を維持する - すべての HTML ファイルの先頭に
header.htmlを追加する - HTML ではないファイルは変更せずに
buildディレクトリへコピーする
- すべての入力ファイルは
-
Makefile ルール
buildターゲットは、source以下のすべてのファイルをbuild以下の出力ファイルに対応させる依存関係を持つ- 自身では作業をしないが、各ファイルに合ったルールが適用されるようにするエントリーポイントの役割を果たす
build/%.htmlルールはsource/%.html、header.html、Makefileに依存するmkdir -p $(dir $@)で出力ディレクトリを作成し、cat header.html $< > $@でヘッダーと入力 HTML を結合して出力するbuild/%ルールは、HTML ではないファイルをcp $< $@でそのままコピーする- この
header.htmlとルールだけがあれば、make buildの実行で、ローカルで開いたり Web サーバーにアップロードしたりできるbuildディレクトリが作られる
拡張例と補助ターゲット
-
現在ページの表示
- ナビゲーションで現在ページを強調表示すると、訪問者がサイト内での位置をひと目で把握できる
- 例ではナビゲーションリンクを探して
currentクラスを追加する sed -E 's|(href="$(subst source,,$<))|class="current" \1|' header.html | cat - $< > $@- 実際の置換方法は、使用しているマークアップ構造に合わせて変える必要がある
-
Markdown から HTML を生成
- HTML を直接書きたくない場合や既存コンテンツが Markdown 形式の場合は、Markdown-to-HTML 変換器をパイプでつなげられる
- 例では smu を使用する
smu $< | cat header.html - > $@- 基本ルールは依然として、
build/foo.htmlがsource/foo.htmlから作られると仮定している - Markdown ファイルにも
.htmlサフィックスを維持するか、入力として.mdファイルを探すようにルールを修正する必要がある
-
ローカルプレビュー
- 一部のサイトは、ブラウザでローカルファイルを直接開くと正確にプレビューしにくい。よくある理由は、相対リンクではなく絶対リンクを使っている場合
- Python は多くのシステムにすでにインストールされており、この用途に適した Web サーバーを含んでいる
serveターゲット:python -m http.server -d build
-
変更時の自動再ビルド
- サイト作業中に毎回手動でビルドし直すのは面倒
entrを使うと、source、header.html、Makefileの変更時に自動でmake buildを実行できるwatchターゲット:find source header.html Makefile | entr make build- 依存関係を避けたいなら、
inotifywaitを使うこともできる
-
GitHub Pages へのデプロイ
- GitHub にリポジトリを置いているなら、生成された HTML を GitHub Pages でホスティングするのは自然な選択
- git の細部を気にしなくて済むようにデプロイコマンドを整える作業は少し厄介だが、
git worktreeで処理できる - 方法は Sangsoo Nam の記事 を基にしている
gh-pagesブランチをpublic_htmlワークツリーとして追加するbuild/*をpublic_htmlにコピーするgit add --all、git commit -m "Deploy to github pages"、git push origin gh-pagesを実行する- 最後に
git worktree remove public_htmlでワークツリーを削除する
-
実例
- このアプローチで作られたページは karlb/astridbartel.de で見られる
- 完全な静的サイトジェネレーターは Makefile の6行から始められ、特殊な依存関係やメンテナンス負担なしに、必要に合わせて素早く変更できる
1件のコメント
Hacker News の意見
その後、ニュースと RSS フィード、研究論文と講義資料の自動一覧、タグでフィルタリングできる本のリストのような機能を追加した。今も Makefile だが、Makefile 自体はむしろ少し単純になり、その代わり HTML を行単位で扱うために優れた xml2 と 2xml ユーティリティを使う Bash スクリプトを呼び出している。主に
grepやsedのような中核ユーティリティを活用しているさらに必要なときに自動で
makeを呼び出す git hook もいくつか付けており、特に Web サイトがホストされているリモートサーバーでは、リポジトリに push すると公開版が再ビルドされる何年も非常にうまく動いていて、git の履歴は 2009 年までさかのぼる。最初のコミットを見ると
beccad7 (FIRST_VERSION) Initial commit、d1cc6d7 adding link to Google Reader shared items、6ccfd0c fix typo、d337959 adding link to Identi.ca accountで、本当に 15 年が過ぎたxml2と2xmlユーティリティは少し 放置されたプロジェクト のように見えるhttps://github.com/cryptorick/xml2
https://manpages.debian.org/unstable/xml2/2xml.1.en.html
source/でファイルを削除してもbuild/では削除されないこと自分のプロジェクトではサイト全体を再ビルドしても十分速いので、再ビルド前に build フォルダ全体を削除 する方を選んだ
https://github.com/jez/jez.github.io/blob/source/Makefile#L1...
こうするとビルドシステムを使う大きな理由である増分ビルドがかなり無力化されるが、それでも再生成したいページが分かっていれば、そのファイルだけを直接
makeできるMakefile でこうしたパターンに対する一般的な回避策があるなら知りたい
GNU Make の
shell関数でファイルを列挙し、filter-out関数で「想定された」出力を除外する。見苦しいハックだが、shell関数による変数展開の一部としてコマンドを実行させることで、毎回実行されることを保証しているMakefile のリンク: https://github.com/jaredkrinke/make-blog/blob/main/Makefile
make cleanのような核兵器級のオプション以外にも、削除やリネーム用の特定の Make ターゲットを用意できる。例えばmake rm sourcefileやmake mv sourcefile newsourcefileが、元ファイルと生成されたターゲットの削除・リネームをまとめて処理するようにする、といった形実際、かなり大きなブログやオンラインプロジェクトでも
make clean/make allサイクルはたいてい十分速く、テンプレートやサイトのデザイン要素を直すときには頻繁に必要になる。再ビルド時間が気になるほどの規模なら、ソースをデータベースで管理し、クライアントアクセス時に動的生成する本物の CMS を使う方が適しているかもしれないmake cleanでよくない?rm/%.html:@rm -f source/%.html build/%.html実行は
$ make rm/page.htmlとすればよいprune機能があるhttp://neilmitchell.blogspot.com/2015/04/cleaning-stale-file...
ただし
makeでも通用する最良の解決策は、成果物の最終的な.tar.gzアーカイブを作るmake distターゲットを用意することだと思う。規則をきちんと書けば、古いファイルは入らない。大きなプロジェクトでは遅いかもしれないが、開発中はどうせ使う必要がなく、リリース時だけ使えばよい。増分ビルドは引き続き可能で、最後の.tar.gzだけを最初から作ればよいblog.shシェルスクリプトの作業に直接インスピレーションを受けたそれを持ってきて手を入れ、自分のミニマルな 静的サイトジェネレーター である
barfを作った。Karl が素晴らしい仕事を公開していなければ、存在しなかったはずだ[0]: https://github.com/karlb/karl.berlin/blob/master/blog.sh
[1]: https://barf.bt.ht
shiteと呼んでいて、これで自分のサイトを作っている。名前はソフトウェア品質をほのめかしている :)いちばん気に入っている点は、これまで何もアップグレードする必要がなく、今後もその必要はなさそうなこと。その次に良い点は、JavaScript なしで ホットリロード できること
[1] https://github.com/adityaathalye/shite
[2] https://evalapply.org
m4を少し混ぜると、同じ 骨組みだけのアプローチ を保ちながらも、もう少し柔軟性を得られる20年ほど前に、そういう方式で作られた小さなWebサイトを保守したことがある。ただ、個人サイトを除くと、最近このモデルがうまく合うかは分からない。この方式は本質的に Web 1.0 的な役割を強制するからだ。貢献するすべてのユーザーがHTMLに熟達している必要があるか、誰かが「ウェブマスター」という雑務を引き受けなければならない
[1] https://en.wikipedia.org/wiki/M4_(computer_language)
m4を少しだけ」などというものは存在しないまっさらなプロジェクトを始め、今回は
m4には触らないと誓う。すると重複コードを減らすために、小さなm4呼び出しを1つ入れることになる1年後、Webサイトから「cat」という単語がひっそり消える理由を探して、5重のマクロ展開を掘り進める羽目になり、結局ジュニア開発者がマニュアルからコピーせずに自分で for ループを実装しようとして引用符を壊していたことに気づく
目先の問題を解決したあとは、DSLのデバッグがあまりに難しいと判断し、プロジェクトごとにコピーしてきたM4マクロファイルを持ち込む。その後、出力にコメントを追加してスタックトレース生成スクリプトが動くようにするマクロ生成マクロで、
defineの使用箇所をすべて置き換えるのに1日を費やす次のプロジェクトでは絶対のルールを作るつもりだ。
m4禁止! もちろん、あの1か所くらいは例外かもしれないm4やperlのような一般的なテキスト置換に頼るより、HTMLとXMLの基盤であり共通の上位集合でもある SGML を使うことを勧めるSGMLは、簡単な型検査付きテキストマクロ、つまりエンティティ展開を標準で提供し、型を理解するパラメータ化マクロ展開も可能だ。ここでいう型とは、マークアップ要素の一般的なコンテンツ型、つまり許可される子要素とその順序を意味し、属性・CDATA・RCDATAのようなコンテキストに展開したりエスケープしたりすることも考慮する
潜在的に悪意のあるユーザーコメントを適切に展開・エスケープしつつ、たとえばインラインレベルのマークアップは許可し
script要素は禁止するといったユーザールールを適用する作業は、SGMLだけがきちんと展開できる。MarkdownやWiki記法をHTMLへ展開したり、外部/シンジケーションのHTMLコンテンツを取り込んだり、RSSやナビゲーション用のoutlineを作ったりすることも可能だ。コマンドラインでかなり複雑な静的サイト準備作業を行うのに向いている[1]: https://sgmljs.net/docs/producing-html-tutorial/producing-ht...
[2]: https://sgmljs.net/docs/sgmlproc-manual.html
m4やsedの検索置換の代わりにenvsubstを試してみるとよいBashスタイルの変数参照、たとえば
$TITLEを環境変数の値に置換するプログラムだexport CURRENT="..."cat page.html | envsubtm4を少しだけ」だなんて、頼むから絶対にやめてくれm4は難解プログラミング言語としてもいまひとつだsendmailで動いていたという事実だけでは、何かを正当化するには十分ではない
ここで面白い記事を読むたびにフィードを購読している。WordpressサイトでもBear BlogでもMicro.blogでもHavenwebブログでも、自作サイトのフィードでも、Hey Homepageの
Really Social Sitesモジュールに追加している最終的には、KagiがSmall Webイニシアチブでやっているように、このブログ一覧を公開したい。ただ、品質を加えるにはキュレーションが鍵で、キュレーションを考え始めると、ある種のオンライン雑誌を始めるのが自然に思えてくる
人々はどこで、他人に共有してもよいという良い意味での「資格意識」を得るのだろうか? 他の人が自分の取り組んでいることに興味を持つと仮定する理由が気になる。ときどき競争のように感じる。「ブログでいいねや露出を得るには、できるだけすごいものを作らなければならない」というような感じだ
協業はもちろん良いことで、すべてを公開してこそ可能になる。ただ、「これが格好よさそうだからやる」と「反応を得るために他人へ共有しようと努力する」の境界がよく分からない
テストファイルを1つ変えるだけでも、単一のコマンドでテスト実行とグラフ生成を含めて論文全体を再生成できると言っていた
https://pages.github.com/
自分のものはJavaScriptなしのホットリロードが欲しくて少し過剰設計になったが、楽しいヤクの毛刈りだった
基本的なアイデアは同じだ。テンプレートにはheredocを使い、プレーンテキストをHTMLに変換するコンパイラを使う。自分の場合は
pandocだ。インデックス生成のために中間CSVを使い、front matterを抜き出す便利なsedテクニックもある。古典的でよい[1] https://github.com/karlb/karl.berlin/blob/master/blog.sh
[2] https://github.com/adityaathalye/shite
[3] 自分のやり方: https://github.com/adityaathalye/shite/blob/master/bin/templ...
ただし、少し限界がある。私は記事を名前空間ごとに整理し、URLに日付を入れているが、
makeはそれを直接うまく処理できない。makeの利点は、遅いコンパイラでビルドされる大きなプログラムに小さな変更があったとき、インクリメンタル再ビルドをはるかに速くしてくれることにある。全体の再ビルドに40分かかる作業が、3秒程度で終わることもある。
静的サイトが共通ヘッダーと数百個のHTMLファイルを
catするだけで1秒未満で一から生成できるなら、スクリプトの代わりにmakeを使う利点はない。依存関係のバグによって不完全なビルドを実行してしまうリスクが増えるだけだ。.phonyとしてマークすればよい。それでも
make buildとmake pushのような区別は持てる。ほかの小さなプロジェクトでは、臨時のバンドラーとしてごく小さなシェルスクリプトを使っていた。CSSとJSをHTML内に挿入する方式で、ビルドしていないファイルもローカルで提供できるようにするのが目的だった。