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を読んでいく#10 - #15

0
Last updated at Posted at 2025-12-02

この記事何?

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

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

#10 New API

src/methods.js
27種類のHTTPメソッドに対応。リファクタリング

 変更前:
  // hono.js内で直接実装
  class Hono {
    get(...args) { return this.addRoute('GET', ...args) }
    post(...args) { return this.addRoute('POST', ...args) }
    put(...args) { return this.addRoute('PUT', ...args) }
    delete(...args) { return this.addRoute('DELETE', ...args) }
    patch(...args) { return this.addRoute('PATCH', ...args) }
  }

  変更後:
  // src/methods.js(新規ファイル)
  const methods = [
    'get', 'post', 'put', 'head', 'delete', 'options',
    'trace', 'copy', 'lock', 'mkcol', 'move', 'patch',
    'purge', 'propfind', 'proppatch', 'unlock', 'report',
    'mkactivity', 'checkout', 'merge', 'm-search', 'notify',
    'subscribe', 'unsubscribe', 'search', 'connect'
  ]

  module.exports = methods

  // src/hono.js
  const methods = require('./methods')

  class Hono {
    constructor() {
      for (const method of methods) {
        this[method] = (...args) => {
          return this.addRoute(method, ...args)
        }
      }
    }
  }

mkcol、purge、propfind、proppatch、mkactivity全く知らなかったので調べる。
WebDAVのメソッドらしい。WebDAVなんぞや。

WebDAV(Web-based Distributed Authoring and Versioning、ウェブダブ)はHypertext Transfer Protocolを拡張したもので、Webサーバ上のファイル管理を目的とした分散ファイルシステムを実現するプロトコルである。

利用中のWebサーバーを活用して、ファイル共有と編集ができる技術や機能をWebDAV(ウェブダブ)といいます。現在、多くのレンタルサーバーのプランで利用可能です。VPSなどで、利用者自身が設定する方法もあります。
現在普及しているGoogleドライブなどのオンラインストレージ・サービスにWebDAVは似ており、クラウド上で複数の利用者が、ファイル共有とその場での編集作業を比較的安全に行うことができます。
WebDAVについてご存知ない方もいると思われ、地味な印象がありますが、実はマイクロソフトが1999年に発表した歴史のある技術です。おかげでセキュリティを保ちつつ、簡単で便利な機能が使えるようになりました。

mkactivityはSubversionのメソッドらしい。

ほえー。

各メソッドの意味

MKCOL(Make Collection)

  • 意味: 新しいコレクション(ディレクトリ)を作成する
  • 用途: WebDAV 上にフォルダを作るとき
  • 例: MKCOL /documents/reports/

「フォルダ作って!」というリクエスト。


PURGE

  • 意味: キャッシュサーバ(特に Varnish Cache)に「このキャッシュ消して!」と要求する
  • 用途: Web サイトでキャッシュを即座に無効化したいとき
  • 例: PURGE /index.html

WebDAV ではなく キャッシュ無効化 のための“拡張”HTTPメソッド。


PROPFIND(Property Find)

  • 意味: ファイル/フォルダのメタデータを取得する

  • 用途:

    • WebDAV でファイル一覧を取得する
    • 更新日時、サイズ、カスタムプロパティを読む
  • 例: PROPFIND /docs/

「そのファイルの属性(情報)教えて」というリクエスト。


PROPPATCH(Property Patch)

  • 意味: メタデータ(プロパティ)を書き換える
  • 用途: WebDAV でタイトル・タグ・作成者などの属性を更新する
  • 例: 権限やカスタムプロパティを変更

「そのファイルの属性を書き換えて」というリクエスト。


MKACTIVITY(Make Activity)

  • 意味: Subversion(SVN)が使うメソッドで、「アクティビティ(コミット単位の作業領域)」を作る
  • 用途: SVN がコミット操作の前に内部的に呼ぶ
  • 例: SVN クライアントが自動で MKACTIVITY /!svn/act/xxx を送る

SVN が「コミット用の作業箱を作りますね」と使う内部メソッド。


メソッド 用途 分類
MKCOL フォルダ作成 WebDAV
PURGE キャッシュ削除 Varnish/拡張HTTP
PROPFIND メタデータ取得 WebDAV
PROPPATCH メタデータ更新 WebDAV
MKACTIVITY SVN のコミット準備 Subversion

はい。とりあえず。なんとなく。そういうのがある。わかった。完全に理解。

次。

  変更前:
  const Hono = require('hono')
  const app = Hono()  // 関数として呼ぶ

  変更後:
  const { Hono } = require('hono')
  const app = new Hono()  // クラスとしてnewで作る
  • Named importに対応({ Hono }でimport可能に)
  • クラスは型パラメータを渡せるので、TypeScriptとの親和性向上する
  • ES Modules対応への第一歩

Named Exportだと何が嬉しいか

# メリット Named Export Default Export 説明
1 複数 export 可能 ○(複数OK) △(1つだけ) Named は1ファイルからいくつでも export できる
2 名前の明確さ ○(名前固定、asで変更可) ○(自由に名前変更可だが混乱の元) Named は API の名前が明示的で分かりやすい
3 Tree Shaking ◎(不要な export は削除されやすい) △(まとめてimportされがち) Named のほうが最適化されやすくバンドルが軽くなる
4 ESM / CommonJS 互換 ○(書き方ほぼ同じ) △(CJSとESMで書き方が違う) Named だと両者の書き方が揃って扱いやすい
5 IDE 補完が強力 ◎({ } 内に候補表示) △(一度変数に入れないと補完されない) Named の方が自動補完が効きやすい

らしい。この辺もなんとなく使ってるから勉強していきたいところ。

TypeScript型定義ファイルの追加

  新規: src/hono.d.ts

  export class Hono {
    router: Router
    middlewareRouters: Router[]

    getRouter(): Router
    addRoute(method: string, args: any[]): Hono
    matchRoute(method: string, path: string): Node
    createContext(req: Request, res: Response): Context
    dispatch(req: Request, res: Response): Response
    handleEvent(event: FetchEvent): Response
    fire(): void

    notFound(): Response
    route(path: string): Hono
    use(path: string, middleware: any): void

    all(path: string, handler: any): Hono
    get(path: string, handler: any): Hono
    post(path: string, handler: any): Hono
    put(path: string, handler: any): Hono
    head(path: string, handler: any): Hono
    delete(path: string, handler: any): Hono
  }

  export class Middleware {}

#11

なし

#12 Create CODE_OF_CONDUCT.md

CODE_OF_CONDUCT.mdの追加。ハラスメント防止などプロジェクトの行動規範(Code of Conduct)を記述したファイル。
Contributor Covenant(バージョン2.0)に基づいて作成。

コミュニティ影響ガイドラインはMozilla の Code of Conduct Enforcement Ladderに着想を得ている

こういう模範的ガイドラインがあるのを知らなかった。

#13 Add keywords to package.json

"keywords": [
    "web",
    "app",
    "http",
    "application",
    "framework",
    "router",
    "cloudflare",
    "workers",
    "fastly",
    "compute@edge"
  ]

package.jsonにkeywordsフィールド追加。知らんかった。npm searchで表示されるので、パッケージの検索エンジンでの発見性が高まる。

#14 Update README, Add instruction

Honoを使って、ゼロからCloudflare Workersを作成する手順の追加

#15 Update d.ts

Web標準APIをinterfaceに変更

 変更前:
  declare class FetchEvent {}
  declare class Request {}
  declare class Response {}
  declare class Context {}

  変更後:
  declare interface FetchEvent {}
  declare interface Request {}
  declare interface Response {}
  • ブラウザやCloudflare Workersが提供
  • classではなくinterfaceとして定義することで、実際の型との衝突を避ける??

Handlerの型定義を追加
type Handler = (c: Context, next: () => void) => Response | void

  • これまでanyだったハンドラーの型を明確化することで、ユーザーがハンドラーを書くときに型補完が効く
  // 変更前: 型がない
  app.get('/hello', (c, next) => {
    // c, next の型が any
    return new Response('Hello');
  });

  // 変更後: 型がある
  app.get('/hello', (c, next) => {
    // c: Context
    // next: () => void
    // 戻り値: Response | void
    return new Response('Hello');
  });

Nodeクラスの内部構造を定義

変更前:
  declare class Node {}

  変更後:
  declare class Node {
    method: string
    handler: any
    children: Node[]
    middlewares: any[]

    insert(method: string, path: string, handler: any): Node
    search(method: string, path: string): Result
  }

  type Result = {
    handler: any
    params: {}
  }
  • Nodeクラス(ルーティングツリーのノード)の内部構造を明確化
  • デバッグ時にNodeの中身が分かる
  • 型推論がより正確になる

Contextクラスの詳細化

変更前:
  declare class Context {}

変更後:
  declare class Context {
    req: Request
    res: Response
    newResponse(params: {}): Response
  }
使用例:

  app.get('/api', (c) => {
    c.req    // Request 型
    c.res    // Response 型
    c.newResponse({ ... })  // Response を返す
  });

非同期処理(async)対応

変更前:
  matchRoute(method: string, path: string): Node
  createContext(req: Request, res: Response): Context
  dispatch(req: Request, res: Response): Response
  handleEvent(event: FetchEvent): Response

変更後:
  matchRoute(method: string, path: string): Promise<Node>
  createContext(req: Request, res: Response): Promise<Context>
  dispatch(req: Request, res: Response): Promise<Response>
  handleEvent(event: FetchEvent): Promise<Response>

Middlewareクラスの型改善

変更前:
  declare interface BuiltinMiddleware {}

  export class Middleware {
    static defaultFilter: BuiltinMiddleware
    static poweredBy: BuiltinMiddleware
  }

変更後:
  export class Middleware {
    static defaultFilter: Handler
    // Add builtin middlewares
    static poweredBy: Handler
  }
  • ビルトインミドルウェアもHandler型であることを明確化

その他、メソッドの並び順を整理。

分からん。眺めただけだと、何してるかわかんない。
型むずい。次。

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?