🔥

Honoのv3.2が出ました

2023/05/19に公開

Honoのv3.2が出ました。

https://github.com/honojs/hono/releases/tag/v3.2.0

今回のアップデートについて、リリースノートとほぼ同じ内容ですが、書きます。

Honoのステータス

ちなみに現在のHonoのステータス。GitHubスターは4.4Kとなっています。

Star History Chart

新しい機能

今回はマイナーアップデートとなります。破壊的変更は含まれないものの、大きな機能がいくつか追加されました。

  • 2つの新しいルーター: LinearRouter and PatternRouter
  • プリセットという概念と実装: hono/tiny, hono/quick
  • app.mount()
  • Node adapter serverの初のメジャーバージョン「v1.0.0」がリリース
  • AWS Lambda function URLsのサポート
  • Cookie Middlewareの追加
  • hono/nextjsからhono/vercelへのリネーム

ではひとつずつ見ていきましょう。

新しいルーター

2つの新しいルーターを紹介します。どちらも@usualomaさんが作りました。

LinearRouter

LinearRouterは非常にquickです。Honoが標準で使っているRegExpRouterはJavaScript界で一番速いルーターの一つですが、ルーティングパスを登録するのが少しだけ遅いです。

app.get('/', handler) // <=== パスの登録 - ここが少し遅い

//...

app.fetch(request)  // <=== ルーティングする - ここはめちゃ速い

ですので、Fastly Compute@Edgeなど、リクエストごとに毎回アプリが初期化される環境ではRegExpRouterがベストの選択とは言えません。

LinearRouterはルーティングパスの登録を非常に素早く行います。他のJavaScriptの高速なルーターと比べても速いです。以下がパスの登録フェーズを含めたベンチマークの結果の一つです。

• GET /user/lookup/username/hey
----------------------------------------------------- -----------------------------
LinearRouter     1.82 µs/iter      (1.7 µs … 2.04 µs)   1.84 µs   2.04 µs   2.04 µs
MedleyRouter     4.44 µs/iter     (4.34 µs … 4.54 µs)   4.48 µs   4.54 µs   4.54 µs
FindMyWay       60.36 µs/iter      (45.5 µs … 1.9 ms)  59.88 µs  78.13 µs  82.92 µs
KoaTreeRouter    3.81 µs/iter     (3.73 µs … 3.87 µs)   3.84 µs   3.87 µs   3.87 µs
TrekRouter       5.84 µs/iter     (5.75 µs … 6.04 µs)   5.86 µs   6.04 µs   6.04 µs

summary for GET /user/lookup/username/hey
  LinearRouter
   2.1x faster than KoaTreeRouter
   2.45x faster than MedleyRouter
   3.21x faster than TrekRouter
   33.24x faster than FindMyWay

また、このコメントによるとFastly Compute@Edgeの環境ではRegExpRouterより40%速くなりました。

PatternRouter

PatternRouterは非常にtinyです。HonoのデフォルトのルーターはSmartRouterで、RegExpRouterとTrieRouterが登録されています。たいていの場合は、高速なRegExpRouterが動き、TrieRouterはRegExpRouterがカバーできないレアケースを補います。とても高性能なのですが、少しファイルサイズが大きくなってしまいます。そこで、ファイルサイズの小さい、PatternRouterの出番です。

もしリソースが限られている場面なら、PatternRouterが使った方がいいでしょう。

PatternRouterだけを使ったHonoアプリケーションのサイズは12KB以下と非常に小さいです。

yusuke $ esbuild --outdir=dist --bundle --minify ./src/index.ts

  dist/index.js  11.9kb

⚡ Done in 9ms

ちなみに、Expressは572KBです。

プリセットという概念と実装

Honoにはこのように目的別にいくつかのルーターがあります。ユーザーはHonoのコンストラクタで好きなルーターを指定することができます。

プリセットは、毎回ルーターを指定せずとも一般的なユースケースに対応するものです。どのプリセットを使ってもHonoクラスの使い方は全て同じです。

今回はhono/tinyhono/quickを紹介します。

hono/tiny

hono/tinyプリセットはPatternRoutrerだけを使います。つまり以下のコードのようなものです。

this.router = new PatternRouter()

hono/tinyを使うには、hono/tinyimportして、Honoクラスをいつもと同じように使います。

import { Hono } from 'hono/tiny'

const app = new Hono()

//...

これだけで、ルーターが切り替わり、ファイルサイズが小さくなります。

hono/quick

hono/quickプリセットはLinearRouterだけを使います。

this.router = new LinearRouter()

他のプリセットを同じようにhono/quickを使うことができます。

import { Hono } from 'hono/quick'

どのプリセットを使えばいいのか?

これで我々はhonohono/tinyhono/quickと3つのプリセットを手に入れました。ではどのプリセットを使えばいいのでしょうか?以下を参考にしてください。

プリセット 最適なプラットフォーム
hono ほとんどのユースケースでオススメです。ルーティング登録がhono/quickより遅いとはいえ、一度登録されれば高いパフォーマンスを発揮します。DenoBun、それにNode.jsなどを使った常駐型のサーバーには最適です。また、Cloudflare WorkersDeno DeployLagonでもこのプリセットを使えばいいでしょう。というのもこれらのようなv8 isolateを使った環境では、isolateは起動後しばらく行き続けるからです(時間が決まっていたり、メモリなどの状況に応じて変化したります)。
hono/quick このプリセットはリクエストのたびにアプリケーションが初期化されるような環境に適しています。Fastly Compute@Edgeはこれに従うので、このプリセットを使うといいでしょう。
hono/tiny このプリセットは一番ファイルサイズの小さいプリセットです。リソースが限られている環境にはいいでしょう。

app.mount()

新しい機能、app.mount()を使うと、itty-routerなど、Hono以外のフレームワークを使ったアプリケーションと統合することができます。

// Create itty-router application
const ittyRouter = IttyRouter()

// Handle `GET /itty-router/hello`
ittyRouter.get('/hello', () => new Response('Hello from itty-router!'))

// Hono application
const app = new Hono()

// Hono application
app.mount('/itty-router', ittyRouter.handle)

Remixもできます。

import { Hono } from 'hono'
import { env } from 'hono/adapter'
import { serveStatic } from 'hono/cloudflare-workers'

import { createRequestHandler } from '@remix-run/cloudflare'
import * as build from './build'

// Remix application
// @ts-ignore
const handleRemixRequest = createRequestHandler(build, process.env.NODE_ENV)

// Hono application
const app = new Hono()

// Static files for Remix
app.get(
  '/remix/build/*',
  serveStatic({
    root: './',
  })
)

// Mount Remix app
app.mount('/remix', handleRemixRequest, (c) => {
  return { env: env(c) }
})

これはつまり、itty-routerRemixQwik、もしくはSolidJSなど、どんなフレームワークで作ろうともHonoのアプリケーションにマウントすることができるということです。

エコシステム

このapp.mount()によって、我々は2つのコンセプトを提唱します。adaptmountです。adaptとはHonoがあらゆるランタイムに「適応する」こと、mountとはあらゆるフレームワークを「マウントする」ことです。これとミドルウェアを組み合わせることで、以下の図のような非常に壮大なエコシステムをつくることができます。

SS

Honoはウェブフレームワークなだけにはとどまらなくなります。

このコンセプトの大きな側面の一つとして、フレームワークは個別にCloudflare Workers、Cloudflare Pages、Vercel、Deno、Bunなどそれぞれのプラットフォーム用のアダプタを用意することが必要なくなります。

Honoがないと以下のようになるでしょう。

SS

もし、あなたのフレームワークがWeb Standard APIに準拠するものであれば、こうした作業は必要なくなります。Honoがあなたのフレームワークをマウントし、どんなランタイムでも動くようにしてあげるのです。

SS

さらに、他のフレームワークでHonoのミドルウェアを使うことができます。例えば、iity-routerで作ったアプリケーションにBasic認証を追加したい時に、個別でスクラッチから実装する必要はなく、Honoのミドルウェアを追加するだけで済みます。

app.use('/another-app/admin/*', basicAuth({ username, password }))

これが私達が作りたかったエコシステムの姿です。

Node.js adapter serverのv1.0.0がリリース

Node.js adapterの最初のメジャーバージョンとなるv1.0.0がさきほどリリースされました!このバージョンではNode.js v18以降のネイティブなWeb Standard APIのみを使っています。外部ライブラリでポリフィルはしていません。これによりファイルサイズがものすごく抑えられました。そして、我々は本当にWeb Standardを追従していくことになりました。

SS

今すぐ使い始めることができます。

npm install @hono/node-server

npmからインストールしたら、serveimportしてHonoのアプリに適応するだけです。

import { serve } from '@hono/node-server'
import { Hono } from 'hono'

const app = new Hono()
app.get('/', (c) => c.text('Hono meets Node.js'))

serve(app, (info) => {
  console.log(`Listening on http://localhost:${info.port}`)
})

ホスト名を含んだルーティングのサポート

新しいgetPath()を使うとホスト名を含んだルーティングを行うことができます。

const app = new Hono({
  getPath: (req) => req.url.replace(/^https?:\/\//, ''),
})

app.get('www1.example.com/hello', (c) => c.text('hello www1'))
app.get('www2.example.com/hello', (c) => c.text('hello www2'))

このgetPath()を応用すると、例えば、hostヘッダに基づいたルーティングなんてものもできてしまいます。

const app = new Hono({
  getPath: (req) => req.headers.get('host') + req.url.replace(/^https?:\/\/[^\/]+/, ''),
})

app.get('www1.example.com/hello', (c) => c.text('hello www1'))

// The following request will match the route:
// new Request('http://www1.example.com/hello', {
//  headers: { host: 'www1.example.com' },
// })

AWS Lambda functions URLsのサポート

AWS LambdaアダプターがLambda functions URLsをサポートしました。

Cookieミドルウェア

新しくCookieミドルウェアが導入されます。

import { getCookie, setCookie } from 'hono/cookie'

// ...

app.get('/cookie', (c) => {
  const yummyCookie = getCookie(c, 'yummy_cookie')
  // ...
  setCookie(c, 'delicious_cookie', 'macha')
  //
}

これに伴い、c.req.cookie()c.cookie()は非推奨となり、次のメジャーリリース「v4」で廃止されます。

hono/nextjs to hono/vercel

hono/nextjshono/vercelにリネームしました。hono/nextjsは使えますが、非推奨となり、「v4」で廃止されます。

その他

他のアップデートは以下の通りです。

  • serve-staticrewriteRequestPathオプションが追加されました。
  • app.routerNameが追加されました。
  • レスポンスヘッダの値を完全にクリアできるようになりました。
  • http-status.tsからHTTPステータスメッセージを削除しました。
  • パフォーマンスの向上。
  • バグの修正。

まとめ

以上!全てのコントリビューター、スポンサー、ユーザーに感謝です。

Discussion