はじめに
Astro を使ったポートフォリオサイトを自作する中で、esaで編集したマークダウンからwebサイトの記事を更新するような実装をしたので、備忘録を兼ねてまとめておきます
ちなみに今回実装したサイトはこちらです
また esa のマークダウンに任意の frontmatter をつけてGitHub にコミットする機能のみを実装したレポジトリはこちらです
https://github.com/kanakanho/esa-frontmatter-rewrite-push-github
esa からGitHub にコミットする
コミットする方法
esa から GitHub へのコミットは管理者権限さえあれば比較的に簡単にできます。
この方法については既に esa の方がまとめて下さっているのでそちらの記事を貼っておきます docs.esa.io help / GitHub Webhook (β)
簡単に説明すると https://{teamName}.esa.io/team/webhooks
にアクセスして
下の Webhook を追加
から
GitHub を選んで必要な項目を入力すると特定の esa Root Category
に投稿された post
の記事が GitHub へ共有されます
自動的に追加される frontmatter
[!TIP] Frontmatter とは markdown ファイルの先頭について、ファイルについて追加の情報を記述するためのフォーマットです
---
で区切られる場合 yml 形式が適応されます
esa には自分で編集したマークダウンに加えて下に示すような frontmatter が追加されます。
---title: post のタイトルcategory: posts/yyyy/mm/ddtags:created_at: 'yyyy-mm-ddTtt:mm:ss+00:00'updated_at: 'yyyy-mm-ddTtt:mm:ss+00:00'published: truenumber: 123---
Astro でマークダウンの frontmatter を分析する
任意の frontmatter を追加したマークダウンを GitHub にコミットする Astro では frontmatter を解析して本文とは別の情報を取得するための方法があります。
getCollection
や Astro.glob()
を使ってマークダウンファイルを読み込むと frontmatterの情報がプログラムの中で扱えるようになります。
読み込む方法についても簡単なコードを置いておきます
getCollection を使う方法
src├── content│ ├── config.ts│ └── post│ ├── first.md│ └── second.md├── layouts│ └── Layout.astro└── pages ├── index.astro └── posts └── [number].astro
const post = defineCollection({ type: "content", schema: z.object({ number: z.number(), title: z.string(), date: z.string(), tags: z.array(z.string()), image: z.string(),})
---import { getCollection, type CollectionEntry } from "astro:content";
type Frontmatter = { number: string; title: string; date: string; tags: string[]; image: string,}
export async function getStaticPaths() { const posts: CollectionEntry<"post">[] = await getCollection("post");
return posts.map((post) => ({ params: { number: post.data.number }, props: post, }));}
type Props = InferGetStaticPropsType<typeof getStaticPaths>;
const post: Props = Astro.props;const frontmatter: Frontmatter = post.data;---<!-- HTMLを記述する -->
Astro.glob()を使う方法
src├── content│ └── post│ ├── first.md│ └── second.md├── layouts│ └── Layout.astro└── pages ├── index.astro └── posts └── [number].astro
---import type { MarkdownInstance } from "astro";
type Frontmatter = { number: string; title: string; date: string; tags: string[]; image: string,}
export async function getStaticPaths() { const posts: MarkdownInstance<Frontmatter>[] = await Astro.glob<Frontmatter>("../../content/posts/*.md");
return posts .map((post) => ({ params: { number: post.frontmatter.number, }, props: { post: { frontmatter: post.frontmatter, Content: post.Content, }, }, }));}
const frontmatter = Astro.props.post.frontmatter;---<!--HTMLを記述する-->
これらを使うことで frontmatter から情報を得られて、frontmatter の情報を元にルーティングを分けたり、タグによるフィルターや日時によるソートが簡単に行えます。
しかしそれらを esa のデフォルトの frontmatter だけで行うのは少し難しいです。 そこで任意の frontmatter をつけた上でコミットする方法を検討しました。
esa の webhook を受け取る API を作る
esa の webhook を受け取るための API はなんでもいいのですが、デプロイの利便さとログの見やすさから CloudflareWorkers + Hono を採用しました。
Hono のエンドポイントを作成する
import { Hono } from "hono";import Webhook from "./webhook/Webhook";
const app = new Hono();
app.post("/api/webhook", async (c) => { return await Webhook(c);});
export default app;
esa の Generic webhook のデータ形式
Generic webhook では以下のドキュメントに従ってデータが送られてきます
TSの型にするとこのようになる
export type PostCreate = { kind: "post_create"; team: { name: string; }; post: { name: string; body_md: string; body_html: string; message: string; wip: boolean; number: number; url: string; }; user: { icon: { url: string; thumb_s: { url: string; }; thumb_ms: { url: string; }; thumb_m: { url: string; }; thumb_l: { url: string; }; }; name: string; screen_name: string; };};
export type PostUpdate = { kind: "post_update"; team: { name: string; }; post: { name: string; body_md: string; body_html: string; message: string; wip: boolean; number: number; url: string; diff_url: string; }; user: { icon: { url: string; thumb_s: { url: string; }; thumb_ms: { url: string; }; thumb_m: { url: string; }; thumb_l: { url: string; }; }; name: string; screen_name: string; };};
export type PostArchive = { kind: "post_archive"; team: { name: string; }; post: { name: string; body_md: string; body_html: string; message: string; wip: boolean; number: number; url: string; }; user: { icon: { url: string; thumb_s: { url: string; }; thumb_ms: { url: string; }; thumb_m: { url: string; }; thumb_l: { url: string; }; }; name: string; screen_name: string; };};
export type PostDelete = { kind: "post_delete"; team: { name: string; }; post: { name: string; wip: boolean; number: number; }; user: { icon: { url: string; thumb_s: { url: string; }; thumb_ms: { url: string; }; thumb_m: { url: string; }; thumb_l: { url: string; }; }; name: string; screen_name: string; };};
frontmatter を追加する
任意のfrontmatterを追加するためには esa の post の中に何らかの方法で内容を記述しなければなりません これにマークダウンで本文を記述する前にymlのコードブロックを用意してその中に frontmatter に追加したいいくつかの要素を入れることで対応しました
形式としては以下のようになります
\```yml title=".yml"isActive: falsetags:description:repository:youtube:website:image:\```
<!--more-->
frontmatter を生成する
isActive: truenumber: 35title: esa の post のタイトルdate: コミットした日付options: tags: - esa の純正のタグとは関係のないタグ(markdown の中で記述したもの) description: posts の説明(markdown の中で記述したもの) repository: posts に関連するレポジトリ(markdown の中で記述したもの) youtube: posts に関連する youtube (markdown の中で記述したもの) website: posts に関連するウェブサイト(markdown の中で記述したもの) image: posts に関連する画像(markdown の中で記述したもの)
上の4つは markdown に yml が記述されているかに関係なく生成が可能なので追加しています それ以外のものは option として yml に書いてあればそれを入れて、なければ null を入れるようにしています
処理はYaml のライブラリを使っています
GitHub へのコミット
Github へのコミットを行う方法については別の解説を書きましたので、そちらを参考にしてください
https://www.kanakanho.dev/posts/id/58
終わりに
esa は編集とマークダウンの管理が便利で大変重宝しています。これまでは esa の中だけでしか編集した文書を利用できなかったのですが、これでどんなところでも使えるようになったのでますます便利さが増えていくと思います。 これからもどんどん記事を書いていきたい!