pnpm workspaces (monorepo) 上に Storybook 6.5 を導入する
これはなに
pnpm workspaces で構築した monorepo プロジェクトに Storybook による UI カタログを追加するまでの手順をまとめたものです。
pnpm は依存モジュールの取り扱いが npm や yarn と大きく異なるため、一般的には Storybook の 2023 年 2 月現在の現行バージョンである v6.5
の導入が非推奨とされています。公式ドキュメントにも pnpm によるインストール手順は記載されていません。しかし実際は手順さえ怠らなければ pnpm の設計思想を侵すことなく Storybook を導入できます。本稿ではその手順をご紹介します。
また、本稿の後半では Storybook のビルダーを webpack から Vite に置き換える手順についても言及します。
サンプルコード
※ feat/storybook6-with-webpack
ブランチ
本稿でご紹介する内容に則したプロジェクトのサンプルです[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 のセットアップ(webpack)
node モジュールをインストール
Storybook をビルド・起動するのに最低限必要な node モジュールをインストールします。
cd apps/catalog
pnpm add -D \
react react-dom \
@storybook/react \
@storybook/{builder,manager}-webpack5 \
@storybook/addon-{essentials,interactions,links}
npm, yarn との大きな違いは react
, react-dom
を明示的にインストールする必要がある点です。npm, yarn であれば全ての依存モジュールが node_modules
ディレクトリー配下にフラットに展開されるため、暗黙的にインストールされるこれら 2 つのモジュールは何もせずともよしなに参照してくれます。しかし pnpm はフラットに展開しないため、これら 2 つのモジュールも明示的にインストールせねばなりません。
.storybook/main.js
.storybook/preview.js
上記は Storybook CLI で自動生成されるコードと全く同じです。今回はひとまずこれで OK。
npm scripts を定義
こちらも Storybook CLI で自動生成されるコードと本質的には同じです。
Storybook を起動
packages/catalog
ディレクトリーで以下の npm script を実行します。
pnpm start
正常にビルドされ、 http://localhost:6006
で UI カタログにアクセスできるはずです。
error:0308010C:digital envelope routines::unsupported
というエラーが発生してビルドに失敗する場合の対処法
Node.js のバージョンなど環境によっては error:0308010C:digital envelope routines::unsupported
というエラーが発生してビルドに失敗することがあります。その場合は openssl-legacy-provider
オプションを使用することで回避します。npm scripts を以下のように修正します。
{
"scripts": {
- "start": "start-storybook -p 6006",
- "build": "build-storybook"
+ "start": "NODE_OPTIONS=--openssl-legacy-provider start-storybook -p 6006",
+ "build": "NODE_OPTIONS=--openssl-legacy-provider build-storybook"
}
}
Storybook のビルダーを Vite に置き換える
デフォルトのビルダーである webpack は安定しているものの非常にビルドが遅く、この課題を解消する手段として builder-vite
が公式から提供されています。2023 年 2 月時点の最新バージョンが 0.4.0
であり、所々ピーキーなのは否めませんが、pnpm workspaces にも導入は可能です。
Vite に置き換えたコードのサンプルはこちら。
※ feat/storybook6-with-vite
ブランチ
node モジュールをインストール
Vite に置き換えることで builder-webpack5
, manager-webpack5
は不要となるのであらかじめアンインストールします。
pnpm remove @storybook/{builder,manager}-webpack5
次に @storybook/builder-vite
とこれを動作させるのに最低限必要な node モジュールをインストールします。
pnpm add -D \
@storybook/builder-vite \
@storybook/channel-{postmessage,websocket} \
@storybook/client-api \
@storybook/preview-web \
@storybook/addons \
@storybook/addon-{actions,backgrounds,docs,measure,outline} \
vite \
@vitejs/plugin-react
Vite を使うので vite
, @vitejs/plugin-react
はまだ察しが付きますが、その他に膨大な数の @storybook/*
モジュールをインストールを要求されるところが重要なポイントです。これらは @storybook/builder-vite
が直接依存するモジュールであり、npm
, yarn
であれば node_modules ディレクトリー配下にフラットに展開されるため、明示的なインストールは不要です。しかし pnpm の場合は react
, react-dom
の時と同様にフラットに展開しないことが理由で、これら全てを明示的にインストールする必要があります。流石にこれは面倒が過ぎるものの、@storybook/builder-vite
がそのように実装されている以上どうしようもありません。
.storybook/main.js
を編集
重要なのは features/storyStoreV7
フィールドです。code splitting に対応するために storyStoreV7
フラグを有効化します。これをしないと起動時に Couldn't find any stories in your Storybook.
というエラーになって stories の読み込みに失敗してしまうため、この設定は必須です。
- 参考文献: Webpack | Storybook
.storybook/preview.js
こちらは webpack のときと全く同じ設定のままで大丈夫です。
.storybook/preview-head.html
webpack のときは不要ですが、 Vite でビルドする際はこれが必要となります。
以上で Vite への置き換え作業は完了です。 pnpm start
コマンドを実行して Storybook がビルド・起動すれば成功です。
Chromatic との連携も問題なく可能
詳しい手順は割愛しますが、本稿で紹介してきた monorepo 構成でも上記にある手順で Chromatic との連携ならびに UI Tests の実施は問題なく可能です。
単純なホスティングだけならまだしも、Visual Regression Testing が手間いらずで実現できてしまうのは非常に大きなメリットと言えるでしょう。
締め
当初は v7.0.0-beta
を使っての環境構築を試みたのですが、冒頭で述べた不具合を踏んでどうにもならなかったため、試行錯誤を重ねて今回の設計に至りました。ひとまず pnpm workspaces でも monorepo の UI カタログが構築可能であることは実証できました。
Test runner の導入が可能かどうかは未検証のため、引き続き調査を進めます。
Discussion