シンプルさの極み
前回の記事で「100行程度のスクリプト」と書きましたが、具体的にどのような仕組みで動いているのか、少し技術的な話をしましょう。
この静的サイトジェネレーター(SSG)の構造は、驚くほど単純です。
- Load:
contentディレクトリ内のMarkdownファイルを読み込む。 - Parse: ファイルの先頭にあるメタデータ(タイトルや日付)と本文を分離する。
- Render: Jinja2テンプレートにデータを流し込み、HTMLを生成する。
- Save:
docsディレクトリにHTMLファイルとして保存する。
これだけです。データベースもなければ、複雑なルーティング処理もありません。
アーキテクチャの解剖
では、各ステップをコードレベルで掘り下げてみましょう。
1. Load & Parse: メタデータの抽出
Markdownファイルの先頭には、YAML形式のようなメタデータを記述しています。これを解析するために、Pythonの標準ライブラリや正規表現を駆使することもできますが、今回は python-markdown ライブラリの Meta 拡張機能を使用しています。
import markdown
def parse_markdown(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
text = f.read()
# MarkdownをHTMLに変換しつつ、メタデータを抽出
md = markdown.Markdown(extensions=['meta'])
html_content = md.convert(text)
# メタデータは辞書型で返ってくる(値はリスト形式なので注意)
meta = md.Meta
return {
'title': meta.get('title', ['No Title'])[0],
'date': meta.get('date', ['1970-01-01'])[0],
'content': html_content,
'slug': file_path.stem # ファイル名をURLの一部として使用
}
この関数のポイントは、ファイルシステム上の「ファイル」を、Pythonプログラムで扱いやすい「辞書オブジェクト」に変換している点です。一度辞書になってしまえば、あとはリストに格納して日付順にソートしたり、フィルタリングしたりと、Pythonの強力なリスト操作機能をフル活用できます。
2. Render: Jinja2によるHTML生成
HTMLの生成には、Python界隈でデファクトスタンダードとなっている Jinja2 を採用しました。
Jinja2の最大の強みは「テンプレートの継承」です。
Webサイトには、ヘッダーやフッター、ナビゲーションなど、全ページで共通する部分があります。これらを base.html という親テンプレートに定義し、個別の記事ページ(article.html)では中身だけを差し替えるという手法をとります。
base.html (親):
<!DOCTYPE html>
<html lang="ja">
<head>
<title>{% block title %}{% endblock %} - My Blog</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>...</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>...</footer>
</body>
</html>
article.html (子):
{% extends "base.html" %}
{% block title %}{{ article.title }}{% endblock %}
{% block content %}
<article>
<h1>{{ article.title }}</h1>
<time>{{ article.date }}</time>
<div class="post-body">
{{ article.content }}
</div>
</article>
{% endblock %}
Python側では、このテンプレートに辞書データを渡すだけです。
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('templates'))
template = env.get_template('article.html')
html = template.render(article=article_data)
ロジック(Python)とデザイン(HTML/CSS)が完全に分離されているため、デザインを変更したいときはPythonコードを一切触る必要がありません。これがメンテナンス性の高さに繋がります。
依存ライブラリは最小限に
使用している外部ライブラリも厳選しました。
markdown: MarkdownテキストをHTMLに変換するため。jinja2: HTMLのテンプレートエンジンのため。
これらはPythonのエコシステムにおいて枯れた(安定した)技術であり、数年後に動かなくなるリスクは非常に低いです。
Node.jsベースのSSG(GatsbyやNext.jsなど)は、node_modules フォルダが数百メガバイトに膨れ上がり、依存ライブラリのバージョン不整合に悩まされることが少なくありません。対して、私たちのSSGは pip install するライブラリが数個だけ。環境構築も一瞬で終わります。
なぜ自作するのか?
「車輪の再発明」は悪いことだとされがちです。しかし、学習目的や、自分だけの特別な要件がある場合には、再発明こそが最短の道になることがあります。
既存のSSGは「あらゆる人のあらゆる要望」に応えるために肥大化しがちです。 しかし、私たちが欲しいのは「このブログを表示するためだけの機能」です。
例えば、このサイトにはコメント欄がありません。検索機能もありません。 必要ないからです。
機能がないということは、メンテナンスするコードが少ないということであり、バグが潜む場所が少ないということです。
このシンプルさが、開発の速度と楽しさを支えています。 100行のコードなら、週末の午後だけで書き上げることができます。そして、そのコードは完全にあなたのコントロール下にあります。もし「タグ機能が欲しい」と思えば、自分で実装すればいいのです。誰かが作ったプラグインを探す必要はありません。
プログラミングの原点である「モノ作り」の喜びを、この小さなSSGは思い出させてくれます。