3 ポイント 投稿者 GN⁺ 2023-09-11 | 1件のコメント | WhatsAppで共有
  • 静的サイトはホスティングが簡単で高速、メンテナンスも少なくて済むため、簡単なサイトなら別のビルダーを学ばなくても Makefile 数行で十分に作れる
  • 中核となる構成は、source のファイル配置を維持したまま、HTML には header.html を付け、残りのファイルは buildそのままコピーする方式
  • make buildfind source -type fpatsubst で入力・出力ファイルを対応させ、.html とその他のファイルに異なる make ルールを適用する
  • 現在ページの強調表示、Markdown 変換、ローカルサーバー、変更検知による再ビルド、GitHub Pages へのデプロイは、必要に応じて追加ターゲットとして付け足せる
  • 依存関係が少なく、修正すべき箇所が明確なため、要件の小さいサイトでは汎用ジェネレーターよりも自作のビルドフローのほうが速い場合がある

Makefile だけで作る基本的な静的サイト

  • 静的サイトジェネレーターは、成果物をホスティングしやすく高速で、メンテナンスの負担も非常に小さい
  • 簡単なサイトなら、既存のサイトビルダーを学んで合わせるより、自分でスクリプトを書くほうが速く、満足度も高い場合がある
  • 自動更新タイムスタンプや RSS フィードのない普通のサイトなら、ブログ用スクリプトよりさらに単純な構成で十分
  • 基本要件

    • すべての入力ファイルは source ディレクトリに置き、出力でも同じディレクトリ構造を維持する
    • すべての HTML ファイルの先頭に header.html を追加する
    • HTML ではないファイルは変更せずに build ディレクトリへコピーする
  • Makefile ルール

    • build ターゲットは、source 以下のすべてのファイルを build 以下の出力ファイルに対応させる依存関係を持つ
    • 自身では作業をしないが、各ファイルに合ったルールが適用されるようにするエントリーポイントの役割を果たす
    • build/%.html ルールは source/%.htmlheader.htmlMakefile に依存する
    • 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.htmlsource/foo.html から作られると仮定している
    • Markdown ファイルにも .html サフィックスを維持するか、入力として .md ファイルを探すようにルールを修正する必要がある
  • ローカルプレビュー

    • 一部のサイトは、ブラウザでローカルファイルを直接開くと正確にプレビューしにくい。よくある理由は、相対リンクではなく絶対リンクを使っている場合
    • Python は多くのシステムにすでにインストールされており、この用途に適した Web サーバーを含んでいる
    • serve ターゲット: python -m http.server -d build
  • 変更時の自動再ビルド

    • サイト作業中に毎回手動でビルドし直すのは面倒
    • entr を使うと、sourceheader.htmlMakefile の変更時に自動で 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 --allgit commit -m "Deploy to github pages"git push origin gh-pages を実行する
    • 最後に git worktree remove public_html でワークツリーを削除する
  • 実例

    • このアプローチで作られたページは karlb/astridbartel.de で見られる
    • 完全な静的サイトジェネレーターは Makefile の6行から始められ、特殊な依存関係やメンテナンス負担なしに、必要に合わせて素早く変更できる

1件のコメント

 
GN⁺ 2023-09-11
Hacker News の意見
  • 個人サイト(https://pablo.rauzy.name/) は、以前は単純な Makefile で生成していた
    その後、ニュースと RSS フィード、研究論文と講義資料の自動一覧、タグでフィルタリングできる本のリストのような機能を追加した。今も Makefile だが、Makefile 自体はむしろ少し単純になり、その代わり HTML を行単位で扱うために優れた xml2 と 2xml ユーティリティを使う Bash スクリプトを呼び出している。主に grepsed のような中核ユーティリティを活用している
    さらに必要なときに自動で make を呼び出す git hook もいくつか付けており、特に Web サイトがホストされているリモートサーバーでは、リポジトリに push すると公開版が再ビルドされる
    何年も非常にうまく動いていて、git の履歴は 2009 年までさかのぼる。最初のコミットを見ると beccad7 (FIRST_VERSION) Initial commitd1cc6d7 adding link to Google Reader shared items6ccfd0c fix typod337959 adding link to Identi.ca account で、本当に 15 年が過ぎた
  • この方式の問題は、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 sourcefilemake mv sourcefile newsourcefile が、元ファイルと生成されたターゲットの削除・リネームをまとめて処理するようにする、といった形
      実際、かなり大きなブログやオンラインプロジェクトでも make clean / make all サイクルはたいてい十分速く、テンプレートやサイトのデザイン要素を直すときには頻繁に必要になる。再ビルド時間が気になるほどの規模なら、ソースをデータベースで管理し、クライアントアクセス時に動的生成する本物の CMS を使う方が適しているかもしれない
    • make clean でよくない?
    • こういう形でよさそう
      rm/%.html:
      @rm -f source/%.html build/%.html
      実行は $ make rm/page.html とすればよい
    • 汎用ビルドシステムの Shake には、まさにこの目的のための prune 機能がある
      http://neilmitchell.blogspot.com/2015/04/cleaning-stale-file...
      ただし make でも通用する最良の解決策は、成果物の最終的な .tar.gz アーカイブを作る make dist ターゲットを用意することだと思う。規則をきちんと書けば、古いファイルは入らない。大きなプロジェクトでは遅いかもしれないが、開発中はどうせ使う必要がなく、リリース時だけ使えばよい。増分ビルドは引き続き可能で、最後の .tar.gz だけを最初から作ればよい
  • この記事で言及されている Karl の 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か所くらいは例外かもしれない
  • m4perl のような一般的なテキスト置換に頼るより、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
  • m4sed の検索置換の代わりに envsubst を試してみるとよい
    Bashスタイルの変数参照、たとえば $TITLE を環境変数の値に置換するプログラムだ
    export CURRENT="..."
    cat page.html | envsubt
  • m4を少しだけ」だなんて、頼むから絶対にやめてくれ
    m4 は難解プログラミング言語としてもいまひとつだ
  • 一度やってみたが、二度とやらない
    sendmailで動いていたという事実だけでは、何かを正当化するには十分ではない
  • HNで出会うほとんどすべての開発者ブログに RSSフィード がある点がよい
    ここで面白い記事を読むたびにフィードを購読している。WordpressサイトでもBear BlogでもMicro.blogでもHavenwebブログでも、自作サイトのフィードでも、Hey Homepageの Really Social Sites モジュールに追加している
    最終的には、KagiがSmall Webイニシアチブでやっているように、このブログ一覧を公開したい。ただ、品質を加えるにはキュレーションが鍵で、キュレーションを考え始めると、ある種のオンライン雑誌を始めるのが自然に思えてくる
    • 開発者として自分のブログを持ちたいと思わない自分が変なのか、理解しようとしているところだ
      人々はどこで、他人に共有してもよいという良い意味での「資格意識」を得るのだろうか? 他の人が自分の取り組んでいることに興味を持つと仮定する理由が気になる。ときどき競争のように感じる。「ブログでいいねや露出を得るには、できるだけすごいものを作らなければならない」というような感じだ
      協業はもちろん良いことで、すべてを公開してこそ可能になる。ただ、「これが格好よさそうだからやる」と「反応を得るために他人へ共有しようと努力する」の境界がよく分からない
    • Bear Blogに似た https://prose.sh もある
  • 友人が makeで科学論文を生成する 方法を説明してくれたことがある
    テストファイルを1つ変えるだけでも、単一のコマンドでテスト実行とグラフ生成を含めて論文全体を再生成できると言っていた
  • すっきりしたアイデアではあるが、すでにGitHubへpushしているなら、ソースだけを上げてもGitHubがMarkdownをホストされたページとして公開してくれる
    https://pages.github.com/
    • そうすると、単なるホスティング以上に GitHubへ依存 することになる。最初からサイト生成をローカルで実行できるようにしておくほうがよい
  • コードが気に入った
    自分のものは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...
    • 彼の GEMINIアプローチ はかなり笑えた。正規表現で書式の大部分を剥がしている

ただし、少し限界がある。私は記事を名前空間ごとに整理し、URLに日付を入れているが、makeはそれを直接うまく処理できない。

  • この種のスクリプトを見ると、なぜ自前で作らずに esbuildvite のようなものを使うのかがよく分かる。
  • makeの利点は、遅いコンパイラでビルドされる大きなプログラムに小さな変更があったとき、インクリメンタル再ビルドをはるかに速くしてくれることにある。
    全体の再ビルドに40分かかる作業が、3秒程度で終わることもある。
    静的サイトが共通ヘッダーと数百個のHTMLファイルをcatするだけで1秒未満で一から生成できるなら、スクリプトの代わりにmakeを使う利点はない。依存関係のバグによって不完全なビルドを実行してしまうリスクが増えるだけだ。
    • ファイル依存関係が実際には重要でないなら、ビルドターゲットを.phonyとしてマークすればよい。
      それでもmake buildmake pushのような区別は持てる。
  • わあ、これは自分のサイトでやろうとしていたこととほとんどまったく同じだ。
    ほかの小さなプロジェクトでは、臨時のバンドラーとしてごく小さなシェルスクリプトを使っていた。CSSとJSをHTML内に挿入する方式で、ビルドしていないファイルもローカルで提供できるようにするのが目的だった。