自分のデザインシステムを作りたいの巻
#フロントエンド
趣味開発 pj で共通して使えるデザインシステムがあると、楽だしほんのりセルフブランディングにもなりそうという話
どうせならフレームワーク問わず使えるものが欲しいので、 Web Components として import できるようにするのが有力?
なにで作るか?
https://zenn.dev/nissy_dev/scraps/7ceaf385df142e
https://webcomponents.dev/blog/all-the-ways-to-make-a-web-component/
Preact
途中までこれで作ってた https://zk-phi.github.io/phi-components/ が…
props 周り
カスタム hook で強引に対応できた
Web Components への wrapper
preact-custom-element
なんか Shadow DOM 使わずにマウントすると DOM が二重に生成される謎バグがあった記憶?
children の扱いもなんか微妙だった気がする
とにかく一度試して止めた
preactement
fork して formAssociated とかに自前対応した上で使ってみた
preact-custom-element にあったようなバグもなく、良い感じかに思われたが…
preactement 製 Custom Element をネストすると、内側の component のイベントハンドラが消えるっぽい
→ こういうことができない
code:form.html
<phi-vspace>
<phi-input :value="value" @input="value = $event" />
<phi-input :value="anotherValue" @input="anotherValue = $event" />
</phi-vspace>
このまま Preact でいくなら、 preactement の代替になるような Web Components wrapper を自前実装?
→ その後気づいたこと
wrapper の自前実装で解決する問題ではない(解決するような wrapper はそう簡単に書けない)かも?
少なくとも preactement は「wrapper が子要素をよしなに parse して children として渡す」過程で壊れてるっぽい
子要素を頑張らずに、たんに children の位置に slot 要素を吐けばよかったりする?
→ DOM の階層が変わるんで CSS が良い感じに反映されないとかそういうことがありそう
ダミーの slot を children としてコンポーネントをレンダしたあと slot.replaceWith(...children) するとかかなあ
parse で要素の実体(インスタンス)が変わってしまっているのがおそらく問題
そのタイミングでイベントハンドラとかが消えてしまう
slot から下が途切れているような vdom (部分継続みたいな)が必要?
vdom 方式の弱点が顕在化してしまっている感
Atomico
Web Components ファーストな UI ライブラリなので、 Web Components 関連の API が手厚い
useProp で value prop を簡単にメンテできたり、コンポーネントライブラリを作ることまでちゃんと想定されている
というか Atomico 製の Web Components library が実際ある (formilk)
しかしいくつか微妙なところもありそう
Custom Element としての attributes と Atomico Component としての props が密結合
→ こんな感じで定義すると、 value prop が勝手に Number としてパースされて渡ってくる
code:atomico.jsx
const props = {
value: { type: Number, value: 0 },
};
function myComponent({ value }: Props<typeof props>) {
return <host>{value * 10}</host>;
}
非プリミティブな値を扱うフォーム部品も作れるようにしたさがあるので、勝手にパースしないでほしい気持ち
→ checkbox の value に配列指定できるようにしたいじゃん
自前でパースしようとすると今度は Atomico Component の value prop まで string 型になってしまう
理想的には「 value attribute は string 、 value prop は T[]」としたい
ダークモード対応するときはちょっとクセのある context API もネックになるかも
Preact なら signal を使えば簡単に良い感じになる
@atomico/store がワンチャンか?
Solid
そもそも UI ライブラリとしてそんなにしっくりきていない、かつ、 Web Components ファーストではないので、結局 Preact と同様の苦労 (wrapper まわり) がありそう
Svelte
使ったことないのでわからんけど多分同じような問題はありそう
Nano JSX
非 VDOM 系 React-like JSX ライブラリ、 Solid のライバルっぽさある、まだ国内の情報もあんまないし気にはなる
付属の Web Components wrapper はわりと非力そうなので、自前実装が要りそう (host に触る手段すらなさそう?)
自前実装?
リアクティビティのコアだけ外に投げて、 API 周りとか JSX 周りだけ自前実装の軽いフレームワークが現実的?
ビューライブラリの内部実装は普通に興味あるしワンチャン
バックエンド(リアクティビティ周り)の候補
VDOM 路線なら Superfine (Hyperapp の view 部分) あたり?
Million.js も最新の VDOM library だけど、こいつは compiler 使った最適化まで入ってるっぽくてむずそう
シグナル路線なら S.js あたり?
あるいはバックエンドから自前実装
自前実装だと SSR サポートとか高機能なの作るのはしんどいけど、 Web Components 前提なら SSR そもそもだし、ねえ
生js
生 CustomElement の中でゴリゴリ createElement する
一番自由度は高いけど単純にしんどそう
-----------------------
その後 2024
Atomico いろいろ良くなってて、カスタムで prop 型定義できるようになったりしてた
けど、やはりまだ slot 周りの苦労などはありそうだった
children を text-node にした場合に不要な空白入ったり <phi-button>hoge</phi-button> など
label attr みたいにしてしまう手はあるが…
やはり React に迎合するのが無難感はある…
せめて、ビジュアル面だけ CSS only library に切り出す、みたいな原点回帰路線
その後 2024 末
他の Web Components ライブラリをちょっと調べてみた
Lit
他のライブラリよりかなり薄いラッパーに見える
attribute と props の相互変換がかなり柔軟に書ける
https://lit.dev/docs/components/properties/#conversion-converter
設計上、 JSX など他のシンタックスで記述することはできない模様
https://github.com/lit/lit/issues/677
ただし、テンプレート文字列に対しても型検査が lit-analyze ライブラリでできるらしい
babel とかトランスパイラを前段に噛ませたら、いけそうな気はしないでもない
CSS はワンチャンありそう
https://zenn.dev/odan/scraps/a50593329742d1
Stencil
JSX 使えて書きやすそうだけど、 attribute にカスタムパーサを定義できないっぽく見える
いちおうこういうキモいことをやればできなくはない模様
https://github.com/ionic-team/stencil/issues/1691#issuecomment-562396209
それはそれとして
そもそも Web Components 全体の問題として、Shadow DOM 周りのだるさなどありそう
グローバルな CSS が反映されないとか
まあこれはむしろそういうものというか、メリットでもあるが
CSS 変数を使ってテーマ機構作るとか、リセットCSS利用とかは難しい
select の option を slot に流し込んでも使えないとか
attr でカンマ区切りリストとかJSONをぶちこむ必要がある
あるいは js で prop をセット
そういう点でも、 wc はまだまだ時期尚早というか、なんなら今後本当に流行るのかも怪しい感がある
いろんなフレームワークからそのまま使えるのはめっちゃ魅力的ではあるけどね…
特に趣味開発用途なら、カジュアルにいろんなフレームワークを試したいもの
CSS only だとカラーピッカーとかポップオーバーとかは難しいので、ライブラリごとに実装が必要そう
おまけ
Lit プロジェクトに Web Components -> React のシンプルなラッパーがあった
https://lit.dev/docs/frameworks/react/#why-are-wrappers-needed
React props を Web Components の props に反映してくれるやつ
React の中で直接 Web Components をレンダリングすると、 attributes になってしまう
… ので、文字列しか渡せなくなってる
※ React 19 以降ならそもそも要らんかも?
https://react.dev/blog/2024/12/05/react-19#support-for-custom-elements
tree-shakable なライブラリのビルド
https://zenn.dev/nissy_dev/articles/how-to-make-tree-shakeable-libraries
ライブラリの構成まわり
https://medium.com/@toukirraju/building-a-react-component-library-with-typescript-rollup-and-storybook-9f480d828867
https://medium.com/simform-engineering/building-a-component-library-with-react-typescript-and-storybook-a-comprehensive-guide-ba189accdaf5
個人的ライブラリ選定ポイント
実装がシンプル、子要素をこねくり回したりしない (≒非VDOM系)
attributes と props の相互関係を自前で実装できる
(optional) テキストノードを slot に入れた時の振る舞い
これは atomico 特有の問題「ではない」可能性もありそうなので、その場合は妥協できる
だとしたら、改めて atomico 試してみてもいいかもだけどね
jsx、または外部ファイルからテンプレートを読み込める、または Emacs 側を (html) tagged template のハイライト・インデントに対応させる
ちなみに atomico.js にはあれから更新なし
その後 2025
preact-custom-elements をベースにラッパーを自前実装した。必要な仕様はおそらく揃ってるはず。インターフェースも使いやすくなった