Deno / Fresh / preact向けのstorybook的なものを構築したい
そもそもあんまりstorybookを運用した経験がない。まずはどのへんが嬉しいのかを調べていく。
Vueで運用する例。PropsをUI上から書き換えたり、自動テストをしたりしている。
操作を行った結果をプレビューできるのは良さそう。
Propsの書き換えは便利に見えるけど、でもいろんなpropsを入力済みにしたケースをたくさん並べたほうが、テストケースっぽくて良さそうに見える。インタラクティビティが本当に必要なのかちょっとわからないな。
StorybookのAlternativeを探す記事。結論としてはStorybookのエコシステムが強い、とのこと。
あまり気乗りしない(大きいツールが好きじゃない)のだが、とりあえずvite + preact + TSのプロジェクトをstorybook化してみよう。
npm create vite
その後、components/MyButton.tsx
を作る。
import type { ComponentChildren } from "preact";
export interface MyButtonProps{
children: ComponentChildren
}
export default function MyButton(props: MyButtonProps){
return <button style={{
backgroundColor: "black",
color: "white"
}}>
{props.children}
</button>
}
というわけでstorybookをインストールする。
npx storybook@latest init
preactとviteが判別され、インストールは成功。
MyButton.stories.tsxを作成する。
import type { Meta, StoryObj } from '@storybook/preact';
import MyButton from './MyButton';
const meta: Meta<typeof MyButton> = {
component: MyButton,
};
export default meta;
type Story = StoryObj<typeof MyButton>;
export const Primary: Story = {
render: () => <MyButton>Hello</MyButton>,
};
無事認識された。いけそうか?
ダメ元でdeno taskで動かしてみる。
hashrock@hashrocknoMac-mini study-preact-vite-storybook % deno task storybook
Task storybook storybook dev -p 6006
@storybook/cli v7.5.3
✔ Port 6006 is not available. Would you like to run Storybook on port 62211 instead? … yes
TypeError: worker.unref is not a function
at startWorkerThreadService (file://./node_modules/.deno/esbuild@0.18.20/node_modules/esbuild/lib/main.js:2306:10)
at transformSync (file://./node_modules/.deno/esbuild@0.18.20/node_modules/esbuild/lib/main.js:2054:29)
at compile2 (file://./node_modules/.deno/esbuild-register@3.5.0/node_modules/esbuild-register/dist/node.js:4882:43)
at Module._compile (file://./node_modules/.deno/esbuild-register@3.5.0/node_modules/esbuild-register/dist/node.js:2254:31)
at Module._extensions..js (node:module:747:10)
at Object.newLoader [as .ts] (file://./node_modules/.deno/esbuild-register@3.5.0/node_modules/esbuild-register/dist/node.js:2262:9)
at Module.load (node:module:658:32)
at Function.Module._load (node:module:539:12)
at Module.require (node:module:677:19)
at require (node:module:791:16)
WARN Broken build, fix the error above.
WARN You may need to refresh the browser.
esbuildのビルドでエラーになる。
(このレベルで依存が多いツールはまあ動かないだろうとは思っていた)
freshプロジェクトでstorybookを使いたいのだが、storybookのビルドはnodeでやらざるを得ないようだ。
さてどうするか。
- このままNode.jsをプロジェクト内に導入することを受け入れて、コンポーネントのみviteの中で作る。
- そんなメリットあったか?
- Histoire / Ladleのようなalternativeがpreactで使えるか試してみる
- 見た感じは望み薄そうな…
- 自分で書く
というかstorybook alternativeはいくらでもみんな作ってそして消えていったものなので、個人で作ったものが本家に太刀打ちできる可能性はゼロなんだよな、この中で「自分で書く」というのはあまり良い選択肢ではないんだろうな…
でも書いてみたい…
結局書くんだからもう書いたらええんじゃ。
ツールをフルスクラッチで書く場合、自分の中で許容できるパターンはただ一つ。「100行で書く」みたいなミニマル実装を作る。絶対に大きくしない。
だからフレームワークべったりで良い。誰か使いたい人がいるなら勝手にフォークしてカスタマイズすればよい。
欲しい機能:
-
*.stories.tsx
を見つけてリストする。 - 特定のストーリーだけをserveする単体ページ。
- リストからコンポーネントを選択すると、単体ページをiframeで表示する。
これだけ。
Storybookは、TSの型からpropsを取得して編集可能にすることができるようだ(どうやってるんだろう?ASTを読んでる?)。まあそれはやりたい人がやるだろう。
割と難しいかもしれない。
まず動的にtsxを見つけて動かすというのができないはず。deno deployは動的importに対応しているが、静的解析が可能なものだけである(パスを動的に生成する場合はeszipにソースを含めてくれない=動かない)。
freshがfresh.gen.tsを生成するのも、動的importを不要にするためである。routesはどこからも繋がっていないのでfresh.gen.tsから繋げてあげる必要があるというわけ。
じゃあstories.tsxのパスを直指定してそれをiframe内にレンダリングするというのはできない。対処としてはソースコードを生成してしまうか、routesかislandsの自動リンクの仕組みを利用してしまうかだ。
…というより、普通に「全部手で書け」というだけの話か。自動でstoriesを見つけるとかはそういうコマンドを用意すればいい。
なんかリサイズを作りたくて作ってしまってる気がする。よくない
なんかComponentをIslandに変換するような仕組みがほしいのではないか?現状だと、islands/MyButtonWrapper.stories.tsxを作る必要がある気がする
まあいいか。今なislandsがネスト可能になったから、インタラクティブなコンポーネントだったら気軽に全部islandsにしちゃえばいいんだよな。
あと結局storiesは全部routesに書け、にした。でも外から見れちゃうのどうなんだろう(middlewareで制限できるとはいえ)。
どうだったらいい、が見えないなぁ…何度か書き直すしかないな。
動き始めた。
今のところislandsにstoriesというフォルダでストーリーを書いていく方式。
なにげにislandsは動的import(ほんとに動的なやつ)も動くと判明してそれを使っている。というのも、islandsはfresh.gen.ts内に自動的にリストアップされるので、基本すべてがeszip内に含まれる。eszipにあればimportしてこれるのだ…(悪用に近いぞ)
本当は MyButton.stories.tsx
みたいな拡張子でコンポーネント本体と区別できるべきだと思う(Ctrl + Pでの検索で両方引っかかるとだるいため)。しかしfreshが .
を含むislandsをimportできないっぽい現象を見つけたため今のところはやっていない(バグっぽい)。
前述の通りprops関連の機能は全カット。自動テストはpuppeteerで書けばいいしアクセシビリティのテストは普通にchromeの機能でやればいいんじゃないでしょうか。darkmodeを確認できる機能はほしい気がするなぁ。あとはドキュメントを書く機能(markdownが書ければそれでよさそう)をつけるかちょっと悩むところ。
グルーピングの機能とかも全然ない。どこまでやったもんだかね。
Darkmode switchを実装しようとしている。
知らなくてハマったんだけど、親要素に dark
classをつけてダークモードの手動切替を行うには、
tailwindのconfigでdarkMode: 'class' を指定する必要があるらしい。
これはプラグインとして実装しようとしている現状では問題で、ユーザに上記設定を強制することになってしまう。設定の有無を取得しにいって、可能な場合のみスイッチを表示するか?なんか微妙だな〜〜