pnpm workspaces (monorepo) 上に Storybook 7.0 を導入する
これはなに
pnpm workspaces で構築した monorepo プロジェクトで Storybook による UI カタログを構築するまでの手順をまとめたものです。
2023 年 4 月 3 日に Storybook v7 がリリースされました。このメジャーアップデートにより pnpm が正式にサポートされ、monorepo 構成下でも正常に動作するようになりました。
Storybook 6.5 でも pnpm + monorepo 構成にて動作させることは可能でしたが、 v6.5 は TypeScript 5.x を正式にサポートしておらず、ビルドに失敗することがあります。これを解消するには Storybook を v7 にマイグレーションする必要があります。
本稿では、必要な node モジュールをイチから手動でインストールしてセットアップする手順と、既存の Storybook プロジェクトを CLI を使って対話形式でマイグレーションする手順をご紹介します。
サンプルプロジェクト
本稿でご紹介する内容に則したプロジェクトのサンプルです[1]。
.
├── apps/
│ ├── app1/ # Next.js で実装したアプリケーション
│ │ ├── src/
│ │ ├── next.config.js
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── app2/ # React + Vite で実装したアプリケーション
│ │ ├── src/
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── vite.config.ts
│ │ └── tsconfig.json
│ └── catalog/ # storybook で実装した UI カタログ
│ ├── .storybook/
│ │ ├── main.js
│ │ └── preview.js
│ └── package.json
├── packages/
│ ├── core/ # app1, app2 から参照される汎用モジュールパッケージ
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── tsconfig/ # tsconfig の共通設定を管理するパッケージ
│ ├── package.json
│ └── tsconfig.json
├── .node-version
├── .npmrc
├── package.json
├── pnpm-lock.yaml
└── pnpm-workspace.yaml
monorepo 構成
ワークスペースを apps
と packages
の 2 種類を用意します。apps
には web アプリケーションとしてビルドおよびデプロイするもの、packages
にはそれ以外のパッケージを配置します。Storybook による UI カタログも 「当該プロジェクト配下にある全 UI パーツを列挙するアプリケーション」 [2]と見なして単独のサブパッケージとして配置します。
app1
, app2
, core
パッケージの設計について
Storybook のセットアップに入る前に他のサブパッケージの設計について言及しておきます。
まず app1
, app2
はそれぞれ Next.js もしくは React で実装する web アプリです。monorepo 配下に複数の web アプリパッケージが存在するのは「カスタマー向けアプリ」「管理者(バックオフィス)向けアプリ」といった 1 つのサービスにつき複数のアプリケーションが存在するケースを想定しています。
core
パッケージは app1
, app2
からの参照を目的とした汎用的なモジュールのみを管理することを想定したものです。具体的にはボタンやセレクトボックスといった UI コンポーネントやカラーコードといったデザイントークン等です。サンプルコードでは FormLabel
, LabeledSlider
という 2 つのコンポーネントと useDebouncedState
というカスタムフックを管理しています。
core
monorepo らしく name
を @learn-monorepo-pnpm/core
としています。この値と version
を使って app1
, app2
から参照されるようにします。次の節で参照方法を説明します。
app1
app1
は Next.js + TypeScript で実装しています。そして core
パッケージのモジュールを参照するため、 core
パッケージをインストールします。
cd apps/app1
pnpm add @learn-monorepo-pnpm/core@workspace:1.0.0
従来の node モジュールをインストールするのに加えてバージョン指定に workspace:
という接頭辞を付けることで同じ monorepo 内のパッケージをインストールできます。
次に next.config.js
に以下の設定を記述します。
これにより core
パッケージにある FormLabel
というカスタムコンポーネントを以下のような絶対パスで参照できるようになります。
app2
こちらは React + Vite + TypeScript で実装しています。基本的には app1
パッケージと大差ないため割愛します。
Storybook のセットアップ
ビルダーは Vite を第一候補とする
Storybook は webpack もしくは Vite のいずれかでビルドします。デフォルトのビルダーは webpack であり、v6.5 までの Vite ビルダーは β 版扱いで動作もピーキーでしたが、v7 から見違えるほど安定して動作するようになりました。Vite はデフォルトで SCSS のトランスパイルや CSS Modules の読み込みをサポートしているのはもちろん、ビルド速度やバンドルサイズなど多くの点で webpack より優れています。そのため、特別な事情がない限り Vite を選択します。
node モジュールをインストール
Storybook をビルド・起動するのに最低限必要な node モジュールをインストールします。
cd apps/catalog
pnpm add -D \
react \
react-dom \
storybook \
@storybook/react \
@storybook/react-vite \
@storybook/addon-{essentials,interactions,links}
v7 より CLI パッケージである storybook
も必要となります。後述する Storybook の起動やビルド処理を実行するのに使います。また、v7 よりビルドモジュールは @storybook/react-vite
に変わります。v6.5 までは @storybook/builder-vite
に加えてこれがピア依存する大量のモジュールも併せてインストールする必要がありましたが、pnpm 正式サポートの影響なのか react-vite
1 つのみで済むようになりました。
pnpm と npm および yarn との大きな違いは react
, react-dom
を明示的にインストールする必要がある点です。npm, yarn であれば全ての依存モジュールが node_modules
ディレクトリー配下にフラットに展開されるため、暗黙的にインストールされるこれら 2 つのモジュールは何もせずともよしなに参照してくれます。しかし pnpm はフラットに展開しないため、これら 2 つのモジュールも明示的にインストールせねばなりません。
npm scripts を定義
v6.5 までは start-storybook
や build-storybook
といった専用のコマンドがそれぞれ用意されていましたが、v7 より共通の CLI パッケージである storybook
を呼びだす設計となりました。
.storybook/main.js
v6.5 までは framework
, core
フィールドを指定していましたが、 v7 から framework
のみとなりました。
+ framework: '@storybook/react-vite',
- framework: '@storybook/react',
- core: {
- builder: '@storybook/builder-vite',
- },
- features: {
- storyStoreV7: true,
- },
また、code splitting のために指定していた features/storyStoreV7
も v7 よりデフォルトで適用されるようになったため、明示的に有効化する必要がなくなりました。
.storybook/preview.js
上記は Storybook CLI で自動生成されるコードと全く同じです。このファイルはマイグレーションの影響を受けないため、v6.5 と v7 とで内容は全く同じです。
Storybook を起動
packages/catalog
ディレクトリーで以下の npm script を実行します。
pnpm start
正常にビルドされ、 http://localhost:6006
で UI カタログにアクセスできるはずです。
CLI を使って既存プロジェクトをマイグレーションする
依存ライブラリのアップデートや設定ファイルの書き換えを自動で行ってくれます。Storybook のあるディレクトリーに移動してマイグレーションコマンドを実行します。
cd apps/catalog
yarn dlx storybook@latest upgrade
実行すると対話形式でアップデートが進行します。
? Do you want to run the 'storybook-binary' migration on your project?
Y
を選択します。 CLI パッケージである storybook
がインストールされます。Storybook の起動やビルド処理を実行するのに使います。
? Do you want to run the 'sb-scripts' migration on your project?
Y
を選択します。 start-storybook
や build-storybook
といった専用のコマンドが CLI パッケージを呼びだすコマンドに置き換えられます。
Storybook v6 では一定より上のバージョンの Node.js だと OpenSSL の互換エラーが発生してビルドに失敗することがあります。これを回避する手段としてビルドコマンドを実行する際に NODE_OPTIONS=--openssl-legacy-provider
というオプションを指定する必要がありました。v7 にてこの問題が解消されたため、コマンドの置き換え後にこのオプションは手動で撤去します。
{
"scripts": {
- "start": "NODE_OPTIONS=--openssl-legacy-provider start-storybook -p 6006",
- "build": "NODE_OPTIONS=--openssl-legacy-provider build-storybook"
+ "start": "storybook dev -p 6006",
+ "build": "storybook build"
}
}
? Do you want to run the 'new-frameworks' migration on your project?
Y
を選択します。ビルドモジュールが @storybook/builder-vite
から @storybook/react-vite
に置き換えられます。v6.5 までは @storybook/builder-vite
がピア依存する大量のモジュールも併せてインストールする必要がありましたが、v7 からそれら全て不要となったため、置き換え後に手動でアンインストールします。
{
"devDependencies": {
- "@storybook/addon-actions": "7.0.5",
- "@storybook/addon-backgrounds": "7.0.5",
- "@storybook/addon-docs": "7.0.5",
"@storybook/addon-essentials": "7.0.5",
"@storybook/addon-interactions": "7.0.5",
"@storybook/addon-links": "7.0.5",
- "@storybook/addon-measure": "7.0.5",
- "@storybook/addon-outline": "7.0.5",
- "@storybook/addons": "7.0.5",
- "@storybook/builder-vite": "0.4.0",
- "@storybook/channel-postmessage": "7.0.5",
- "@storybook/channel-websocket": "7.0.5",
- "@storybook/client-api": "7.0.5",
- "@storybook/preview-web": "7.0.5",
"@storybook/react": "7.0.5",
+ "@storybook/react-vite": "7.0.5",
}
}
.storybook/main.js
も自動的に書き換えられます。v6.5 までは framework
と core
フィールドを指定していましたが、 v7 から framework
のみとなりました。
- framework: '@storybook/react',
- core: {
- builder: '@storybook/builder-vite',
- },
+ framework: {
+ name: '@storybook/react-vite',
+ options: {},
+ },
また、code splitting のために指定していた features/storyStoreV7
も v7 よりデフォルトで適用されるため、明示的に有効化する必要がなくなりました。こちらは手動で削除します。
- features: {
- storyStoreV7: true,
- },
? Do you want to run the 'github-flavored-markdown-mdx' migration on your project?
MDX を利用しているプロジェクトであれば Y
を選択します。本稿のサンプルプロジェクトでは利用していないため n
を選択しました。
? Do you want to run the 'autodocsTrue' migration on your project?
Y
を選択します。各 Story のドキュメントを生成します。v6.5 までのドキュメントはタブ形式で表示されていましたが、v7 からは独立したページとして生成されるようになりました。
v6.5 | v7 |
---|---|
マイグレーションに必要な作業は以上です。
.storybook/preview-head.html
は必須ではなくなった
v6.5 までは Vite でビルドする際に上記のワークアラウンドが必須でしたが、v7 からは不要となりました。よって .storybook/preview-head.html
は他に理由がない限り不要となります。
参考文献
Discussion