2023/12/24
Tag : Craft CMS

Craft CMS のデータ表示するのに Hono を触ってみる

最近気になっている Hono 。

Hono - Ultrafast web framework for the Edges
https://hono.dev/

気にはなっているけど特に触ったことがなかったのでこの機会に触ってみる。

CMSと組み合わせて使うとしたらどういう使い方があるのかなぁ、、、と考えつつ無難にデータとって出すというくらいが自分でやれそうなところかな、と。

ちょうど記事があったこの方みたいな感じ。

日記サイトをHono x Cloudflareに置き換えた
https://qlitre-dialy.ink/post/...

ここまではやれていないが。

Hono を使われてる方の記事とかみると色々と開発されててもうすごいというかんじ。。。

開発者体験っていう意味だと加藤さんが書かれてたような

Astro と Movable Type Data API でページネーション (ページ分割) を実装する - WWW WATCH
https://hyper-text.org/archive...

MTのテンプレートとGitワークフローの組み込みやすい・にくいとかとかもあるしなぁ。
CMS側のテンプレートでやるよりはヘッドレス構成でやる方がいいのか?という話はなかなか答えが出せていない所ではある。
Craft だったら DDEV あればローカルの環境出来るしまぁそれでいいんじゃ無いか、とかとか。

CMSと絡めて Hono を使うということであれば、このスライドにあるようなかんじで CMS の前に置いて使うという方が色々といいのかも知れない。

WordPressとhonoで、 Cloudflare Workersプロキシパターンをやってみた | ドクセル
https://www.docswell.com/s/hid...

とはいえ、色々と調べながら触ってみたが、記事やスライドを公開してくれていて読んでいるだけでも勉強にもなるし、楽しい。
普段自分が手を動かしていないからスキルが足りないのを痛感する。


Hono の公式ドキュメントにある手順でまずは始めてみる。

Hono - Ultrafast web framework for the Edges
https://hono.dev/top

簡単に立ち上がって確かに早い(気がする)。
普段あまりこういうのはやらないから比較対象が少なめ。

次は Craft のデータをとって並べてみる。
src/index.tsx を触ってみる。

そもそも Craft CMS の場合は GraphQL が標準であるからそこの fetch の書き方自体から。。

import { Hono } from 'hono'
import { renderer } from './renderer'
import { prettyJSON } from 'hono/pretty-json'

const app = new Hono()

app.get('*', renderer)
app.use('*', prettyJSON())

app.get('/sample2/', async (c) => {

  const request = new Request("https://example.com/api", {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer hogehoge,
    },
    body: JSON.stringify({
      query: `
        query{
          entries(limit:10,siteId:1){
            title
            id
            url
            uri
            slug
            postDate @formatDateTime(format: "Y-m-d")
          }
      }`,
    })
  });
  const response = await fetch(request)
  const json = await response.text()
  const data = JSON.parse(json)
  return c.json(data);
})
export default app

って感じでデータをとってならべるようにして、 http://localhost:5173/sample2/ で確認できる。

fetch する書き方とか全然わかって無くてそういうのを調べながら書くから時間はかかるが、、、

一覧がとれたから ul/li で並べてみる

import { Hono } from 'hono'
import { renderer } from './renderer'
import { prettyJSON } from 'hono/pretty-json'

const app = new Hono()

app.get('*', renderer)
app.use('*', prettyJSON())

app.get('/list/', async (c) => {

  const request = new Request("https://example.com/api", {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer hogehoge',
    },
    body: JSON.stringify({
      query: `
        query{
          entries(limit:10,siteId:1){
            title
            id
            url
            uri
            slug
            postDate @formatDateTime(format: "Y-m-d")
          }
      }`,
    })
  });
  const response = await fetch(request)
  const json = await response.text()
  const data = JSON.parse(json)
  return c.render(
      <div>
        <h1>Hello!</h1>
        <ul>
          {data.data.entries.map((item)=> {
            return <li><a href={`/${item.uri}`}>{item.title}</a></li>
          })}
        </ul>
  </div>
  )
})
export default app

詳細ページ用のデータをとるとしたらこんな感じで :slug をわたして単一のデータを取得すればいいわけか。

import { Hono } from 'hono'
import { renderer } from './renderer'
import { prettyJSON } from 'hono/pretty-json'

const app = new Hono()

app.get('*', renderer)
app.use('*', prettyJSON())

app.get('/article/:slug', async (c) => {
  const slug = c.req.param('slug')
  const request = new Request("https://example.com/api", {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer hogehoge',
    },
    body: JSON.stringify({
      query: `
        query($slug:[String]){
          entry(siteId:1,slug:$slug){
            title
            id
            url
            uri
            slug
            postDate @formatDateTime(format: "Y-m-d")
          }
      }`,
      variables: {"slug": slug},
    })
  });
  const response = await fetch(request)
  const json = await response.text()
  const data = JSON.parse(json)
  return c.render(
      <div>
        <h1>Hello!</h1>
        <h2>title: {data.data.entry.title}</h2>
      </div>
  )
})
export default app

なるほどなるほど。

そういえば以前こういう Headless 構成のを触ってみたときに RSS とかに毎回プラグイン的なもの入れないといけなかったけど、そういうのはどうやるんだろうか??と思って触ってみる。

Movable Type でも Craft CMS でも基本的にテンプレートさえ書けば気にせず出力出来るというのに慣れてて、プラグインが必要とかになるとちょっと考えてしまう。
多分そこは自分のスキルが足りないだけで、なんてこと無い話なのかも知れない。

import { Hono } from 'hono'
import { renderer } from './renderer'
import { prettyJSON } from 'hono/pretty-json'
import { html, raw } from 'hono/html'

const app = new Hono()

app.get('*', renderer)
app.use('*', prettyJSON())

app.get('/atom.xml', async (c) => {
  // c.header('Content-Type', 'application/xml')
  const request = new Request("https://example.com/api", {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer hogehoge',
    },
    body: JSON.stringify({
      query: `
        query{
          entries(limit:10,siteId:1){
            title
            id
            url
            uri
            slug
            postDate @formatDateTime(format: "Y-m-d")
          }
      }`,
    })
  });
  const response = await fetch(request)
  const json = await response.text()
  const data = JSON.parse(json)
  return c.text(html
`<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>テスト</title>
<link href="/"/>
<link type="application/atom+xml" rel="self" href="/atmo.xml"/>
<updated>2023-12-21T15:20:57+08:00</updated>
<id>https://note.mersy418.com/atom.xml</id>
<author>
<name>mersy</name>
<email>[email protected]</email>
</author>
<entry>
<id>https://note.mersy418.com/article/update-documentation-adr-etc</id>
<link type="text/html" rel="alternate" href="https://note.mersy418.com/article/update-documentation-adr-etc?utm_source=feed"/>
<title>ドキュメントに関する記事を読んでADRとか学びが多かった</title>
<published>2023-12-19T00:00:00+08:00</published>
<updated>2023-12-20T05:22:47+08:00</updated>
<content type="html">
aaa
</content>
</entry>
</feed>`, 201, {
        'Content-Type': 'application/xml',
      }
  )

})

export default app

記事をループするところまでいけてないが、レスポンスとか見る限りはこれでとりあえず xml として返せている様子。

c.header で header を指定することができるっぽい。

Context - Hono
https://hono.dev/api/context#s...

c.text で text で返せて、 header も指定出来たのでこれでよかった。

Context - Hono
https://hono.dev/api/context#t...

あとは、 html Helper を使えば取得したデータを返せるんじゃ無かろうか?というところまで。

html Helper - Hono
https://hono.dev/helpers/html

とりあえず触ってみたところで Advent Calendar の当日になってしまったので一旦ここまで。

触ってて楽しいし面白いなー、という思いと、これなら Twig 書くのでもいいかなぁという思いと。
難しい所だ。

自分の興味だけであれば全然いいけど、実際に案件で使うような場面になったらどうしようか?
フロントエンド側はメンバーによるところもあるだろうし、、、
Cloudflare Workers とかと組み合わせて使う方が現実的かなぁとかとか。
判断するためのスキルが不足してるからとりあえず自分で触ってみるようにしないとダメだな。。


この記事は「Craft CMS Advent Calendar 2023」24日目の記事でした。
明日が最終日 @BUN の記事が楽しみ!