🔥

HonoX の Cloudflare Workers 向けビルドで fetch 以外の handler を組み込む

2025/01/26に公開

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 を組み込んだ方法は以下のとおりです。

  1. いずれかのファイルで実装したい handler を定義して export する。
  2. ビルド結果として出力されるファイルが最終的に default export するオブジェクトのプロパティに 1. で export した handler が追加されるように vite.config.ts を書く。

handler を定義して export する

handler を export するファイルは以下のように実装します。ここでは scheduled() handler を 実装しています。

app/handlers.ts
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-workersentryContentAfterHooks オプションを利用します。

vite.config.ts
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
https://hono.dev/docs/getting-started/cloudflare-workers#using-hono-with-other-event-handlers

ドキュメントのサンプルを参考に HonoX のエントリポイントに実装すると、以下のようなコードになるでしょう。

app/server.ts
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 しています。以下がそのコードを生成している部分です。

https://github.com/honojs/vite-plugins/blob/c40d227c9c143d010dc13dc98dfaa6ef9686093c/packages/build/src/entry/index.ts#L51-L79

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 を認識できません。

プラグインのオプションで任意のコードを挿入する

プラグインは entryContentBeforeHooksentryContentAfterHooks のオプションを提供しています。このオプションを使うとビルド結果として出力されるファイルに任意のコードを挿入できます。

今回はプラグインが提供する 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 を立てています。良い実装アイデアがある方はぜひコメントしてください。

https://github.com/honojs/vite-plugins/issues/214

Discussion