0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HonoのマージされたPRを読んでいく#76 - #80

0
Last updated at Posted at 2025-12-15

この記事何?

エンジニア歴1年半。業務で利用しているHonoが大好きだが、TSもプログラミング知識も弱々すぎて上手く使いこなせない。速いはずが多分生かせてない。
ちゃんと理解するためにソースコードリーディングしたい。

でも目的なくソースコード眺めても意味ないし……ねむい……
そうだ!PRを全部読めば、変遷やなぜ変更されているかストーリー的に分かるのでは。
と思ったのでチャレンジしてみる。PR2000以上あるので、全部できるかは知らん。
https://github.com/honojs/hono

#76 feat: serve static middleware (Cloudflare only)

静的ファイル配信のミドルウェアを追加。

src/middleware/serve-static/serve-static.ts:

  import type { Context } from '../../context'
  import { getContentFromKVAsset } from '../../utils/cloudflare'
  import { getMimeType } from '../../utils/mime'

  type Options = {
    root: string
  }

  const DEFAULT_DOCUMENT = 'index.html'

  // Cloudflare Workers 専用
  export const serveStatic = (opt: Options = { root: '' }) => {
    return async (c: Context, next: Function) => {
      await next()  // ← まず他のルートを試す

      const url = new URL(c.req.url)
      const path = getKVPath(url.pathname, opt.root)

      // KV Sites からファイルを取得
      const content = await getContentFromKVAsset(path)

      if (content) {
        // Content-Type を自動判定
        const mimeType = getMimeType(path)
        if (mimeType) {
          c.header('Content-Type', mimeType)
        }
        c.res = c.body(content)
      } else {
        // ファイルが見つからない場合は何もしない
        // console.debug(`Static file: ${path} is not found`)
      }
    }
  }

  // パスを正規化する関数
  const getKVPath = (filename: string, root: string): string => {
    if (filename.endsWith('/')) {
      // /top/ => /top/index.html
      filename = filename.concat(DEFAULT_DOCUMENT)
    } else if (!getMimeType(filename)) {
      // /top => /top/index.html(拡張子がない場合)
      filename = filename.concat('/' + DEFAULT_DOCUMENT)
    }

    // /foo.html => foo.html(先頭のスラッシュを削除)
    filename = filename.replace(/^\//, '')

    // assets/ => assets(末尾のスラッシュを削除)
    root = root.replace(/\/$/, '')

    // ./assets/foo.html => assets/foo.html
    let path = root + '/' + filename
    path = path.replace(/^\.?\//, '')

    return path
  }

パス変換の例:

  // 例1: ディレクトリアクセス
  getKVPath('/blog/', 'assets')
  // → 'assets/blog/index.html'

  // 例2: 拡張子なし
  getKVPath('/about', 'static')
  // → 'static/about/index.html'

  // 例3: ファイル指定
  getKVPath('/style.css', 'public')
  // → 'public/style.css'
  1. MIME Type判定の実装

src/utils/mime.ts:

  export const getMimeType = (filename: string): string => {
    const regexp = /\.([a-zA-Z0-9]+?)$/
    const match = filename.match(regexp)

    if (!match) {
      return
    }

    let mimeType = mimes[match[1]]

    // テキストファイルには charset を追加
    if (mimeType.startsWith('text') || mimeType === 'application/json') {
      mimeType += '; charset=utf-8'
    }

    return mimeType
  }

  const mimes: { [extension: string]: string } = {
    html: 'text/html',
    css: 'text/css',
    js: 'text/javascript',
    json: 'application/json',
    png: 'image/png',
    jpg: 'image/jpeg',
    gif: 'image/gif',
    svg: 'image/svg+xml',
    ico: 'image/vnd.microsoft.icon',
    txt: 'text/plain',
    pdf: 'application/pdf',
    // ... 89種類の拡張子に対応
  }

動作例:

  getMimeType('index.html')
  // → 'text/html; charset=utf-8'

  getMimeType('style.css')
  // → 'text/css; charset=utf-8'

  getMimeType('image.png')
  // → 'image/png'

  getMimeType('data.json')
  // → 'application/json; charset=utf-8'

使い方

  import { Hono } from 'hono'
  import { serveStatic } from 'hono/middleware'

  const app = new Hono()

  // 静的ファイル配信ミドルウェアを適用
  app.use('/static/*', serveStatic({ root: './assets' }))

  // 動的ルート
  app.get('/api/hello', (c) => c.json({ message: 'Hello' }))

  export default app

むずい

#77 fix: mustache template encoding

Mustache テンプレートのエンコーディング問題を修正.

KV Sites から取得したテンプレートが ArrayBuffer 型になることがある。

 const content = await getContentFromKVAsset('template.mustache')
  // content = ArrayBuffer { ... }  ← 文字列じゃない

  Mustache.render(content, data)  // ← エラー!
// ArrayBuffer の場合
  const buffer = new ArrayBuffer(10)
  bufferToString(buffer)
  // → TextDecoder で UTF-8 文字列に変換

  // すでに文字列の場合
  const str = "Hello"
  bufferToString(str)
  // → そのまま返す

#78 fix: about parsedBody on Request

Request.parsedBody の型定義をグローバルに移動。重複した型定義を削除。

#79 example: fix blog example

Blog サンプルの修正。ビルド設定とテストコードを改善。

#80 なし

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?