この記事何?
エンジニア歴1年半。業務で利用しているHonoが大好きだが、TSもプログラミング知識も弱々すぎて上手く使いこなせない。速いはずが多分生かせてない。
ちゃんと理解するためにソースコードリーディングしたい。
でも目的なくソースコード眺めても意味ないし……ねむい……
そうだ!PRを全部読めば、変遷やなぜ変更されているかストーリー的に分かるのでは。
と思ったのでチャレンジしてみる。PR2000以上あるので、全部できるかは知らん。
https://github.com/honojs/hono
61 chore: Add serve static example
静的ファイル配信のサンプルを追加。
// examples/serve-static/src/index.ts
import { Hono } from 'hono'
const app = new Hono()
// 静的ファイルを配信(KV Sitesを使用)
app.get('/static/*', async (c) => {
const path = c.req.path.replace(/^\/static/, '')
const object = await c.env.ASSETS.get(path)
if (!object) {
return c.text('Not Found', 404)
}
return new Response(object.body, {
headers: {
'Content-Type': object.httpMetadata.contentType,
},
})
})
export default app
static/hello.txt:
=> Hello from static file!
#62 feat: Add new shortcuts for request/response
ショートカットメソッドを追加して、よりシンプルなコードが書けるように修正
追加されたショートカット
- Context ヘッダー設定のショートカット
// 変更前
app.get('/', (c) => {
c.res.headers.set('X-Custom', 'value')
return c.text('Hello')
})
// 変更後
app.get('/', (c) => {
c.header('X-Custom', 'value') // ショートカット
return c.text('Hello')
})
2.ステータスコードとステータステキストの設定
app.get('/', (c) => {
c.status(201) // ステータスコード設定
c.statusText('Created!!!!') // ステータステキスト設定
return c.body('Resource created')
})
3.c.body() メソッド
app.get('/', (c) => {
return c.body('Hello') // シンプルなレスポンス
})
実装
// src/context.ts
export class Context {
private _headers: Headers = {}
private _status: number
private _statusText: string
header(name: string, value: string): void {
this._headers[name] = value
}
status(number: number): void {
this._status = number
}
statusText(text: string): void {
this._statusText = text
}
body(body: BodyInit, init?: ResponseInit): Response {
return this.newResponse(body, init)
}
newResponse(body: BodyInit, init?: ResponseInit): Response {
return new Response(body, {
status: this._status || init?.status || 200,
statusText: this._statusText || init?.statusText,
headers: { ...init?.headers, ...this._headers },
})
}
}
#63 refactor(equal): user Expect.toStrictEqual to simplify value comparison
テストコードのリファクタリング
// 変更前
expect(res.param).toEqual({ id: '123' })
// 変更後
expect(res.param).toStrictEqual({ id: '123' })
toStrictEqual の利点:
- より厳密な比較(undefined プロパティも検証)
- より安全なテスト
#64 feat: Cookie middleware
Cookie ミドルウェアを追加。
使用方法
import { Hono, Middleware } from 'hono'
const app = new Hono()
app.use('*', Middleware.cookie())
app.get('/', (c) => {
// Cookieを読み取る
const sessionId = c.req.cookie('session_id')
// Cookieを設定
c.cookie('user', 'john', {
maxAge: 60 * 60 * 24, // 1日
httpOnly: true,
secure: true,
sameSite: 'Lax',
})
return c.text('Hello')
})
CookieOptions
type CookieOptions = {
domain?: string // Cookie有効ドメイン
expires?: Date // 有効期限
httpOnly?: boolean // JavaScriptからアクセス不可
maxAge?: number // 有効期限(秒)
path?: string // Cookie有効パス
secure?: boolean // HTTPS のみ
sameSite?: 'Strict' | 'Lax' | 'None' // CSRF対策
}
実装
// Cookieをパース
const parse = (cookie: string): Cookie => {
const pairs = cookie.split(/;\s*/g)
const parsedCookie: Cookie = {}
for (let i = 0, len = pairs.length; i < len; i++) {
const pair = pairs[i].split(/\s*=\s*([^\s]+)/)
parsedCookie[pair[0]] = decodeURIComponent(pair[1])
}
return parsedCookie
}
// Cookieをシリアライズ
// 例:Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secureのような文字列を作る
const serialize = (name: string, value: string, opt?: CookieOptions): string => {
// 特殊文字をエンコード 例:'john@example.com' → 'john%40example.com'
let cookie = `${name}=${encodeURIComponent(value)}`
if (opt?.maxAge) {
cookie += `; Max-Age=${opt.maxAge}`
}
if (opt?.httpOnly) {
cookie += '; HttpOnly'
}
if (opt?.secure) {
cookie += '; Secure'
}
if (opt?.sameSite) {
cookie += `; SameSite=${opt.sameSite}`
}
return cookie
}
#65 refactor: refactor something
プロジェクト構造などのリファクタリング。
cookieの処理の中身などが知れてよかった。今回はここまで。