Github ActionsとIssuesでブログを書く
Github Issuesをブログとして使うアイデアは太古の昔からある。ここで言っているのはIssueを使ってブログ記事を書き、タグをつけ、フィルタや検索して管理しながら、そこからHTMLを生成するようなやつのことだ。ちょっと検索しただけでもいくつかの実装を見かける。
また、実装を解説したブログSimple GitHub Issues Powered Blogもある。いずれも基本的にはGithub Issuesをバックエンドとして使い、フロントのJavaScriptがIssuesのAPIから情報をとってHTMLを生成している。例えばこれなんかはシンプルでいい感じ。
Github Actionsを使う
今回書くのは、これのGithub Actions版だ。似たようなSPA版実装がたくさんあるのでGithub Actions版を作ってバランスをとることにする。Issuesの作成・更新をトリガーにGithub ActionsがサーバーサイドでHTMLをレンダリングしてWeb1.0な感じのサイトをつくってみる。Github Pagesでホストするので全てGithub内で完結することになる。
仕組み
Issueの作成からサイトのビルドまでの流れは以下のようになる[1]。ビルドに関してはGithub Actionsがさまざまなコンテキスト(例えばIssue Id、Issue Title、Account Nameなど)を持っているのでそれをHTMLへ埋め込むだけの処理になる。こういう処理にはテンプレートエンジンが便利だ。今回は懐かしのMustacheを使った。またIssuesに書き込まれた本文のMarkdownをHTMLに変換する部分はGithubにAPIがある。変換のライブラリを探してきてActions内で動かすつもりでいたのでこれは助かる[2]。
Github Actions
Github Actionsの中身はこんな感じになる。このYAMLはあまり処理をしていなくて実態は後述するJavascriptのコード build-pages.js
である。YAMLは環境変数をjsに渡すのとGithub Pagesへのpushを行なっているだけ。
name: Build Pages
on:
issues:
types: [opened, edited]
env:
owner: ${{github.repository_owner}}
repo: ${{github.event.repository.name}}
target_issue_id: ${{github.event.issue.id}}
jobs:
build-pages:
runs-on: ubuntu-latest
steps:
- name: View the github context
run: echo "$GITHUB_CONTEXT"
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
- name: View github-script context
uses: actions/github-script@v6
with:
script: console.log(context)
- name: Checkout blog repo
uses: actions/checkout@v3
- name: Install Octokit and Mustache
run: npm install @octokit/rest mustache
- name: Env test
uses: actions/github-script@v6
with:
script: |
console.log(process.env)
- name: Build pages
run: node src/build-pages.js
- name: Git setting
run: |
git config --local user.email "your_email_address"
git config --local user.name "${{github.actor}}"
- name: Commit and push pages
run: |
git add index.html posts/${{github.event.issue.id}}.html
git commit -m "update" -a
git pull
git push origin main
const { Octokit } = require("@octokit/rest");
const octokit = new Octokit({});
const Mustache = require('mustache');
const fs = require("fs");
const df = require("./date-format.js")
// Build
octokit.rest.issues.listForRepo({
owner: process.env.owner,
repo: process.env.repo,
})
.then(issues => {
issues.owner = process.env.owner
issues.repo = process.env.repo
// Format issue object
issues.data.map(issue => issue.updated_at_short = df.shortDate(issue.updated_at))
console.log("issues: ", issues)
// Build index
const index_template = fs.readFileSync("template/index.template.html", "utf8").toString();
const index_html = Mustache.render(index_template, issues)
fs.writeFileSync("index.html", index_html, "utf8");
// Build post
target_issue = issues.data.filter((ti) => {
return ti.id == process.env.target_issue_id
});
target_issue = target_issue[0]
markdown = target_issue.body
const issue_template = fs.readFileSync("template/post.template.html", "utf8").toString();
octokit.rest.markdown.render({"text": markdown, "mode": "gfm"})
.then(issue_html => {
target_issue.issue_html = issue_html
console.log("target_issue: ", target_issue)
const issue_page = Mustache.render(issue_template, target_issue)
fs.writeFileSync("posts/" + process.env.target_issue_id + ".html", issue_page, "utf8");
})
});
build-pages.js
はHTMLのテンプレートを読み込んでそこにGithub Actionsのcontextの変数をMustacheを使って埋め込み、index.html
と<issue_id>.html
を作るだけのシンプルな処理。ローカルでデバッグする際には以下のように環境変数を渡して実施していた。
export owner=your_account
export repo=blog
export target_issue_id=1198469729
Mustache
あとは好きなindex.html
と<post_id>.html
のテンプレートファイルを用意してMustacheを使って<h1>{{title}}</h1>
という感じで好きなHTMLに変数を埋め込むだけだ。APIから帰ってくるマークダウンから変換したHTMLをそのままHTML内に埋め込みたい場合は{{{html}}}
と髭を3つにしてやればよい。
まとめ
またもやGithub Issueからブログをつくるやつを増やしてしまった。今回のはサーバー側で静的サイトを作るのでこれまでのSPAなサイトと違って、軽快でシンプルなサイトを作るのに向くと思う。また静的サイトジェネレータと比べてもビルドが自動化されていているので、その点でも楽ができるのではないだろうか。次は誰が書く?
Discussion