| title | slug | published-on | published-at |
|---|---|---|---|
pre-commit+playwrightでUIプレビューを自動生成し、READMEを常に最新に保つ仕組み |
keep-readme-ui-preview-up-to-date-with-pre-commit-and-playwright |
2025-12-12 05:00:00 -0800 |
UIを触るたびにREADMEのスクリーンショットが古くなる問題は、地味にストレスが溜まる。 毎回手動でキャプチャして差し替えるのも面倒だし、やらなくなるのが目に見えている。
そこで、pre-commitをフックにしてUIプレビューを自動生成し、そのままREADMEに反映させる仕組みを組んだ。 一度作ってしまえば、以降は「いつの間にかREADMEが最新になっている」状態になる。
例えば、こんな感じのUIプレビューがコミットごとに自動更新される。
やっていることはシンプルで、処理の流れはこうなる。
- コードやUIを編集する
- pre-commitでローカル開発サーバーを起動し、Playwrightでスクリーンショットを撮る
[Optional]生成されたPNGをBase64でSVGに埋め込む(GitHub表示対策)- README.mdはそのSVG(またはPNG)を参照するだけ
ポイントは 「pre-commitで必ず実行されるが、無駄な再生成はしない」 ところ。
最初は単純にPNGをREADMEに貼っていたが、GitHubではCSSや影、背景の表現が効かない。 UIの雰囲気が伝わりにくく、せっかくのプレビューがのっぺりして見える。
そこで、PNGを Base64でSVGに埋め込み、枠やシャドウをSVG側で表現 する方式に切り替えた。 これならGitHub上でも見た目をコントロールできる。
.pre-commit-config.yaml ではローカルフックを2つ定義している。
- UIプレビュー生成
- PNG → SVGカード生成
- repo: local
hooks:
- id: make-preview
name: make preview with PREVIEW_OUTPUT
language: system
entry: ./scripts/maybe_make_preview.sh
pass_filenames: false
- id: generate-web-editor-card
name: generate web editor card svg
language: system
entry: python3 scripts/generate_web_editor_card.py
pass_filenames: falseここでは ファイル差分はpre-commitに任せず、スクリプト側で制御 している。
::: details .pre-commit-config.yaml 全体
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: local
hooks:
- id: make-preview
name: make preview with PREVIEW_OUTPUT
language: system
entry: ./scripts/maybe_make_preview.sh
pass_filenames: false
- id: generate-web-editor-card
name: generate web editor card svg
language: system
entry: python3 scripts/generate_web_editor_card.py
pass_filenames: false:::
pre-commitで生成物を書き換えると、毎回差分が出てコミットできなくなる。 この問題を避けるために、影響しうるファイル群のハッシュを取る方式を採用した。
scripts/maybe_make_preview.sh の役割は以下。
- UIに影響するファイルだけを
git ls-filesで列挙 - それらのSHA-256をまとめてハッシュ化
- 前回と同じならプレビュー生成をスキップ
- 変わっていれば生成し、ハッシュを
.preview.hashに保存
CURRENT_HASH=$(printf '%s\0' "${PREVIEW_FILES[@]}" | xargs -0 sha256sum | sha256sum | cut -d ' ' -f1)
if [[ -f "$HASH_FILE" ]] && [[ "$(cat "$HASH_FILE")" == "$CURRENT_HASH" ]]; then
echo "preview: no relevant changes detected, skipping"
exit 0
fiこの方式にしてから、pre-commitが「賢くなった」感覚がある。 不要な起動やキャプチャが走らず、体感もかなり軽い。
実際のスクリーンショット生成はPythonとPlaywrightに任せている。
- Pythonでローカル開発サーバーを起動
- URLが立ち上がるまでポーリング
- NodeスクリプトでPlaywrightを起動しキャプチャ
- 終わったらサーバーを確実に終了
preview.py はこのオーケストレーション役だ。
if not wait_for_server(PREVIEW_URL):
print("Server did not become ready within the timeout window.")
return 1
subprocess.run(
["node", "scripts/capture.mjs", str(OUTPUT_PATH), PREVIEW_URL],
check=True,
)Playwright側は極力シンプルにしている。
viewport指定と networkidle 待ちだけで、余計なことはしない。
最後に、生成されたPNGをSVGに包む。
scripts/generate_web_editor_card.py では、
- PNGをBase64エンコード
- SVGテンプレートに埋め込み
- 影・角丸・背景をSVG側で定義
という処理をしている。
この段階で「READMEに載せる前提の見た目」を完成させておくのがポイント。
README側はただ <img> で参照するだけになる。
一度この仕組みを入れると、
- UIを触る
- そのまま
git commit - READMEのプレビューが勝手に更新されている
という状態になる。
「ドキュメントは後で直す」が起きにくくなるのが一番の収穫だった。 pre-commitは整形ツールという印象が強いが、ローカルで完結する自動化にはかなり向いている。
UIを持つリポジトリなら、わりと汎用的に使える構成だと思う。
- コード(Gist): https://gist.github.com/ackkerman/d60a849f7e3a0a63f983e4aa2d561f1e
- ファイル構成例
.pre-commit-config.yaml .Makefile .scripts/ ├── capture.mjs ├── generate_web_editor_card.py ├── maybe_make_preview.sh └── preview.py
- ファイル構成例

https://zenn.dev/moxak/articles/eb7a5dfc0c11a5