fresh (deno) で shadcn/ui を使う
モチベーション
Radix UI と Tailwind CSS を用いたコンポーネントライブラリ shadcn/ui を fresh で使う。
この分野でよくあるように shadcn/ui (の基礎の Radix UI) は React への依存が強く、preact ベースである fresh で使うことはできない。
が、「手作業で色々書き直したら使えないことはない」的な issue コメントを見つけたので、自分でも試してみる。
デモのページ
わりと Radix-ui / Tailwind 以外の外部パッケージの使用も多く、それはどうなのと思う。
What do you mean by not a component library?
I mean you do not install it as a dependency. It is not available or distributed via npm.
Pick the components you need. Copy and paste the code into your project and customize to your needs. The code is yours.
結局 shadcn-ui の dependency を install する必要があるなら、誇大広告じゃないですかね
準備
- 上述の issue に従い、unocss が提供する preset 経由で shadcn/ui の tsx を入手する。
↓ の src 以下のファイルを いい感じに fresh プロジェクトに移す。
https://github.com/fisand/unocss-preset-shadcn
- import map に関連ライブラリを追加する。
自分の場合は、deno.json に以下のように追記した。deno.json{ "imports": { "$fresh/": "https://deno.land/x/fresh@1.4.3/", "preact": "https://esm.sh/preact@10.15.1", "preact/": "https://esm.sh/preact@10.15.1/", "preact-render-to-string": "https://esm.sh/*preact-render-to-string@6.2.1", "@preact/signals": "https://esm.sh/*@preact/signals@1.1.3", "@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.2.3", "twind": "https://esm.sh/twind@0.16.19", "twind/": "https://esm.sh/twind@0.16.19/", "$std/": "https://deno.land/std@0.193.0/", + "class-variance-authority": "https://esm.sh/class-variance-authority@0.7.0", + "clsx": "https://esm.sh/clsx@2.0.0", + "tailwind-merge": "https://esm.sh/tailwind-merge@1.14.0" } }
個別の tsx ファイルにおける共通処理
- React のインポートを preact/compat に差し替えるpiyo.tsx
- import * as React from "react" + import * as React from "preact/compat"
-
interface XxxProps
において存在しないXxxHTMLAttributes
をHTMLAttributes
に変更し、それによって発生するプロパティの不一致を型操作によって解消する。
例えば button.tsx の場合、size
プロパティに Class Variance Authority が介入するが、これは preact/compat のHTMLAttributes
のsize
と型が一致しない。なので、HTMLAttributes
からsize
を取り除いてバッティングしないようにする[1]。button.tsx の場合export interface ButtonProps - extends React.ButtonHTMLAttributes<HTMLButtonElement>, + extends Omit<React.HTMLAttributes<HTMLButtonElement>, "size">, VariantProps<typeof buttonVariants> {}
-
class=...
で twind-css の入力ができるように引数を調整するbutton.tsx の場合const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( - ({ className, variant, size, ...props }, ref) => { + ({ class:className, variant, size, ...props }, ref) => { return ( <button className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} /> ) } )
-
力技すぎるので問題があるかも ↩︎
基本的には、これでコンポーネントの描画までは進めるはず。
-
radix-ui がからむとダメかも
radix-ui がからむと、 React と preact/compat の型の不十分な互換性から any が出る。
が、これは定義し直した型をローカルにおいてそこからインポートすれば回避できた
-
@radix-ui/xxx
がインポートする@radix-ui/yyy
が不適切なバージョンの preact をインポートするので render-to-string が失敗する
import_map で@radix-ui/xxx
系のインポートを全て external にし、1つずつ preact のバージョンを指定すると回避できる
-
preact/hooks でTypeError: _.__ is not a function
のエラーが出て動作しない
external=
とdeps=
で preact のインポートが重複していることが原因だった。
もう全部 externak インポートで処理することにした
進捗
unocss が出しているものは、slider と tooltip 以外はちゃんと動くようになった。
import map の肥大化が凄まじい。
「ref を明示的に設定 + useEffect として外側に処理を切り出し」によって、一応は対応できた。
上記の Preact の言い分にあるように 、オリジナルでは、 useComposedRefs() として、 ref の設定処理のなかで StateUpdater を呼ぶという処理が行われている。しかし、preact でこれを行うと state が変更されても re-render が走らないらしく、それを前提としているコンポーネントの追加/削除が行われず、上のような問題が発生する。
React.ReactNode の置き換え
Array<VNode> | VNode | string
が一番エラーが出なさそう
普通にエラー出た。安全なのは Array<VNode | string> | VNode | string
か?
<span> をかませずに Element と文字列を並列させる記述は違和感あるけど...