この記事何?
エンジニア歴1年半。業務で利用しているHonoが大好きだが、TSもプログラミング知識も弱々すぎて上手く使いこなせない。速いはずが多分生かせてない。
ちゃんと理解するためにソースコードリーディングしたい。
でも目的なくソースコード眺めても意味ないし……ねむい……
そうだ!PRを全部読めば、変遷やなぜ変更されているかストーリー的に分かるのでは。
と思ったのでチャレンジしてみる。PR2000以上あるので、全部できるかは知らん。
https://github.com/honojs/hono
#71 なし
#72 feat: Mustache Middleware
Mustache テンプレートエンジンのミドルウェアを追加。KV Sites からテンプレートを読み込んでレンダリング
Mustache テンプレートエンジンとは
様々な言語で組み合わせて使えるテンプレートエンジンです。
Loopなどロジック地味たことをする構文が存在しないので、Logic-Less templatesと呼ばれています。
タグの記法が{{hoge}}みたいになっていて、 { が口ひげみたいに見えるためmustache(口ひげ)というらしいです。
テンプレートエンジンとは
テンプレートエンジンとはデータとテンプレートを合体させ、文字列を作る仕組みのことです。
RailsのslimとかerbみたいなやつのJS版か。理解。
主な変更
- Mustache Middleware の実装
src/middleware/mustache/mustache.ts:
import type { Context } from '../../context'
import { getContentFromKVAsset } from '../../utils/cloudflare'
const EXTENSION = '.mustache'
export const mustache = () => {
return async (c: Context, next: Function) => {
let Mustache: any
try {
Mustache = await import('mustache') // 動的インポート
} catch (e) {
console.error(`Mustache is not found! ${e}`)
throw new Error(`${e}`)
}
// c.render() を追加
c.render = async (filename, view = {}, options?) => {
// KV Sites からテンプレートを取得
const content = await getContentFromKVAsset(`${filename}${EXTENSION}`)
if (!content) {
throw new Error(`Template "${filename}${EXTENSION}" is not found`)
}
// Partials(部分テンプレート)の処理
const partialArgs: { [name: string]: string } = {}
if (options) {
const partials = options as Partials
for (const key of Object.keys(partials)) {
const partialContent = await getContentFromKVAsset(`${partials[key]}${EXTENSION}`)
if (!partialContent) {
throw new Error(`Partial Template "${partials[key]}${EXTENSION}" is not found`)
}
partialArgs[key] = partialContent
}
}
// Mustache でレンダリング
const output = Mustache.render(content, view, partialArgs)
return c.html(output)
}
await next()
}
}
- Context の変更(c.body() の追加)
src/context.ts:
export class Context {
// render メソッドの型定義を追加
render: (template: string, params?: object, options?: object) => Promise<Response>
constructor(req: Request, opts?: { res: Response; env: Env; event: FetchEvent }) {
this._headers = {}
// this.body = this.newResponse // ← 削除
}
// c.body() をメソッドとして実装
body(data: any, status: number = this._status, headers: Headers = this._headers): Response {
return this.newResponse(data, {
status: status,
headers: headers,
})
}
// text(), json(), html() が c.body() を使うように変更
text(text: string, status: number = this._status, headers: Headers = {}): Response {
headers['Content-Type'] = 'text/plain'
return this.body(text, status, headers) // ← 統一
}
json(object: object, status: number = this._status, headers: Headers = {}): Response {
const body = JSON.stringify(object)
headers['Content-Type'] = 'application/json; charset=UTF-8'
return this.body(body, status, headers) // ← 統一
}
html(html: string, status: number = this._status, headers: Headers = {}): Response {
headers['Content-Type'] = 'text/html; charset=UTF-8'
return this.body(html, status, headers) // ← 統一
}
}
- Cloudflare ユーティリティの追加
src/utils/cloudflare.ts:
declare const __STATIC_CONTENT: KVNamespace, __STATIC_CONTENT_MANIFEST: string
export const getContentFromKVAsset = async (path: string): Promise<string> => {
let ASSET_MANIFEST: { [key: string]: string }
// マニフェストをパース
if (typeof __STATIC_CONTENT_MANIFEST === 'string') {
ASSET_MANIFEST = JSON.parse(__STATIC_CONTENT_MANIFEST)
} else {
ASSET_MANIFEST = __STATIC_CONTENT_MANIFEST
}
const ASSET_NAMESPACE = __STATIC_CONTENT
const key = ASSET_MANIFEST[path] || path
if (!key) {
return
}
// KV からテキストとして取得
let content: string = await ASSET_NAMESPACE.get(key, { type: 'text' })
if (content) {
content = content as string
}
return content
}
使い方
例:
import { Hono, Middleware } from 'hono'
const app = new Hono()
// Mustache ミドルウェアを使用
app.use('*', Middleware.mustache())
app.get('/', async (c) => {
// c.render() でテンプレートをレンダリング
return await c.render('index', {
title: 'Hello Mustache',
message: 'Welcome to Hono!',
}, {
// Partials(ヘッダー、フッターなど)
header: 'header',
footer: 'footer',
})
})
export default app
テンプレート(KV Sites に配置):
<!-- view/index.mustache -->
{{> header}}
<h1>{{title}}</h1>
<p>{{message}}</p>
{{> footer}}
<!-- view/header.mustache -->
<!DOCTYPE html>
<html>
<head><title>{{title}}</title></head>
<body>
<!-- view/footer.mustache -->
</body>
</html>
- c.body() がメソッドになった
- 前: this.body = this.newResponse(プロパティ)
- 後: body(data, status, headers)(メソッド) - c.render() が追加
- Mustache テンプレートをレンダリング
- KV Sites から自動で読み込み
- Partials(部分テンプレート)対応 - Cloudflare専用
- KV Sites に依存
- __STATIC_CONTENT と __STATIC_CONTENT_MANIFEST を使用
#73 fix: diable mustache middleware
Mustache ミドルウェアを一時的に無効化(コメントアウト)
#74 fix: mustache middleware
Mustache ミドルウェアの修正。動的インポートから require() に変更し、再度有効化。
なんでこう修正したかわかんなかった。多分パフォーマンスなんだろうな。ううむ。
#75 chore: tweak
細かい調整
(chore = 雑用的な変更)。
Mustache テンプレートエンジン知らなかったので面白かった。