HonoX の Cloudflare Workers 向けビルドで fetch 以外の handler を組み込む
HonoX で作成したアプリケーションを Cloudflare Workers 向けにビルドするにあたって、fetch 以外の Handlers を組み込む方法についてまとめます。
やりたいこと
今回作りたいアプリケーションの実現イメージをざっとまとめます。
- HonoX でアプリケーションを作って Cloudflare にデプロイする。
- バッチ処理を共存させたいので Pages ではなく Workers にデプロイする。
- 静的コンテンツは Cloudflare Workers の Static Assets で配信する。
- バッチ処理は Cron Triggers から起動したいので、
scheduled()
handler も実装する。
HonoX を Cloudflare Workers 向けに Vite でビルドするには、プラグインとして @hono/vite-build/cloudflare-workers
を使用します。ただし、これは fetch 以外の handler に対応していません。(2025/01/25 時点)
プラグインのソースコードを追ったところ、vite.config.ts に設定を追加して fetch 以外の handler を組み込むことができました。その方法をまとめます。
なお、この記事はアプリケーションのエントリポイントとして app/server.ts
を使用していることを前提とします。
実現方法
今回 fetch 以外の handler を組み込んだ方法は以下のとおりです。
- いずれかのファイルで実装したい handler を定義して export する。
- ビルド結果として出力されるファイルが最終的に default export するオブジェクトのプロパティに 1. で export した handler が追加されるように vite.config.ts を書く。
handler を定義して export する
handler を export するファイルは以下のように実装します。ここでは scheduled()
handler を 実装しています。
import type { Env } from 'hono'
export const scheduled: ExportedHandlerScheduledHandler<Env['Bindings']> = async (ctrl, env, ctx) => {
console.log('Scheduled', controller, env, ctx)
}
vite.config.ts の設定で handler を組み込む
@hono/vite-build/cloudflare-workers
の entryContentAfterHooks
オプションを利用します。
import build from '@hono/vite-build/cloudflare-workers'
import adapter from '@hono/vite-dev-server/cloudflare'
import honox from 'honox/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
honox({ devServer: { adapter } }),
build({
entryContentAfterHooks: [
(content) => {
return `const handlerModules = import.meta.glob('/app/handlers.ts', { eager: true })
for (const [, handlers] of Object.entries(handlerModules)) {
if (handlers) {
for (const [name, handler] of Object.entries(handlers)) {
${content}[name] = handler
}
}
}`
},
],
})
],
})
entryContentAfterHooks
で返した文字列は、ビルド結果として出力されるファイルに挿入されます。関数の引数 content
には、ビルド結果として出力されるファイルが最終的に default export するオブジェクトの変数名が入っています。
このコードによって /app/handler.ts
で export したプロパティが Workers に default export されるオブジェクトに組み込まれ、Cloudflare Workers で利用可能になります。
@hono/vite-build/cloudflare-workers
の仕組みと解決策
Hono のドキュメントには、Cloudflare Workers で fetch 以外の handler を使用するサンプルが掲載されています。
Using Hono with other event handlers
ドキュメントのサンプルを参考に HonoX のエントリポイントに実装すると、以下のようなコードになるでしょう。
import type { Env } from 'hono'
import { showRoutes } from 'hono/dev'
import { createApp } from 'honox/server'
const app = createApp()
showRoutes(app)
const scheduled: ExportedHandlerScheduledHandler<Env['Bindings']> = async (ctrl, env, ctx) => { }
export default {
fetch: app.fetch,
scheduled,
}
しかしながら、このように書いても Cloudflare Workers は scheduled()
handler を認識できません。
エントリポイントの Hono インスタンスは別の Hono インスタンスで wrap される
@hono/vite-build
パッケージで提供されるプラグインでビルドした結果出力されるファイルは、 app/server.ts
で default export した Hono インスタンスを、さらに別の新しい Hono インスタンスで wrap しています。以下がそのコードを生成している部分です。
HonoX で作成したアプリケーションをビルドする場合、最終的なエントリポイントはコードを単純化して整理すると以下のような実装になっています。
import { Hono } from 'hono'
const mainApp = new Hono()
// ここに entryContentBeforeHooks で定義したコードを挿入
const app = await import('/app/server.ts')
mainApp.route('/', app)
mainApp.notFound((c) => {
let executionCtx
try {
executionCtx = c.executionCtx
} catch {}
return app.fetch(c.req.raw, c.env, executionCtx)
})
// ここに entryContentAfterHooks で定義したコードを挿入
export default mainApp
このため、ユーザーが用意する app/server.ts
で default export するのは Hono インスタンスでなければなりません。
また、最終的に default export されるのも Hono インスタンスです。
よって Cloudflare Workers は fetch()
以外の handler を認識できません。
プラグインのオプションで任意のコードを挿入する
プラグインは entryContentBeforeHooks
と entryContentAfterHooks
のオプションを提供しています。このオプションを使うとビルド結果として出力されるファイルに任意のコードを挿入できます。
今回はプラグインが提供する entryContentAfterHooks
オプションを使って scheduled()
handler の対応を実装しました。結果として出力されるファイルは以下のように変更されます。
import { Hono } from 'hono'
const mainApp = new Hono()
const app = await import('/app/server.ts')
mainApp.route('/', app)
mainApp.notFound((c) => {
let executionCtx
try {
executionCtx = c.executionCtx
} catch {}
return app.fetch(c.req.raw, c.env, executionCtx)
})
const handlers = await import('/app/handlers.ts')
mainApp.scheduled = handlers.scheduled
export default mainApp
さいごに
@hono/vite-build/cloudflare-workers
が公式に fetch()
以外の handler をサポートしてくれると嬉しいので issue を立てています。良い実装アイデアがある方はぜひコメントしてください。
Discussion