HonoXでshadcn/uiを使用する
Intro
Honoはreact-rendererというmiddlewareを提供しています。これを使用すればReactが動きます。すごい。
そして最近のUIコンポーネント系で人気なshadcn/uiというものがあります。これはReactに依存しているので先ほどのmiddlewareの出番ということですね。 最近このUIコンポーネントをHonoXで使いたいということがあり、その際に苦戦したポイントなどをまとめておきます。
Repository
https://github.com/yossydev/honox-shadcnというリポジトリを作ってあります。これを参考にしていただければ簡単にしていただければ環境作れると思います。 実際に動作見てみたい方はcloudflare pageでデプロイしてあるので、https://honox-shadcn.pages.dev/でみれます。
苦戦したところ
HonoXのREADMEにreactRendererの使い方があるんですが、そこに書いてある内容だけでは動かなかったので、いくつか自分の方で書いておきます。
vite.config.tsにssrオプションを追加
ssr: { external: ["react", "react-dom"] },
reactとreact-domをssrオプションに追加しましょう。基本htmlとcssはssrで事前に生成したものをクライアントに渡しているので、その生成時にreactとreact-domを外部パッケージとして含めてあげる必要があります。
[vite] Error when evaluating SSR module /~~~~/node_modules/.pnpm/[email protected]/node_modules/react/jsx-runtime.js:
|- ReferenceError: module is not defined
設定していないとこのようなエラーになると思います。
HasIslandが動かない
基本HonoXを使う方はScriptコンポーネントを使用しているかと思いますが、その内部にはHasIslandというコンポーネントを使って、scriptを読み込むかどうかのチェックを行なっています。
HasIslandは以下のような実装になっています
import type { FC } from 'hono/jsx'
import { useRequestContext } from 'hono/jsx-renderer'
import { IMPORTING_ISLANDS_ID } from '../../constants.js'
export const HasIslands: FC = ({ children }) => {
const c = useRequestContext()
return <>{c.get(IMPORTING_ISLANDS_ID) ? children : <></>}</>
}
ここで問題なのが、このコンポーネントがhono/jsx-renderer
を使用しているところです。
reactRendererを使用する場合は@hono/react-renderer
のuseRequestContext
を使う必要があるので、HasIslandコンポーネントは動かずせっかくのislandアーキテクチャが意味をなさなくなってしまいます。
import { reactRenderer } from "@hono/react-renderer";
import { useRequestContext } from "@hono/react-renderer";
import { FC, PropsWithChildren } from "react";
const HasIslands: FC<PropsWithChildren> = ({ children }) => {
const IMPORTING_ISLANDS_ID = "__importing_islands" as const;
const c = useRequestContext();
return <>{c.get(IMPORTING_ISLANDS_ID) ? children : <></>}</>;
};
回避策として、自分でコンポーネントを作ればislandコンポーネントが機能するようになります。
__importing_islands
という定数は、ファイル内にislandディレクトリからのimportがあれば付く識別子です。それを使ってc.get(IMPORTING_ISLANDS_ID)
とgetすることでislandコンポーネントがimportされていればtrueが返ってくるみたいな仕組みになっています。
ここら辺は以前コントリビュートした際の知見が活きています💪 feat: preserved file island support #114
この件に関してはCreating HasIslands component with useRequestContext in react-renderer #127でissueを出しました。
client.tsで型エラーになる
READMEによると、以下のようにclient.ts
をするように書いてあります
import { createClient } from "honox/client";
createClient({
hydrate: async (elem, root) => {
const { hydrateRoot } = await import("react-dom/client");
hydrateRoot(root, elem);
},
createElement: async (type: any, props: any) => {
const { createElement } = await import("react");
return createElement(type, props);
},
});
しかしこれだとTSのコンパイルエラーになります
app/client.ts:6:23 - error TS2345: Argument of type 'Node' is not assignable to parameter of type 'ReactNode'.
6 hydrateRoot(root, elem);
~~~~
app/client.ts:8:3 - error TS2322: Type '(type: any, props: any) => Promise<React.CElement<any, React.Component<any, any, any>>>' is not assignable to type 'CreateElement'.
Type 'Promise<CElement<any, Component<any, any, any>>>' is not assignable to type 'Node | Promise<Node>'.
Type 'Promise<CElement<any, Component<any, any, any>>>' is not assignable to type 'Promise<Node>'.
Type 'ComponentElement<any, Component<any, any, any>>' is missing the following properties from type 'Node': baseURI, childNodes, firstChild, isConnected, and 46 more.
8 createElement: async (type: any, props: any) => {
~~~~~~~~~~~~~
この問題もすでにissueがあり既知の問題みたいなので、そのうち直されると思います(Type Error Occurs When Trying to Use React with createClient #87)
SSGするとエラーになる
TypeError: __vite_ssr_import_0__.jsxDEV is not a function
↑ のようにSSGを使うとエラーになります。これがあんまりわかってなくて、一旦SSGしないようにしているんですが、またどこかで調べてみたいです。
まとめ
いくつか苦戦ポイント上げましたが、これ以外は特になくて、基本環境構築もshadcn/ui通りに進めればできるのでとっても良いです。
関連
0