なつねこメモ

主にプログラミング関連のメモ帳 ♪(✿╹ヮ╹)ノ 書いてあるコードは自己責任でご自由にどうぞ。記事本文の無断転載は禁止です。

HonoX で `app/` ディレクトリ以外でももろもろを動作させたい

素直に app/ でやれば良いのだが、やんごとなき理由で app/ 以外の、例えば src/my-worldest-strong-app/ などで開発したいことがある (かもしれない)。 そういうときにやると良いハック。 PR は送るべきか悩んで放置している。

まずは HonoX にパッチを当てる必要があるので、パッチを当てます。お使いのパッケージマネージャーでできる方法で試しましょう。

$ pnpm patch honox

差分は以下のような形。単純に honopx/vite/client に対して、 entry を渡せるようにしただけ:

diff --git a/dist/vite/client.d.ts b/dist/vite/client.d.ts
index 7d4b2c20a9fc97ad538d89c850d0517df41271a0..54f6a0f4383c78f4dd353ae7c4a28572148098e0 100644
--- a/dist/vite/client.d.ts
+++ b/dist/vite/client.d.ts
@@ -1,6 +1,7 @@
 import { Plugin } from 'vite';
 
 type Options = {
+    entry?: string;
     jsxImportSource?: string;
     assetsDir?: string;
 };
diff --git a/dist/vite/client.js b/dist/vite/client.js
index 2beda22387a7506caafe9e4b961facc1af661b05..13935f18b3b72124d5227ae1c86f15fe113380e1 100644
--- a/dist/vite/client.js
+++ b/dist/vite/client.js
@@ -1,4 +1,5 @@
 const defaultOptions = {
+  entry: "/app/client.ts",
   jsxImportSource: "hono/jsx/dom",
   assetsDir: "static"
 };
@@ -9,7 +10,7 @@ function client(options) {
       return {
         build: {
           rollupOptions: {
-            input: ["/app/client.ts"]
+            input: [options?.entry ?? defaultOptions.entry]
           },
           assetsDir: options?.assetsDir ?? defaultOptions.assetsDir,
           manifest: true

適用したら、次の手順。 まず、 vite.config.ts にて、クライアント/サーバーともにエントリーポイントを書き換える。 このとき、 server 側 (honox/vite) も同様に app にあることを前提としたコードがあるのだが、そこについてはオプションで対応できるので、あわせて指定している (islandComponents)。

import pages from "@hono/vite-cloudflare-pages";
import honox from "honox/vite";
import client from "honox/vite/client";
import { defineConfig } from "vite";
import path from "node:path";

export default defineConfig(({ mode }) => {
  if (mode === "client") {
    return {
      plugins: [client({ entry: "/src/client.ts" })],
    };
  }
  return {
    plugins: [
      honox({
        entry: "/src/server.ts",
        islandComponents: {
          isIsland: (id) => {
            return path
              .resolve(path.join(import.meta.dirname, "src", id))
              .includes("islands");
          },
        },
      }),
      pages(),
    ],
  };
});

次はサーバー側のエンドポイントにて、自動検索されるパスをすべて置き換える。 基本は次のようにすれば動くはず。 routes も変えたい!という場合は良い感じに書き換えよう。

// src/server.ts
mport { showRoutes } from "hono/dev";
import { createApp } from "honox/server";

const app = createApp({
  root: "/src/routes",
  NOT_FOUND: import.meta.glob("/src/routes/**/_404.(ts|tsx)", {
    eager: true,
  }),
  ERROR: import.meta.glob("/src/routes/**/_error.(ts|tsx)", {
    eager: true,
  }),
  RENDERER: import.meta.glob("/src/routes/**/_renderer.tsx", {
    eager: true,
  }),
  MIDDLEWARE: import.meta.glob("/src/routes/**/_middleware.(ts|tsx)", {
    eager: true,
  }),
  ROUTES: import.meta.glob("/src/routes/**/!(_*|*.test|*.spec).(ts|tsx|mdx)", {
    eager: true,
  }),
});

showRoutes(app);

export default app;

同様にしてクライアント側のエントリーポイントも設定する。

// src/client.ts
import { createClient } from "honox/client";

createClient({
  island_root: "/src",
  ISLAND_FILES: {
    ...import.meta.glob("/src/islands/**/[a-zA-Z0-9[-]+.(tsx|ts)"),
    ...import.meta.glob("/src/routes/**/_[a-zA-Z0-9[-]+.island.(tsx|ts)"),
  },
});

最後に、 Renderer においてクライアント側エントリーポイントのパスを指定している場所があるので、書き換える。

--- a/app/routes/_renderer.tsx
+++ b/src/routes/_renderer.tsx
@@ -9,10 +9,10 @@ export default jsxRenderer(({ children, title }) => {
         <meta charset="utf-8" />
         <meta name="viewport" content="width=device-width, initial-scale=1.0" />
         <title>{title}</title>
-        <Script src="/app/client.ts" async />
+        <Script src="/src/client.ts" async />
         <Style />
       </head>
       <body>{children}</body>
     </html>

あとはいつも通り開発環境を起動すると、もろもろが動く。終わり。

サンプル:

github.com