Storybook を Monorepo に導入:Yarn Workspaces/Turborepo の最短レシピ

Monorepo で複数のプロジェクトを管理していると、コンポーネントの可視化や開発効率の向上のために Storybook を導入したくなりますよね。しかし、Yarn Workspaces や Turborepo といった Monorepo ツールと Storybook を組み合わせる際、設定の複雑さや依存関係の解決に悩まされることも少なくありません。
本記事では、Yarn Workspaces と Turborepo を使った Monorepo 環境に Storybook を最短で導入する手順を、初心者の方でもわかりやすく解説します。実際に動作するサンプルコードとともに、つまずきやすいポイントも丁寧にご紹介しますので、ぜひ最後までお読みください。
背景
Monorepo とは
Monorepo(モノレポ)は、複数のプロジェクトやパッケージを 1 つのリポジトリで管理する開発手法です。
従来の Multi-repo では、各プロジェクトが独立したリポジトリを持つため、共通コードの管理やバージョン管理が煩雑になりがちでした。Monorepo を採用することで、コードの再利用性が向上し、依存関係の管理が容易になります。
以下の図は、Monorepo の基本構造を示しています。
mermaidflowchart TB
root["Monorepo ルート"]
root --> packages["packages/"]
packages --> ui["ui パッケージ"]
packages --> utils["utils パッケージ"]
packages --> app["app パッケージ"]
ui --> ui_components["React コンポーネント"]
utils --> util_funcs["共通関数"]
app --> app_code["アプリケーション<br/>コード"]
app -.依存.-> ui
app -.依存.-> utils
この構成により、app
パッケージは ui
や utils
を簡単に参照でき、コードの重複を避けられます。
Yarn Workspaces と Turborepo の役割
Yarn Workspaces は、Yarn が提供する Monorepo 管理機能です。複数のパッケージを 1 つの node_modules
で管理し、依存関係の解決を効率化します。
Turborepo は、Monorepo 向けのビルドシステムで、タスクのキャッシュや並列実行により、ビルド時間を大幅に短縮できます。
以下の図は、両者の役割分担を示しています。
mermaidflowchart LR
yarn["Yarn Workspaces"]
turbo["Turborepo"]
deps["依存関係管理"]
build["ビルド最適化"]
yarn --> deps
turbo --> build
deps --> node["node_modules<br/>共通化"]
build --> cache["キャッシュ/<br/>並列実行"]
Yarn Workspaces がパッケージの依存を一元管理し、Turborepo がビルドプロセスを最適化することで、開発体験が大きく向上します。
Storybook の役割
Storybook は、UI コンポーネントを独立した環境で開発・テストするためのツールです。
コンポーネントカタログとして機能し、デザイナーや他の開発者との共有もスムーズに行えます。Monorepo 環境では、複数のパッケージにまたがるコンポーネントを一元管理できるため、Storybook の導入は特に有効です。
課題
Monorepo 環境での Storybook 導入の難しさ
Monorepo に Storybook を導入する際、以下のような課題に直面することがあります。
- 依存関係の解決: Workspace 間のパッケージ参照が正しく認識されない
- 設定ファイルの配置: Storybook の設定をどのパッケージに置くべきか判断が難しい
- ビルドキャッシュの管理: Turborepo のキャッシュと Storybook のビルドが干渉する
- パス解決の問題: TypeScript や Webpack のパス解決が複雑になる
これらの課題を解決しないと、Storybook が正しく起動しなかったり、コンポーネントが表示されなかったりします。
以下の図は、典型的な問題の発生フローを示しています。
mermaidflowchart TD
start["Storybook 起動"]
start --> check_deps["依存関係チェック"]
check_deps -->|失敗| err1["Error: Module<br/>not found"]
check_deps -->|成功| check_path["パス解決"]
check_path -->|失敗| err2["Error: Cannot<br/>resolve path"]
check_path -->|成功| check_build["ビルド実行"]
check_build -->|失敗| err3["Error: Build<br/>failed"]
check_build -->|成功| success["Storybook<br/>正常起動"]
これらのエラーを回避するために、適切な設定が必要となります。
解決策
基本方針
Monorepo に Storybook を導入する際の基本方針は以下の通りです。
- 専用パッケージとして Storybook を配置:
packages/storybook
など専用のパッケージを作成する - Workspace の依存関係を明示: 他のパッケージを参照する場合は
package.json
に明記する - Turborepo のタスクに統合:
turbo.json
に Storybook のビルドタスクを追加する - パス解決を統一: TypeScript の
tsconfig.json
や Webpack の設定でパスエイリアスを統一する
この方針に従うことで、Monorepo 環境でもスムーズに Storybook を動作させられます。
ディレクトリ構成
以下のようなディレクトリ構成を想定します。
csharpmonorepo-root/
├─ package.json # ルートの package.json
├─ turbo.json # Turborepo の設定
├─ yarn.lock
└─ packages/
├─ ui/ # UI コンポーネントパッケージ
│ ├─ package.json
│ ├─ src/
│ │ └─ Button.tsx
│ └─ tsconfig.json
├─ utils/ # ユーティリティパッケージ
│ ├─ package.json
│ └─ src/
│ └─ format.ts
└─ storybook/ # Storybook 専用パッケージ
├─ package.json
├─ .storybook/
│ ├─ main.ts
│ └─ preview.ts
└─ stories/
└─ Button.stories.tsx
この構成により、Storybook を独立したパッケージとして管理でき、他のパッケージへの影響を最小限に抑えられます。
設計のポイント
以下の図は、Storybook パッケージと他パッケージの関係を示しています。
mermaidflowchart LR
sb["storybook<br/>パッケージ"]
ui["ui<br/>パッケージ"]
utils["utils<br/>パッケージ"]
sb -.参照.-> ui
sb -.参照.-> utils
ui -.参照.-> utils
Storybook パッケージは、UI コンポーネントやユーティリティを参照するだけで、逆方向の依存は発生しません。これにより、依存関係がシンプルになります。
具体例
ステップ 1: Monorepo の初期設定
まず、Yarn Workspaces を有効にした Monorepo を作成します。
ルートディレクトリに package.json
を作成し、以下のように設定してください。
json{
"name": "monorepo-example",
"private": true,
"workspaces": ["packages/*"],
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"storybook": "yarn workspace storybook storybook"
},
"devDependencies": {
"turbo": "^1.10.0"
}
}
ここでは、workspaces
フィールドで packages/*
配下のすべてのディレクトリを Workspace として登録しています。
また、scripts
には Turborepo を使ったビルドコマンドと、後ほど作成する Storybook の起動コマンドを定義しています。
ステップ 2: Turborepo の設定
次に、Turborepo の設定ファイル turbo.json
をルートに作成します。
json{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"dev": {
"cache": false
},
"storybook": {
"cache": false,
"dependsOn": ["^build"]
}
}
}
この設定により、build
タスクは依存パッケージのビルドが完了してから実行されます。
storybook
タスクは、キャッシュを無効化し、依存パッケージのビルド完了後に実行されるように設定しています。これにより、常に最新のコンポーネントが Storybook に反映されます。
ステップ 3: UI パッケージの作成
UI コンポーネントを管理する packages/ui
を作成します。
まず、packages/ui/package.json
を作成してください。
json{
"name": "ui",
"version": "1.0.0",
"main": "./src/index.ts",
"scripts": {
"build": "tsc"
},
"devDependencies": {
"@types/react": "^18.2.0",
"react": "^18.2.0",
"typescript": "^5.0.0"
},
"peerDependencies": {
"react": "^18.2.0"
}
}
このパッケージでは、React コンポーネントを TypeScript で開発し、tsc
でビルドします。
次に、packages/ui/tsconfig.json
を作成します。
json{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"jsx": "react-jsx",
"declaration": true,
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
これにより、TypeScript のコンパイル設定が完了します。
次に、サンプルのボタンコンポーネントを作成しましょう。packages/ui/src/Button.tsx
を作成してください。
typescriptimport React from 'react';
// ボタンコンポーネントのプロパティ型定義
export interface ButtonProps {
label: string; // ボタンに表示するテキスト
onClick?: () => void; // クリック時のコールバック
variant?: 'primary' | 'secondary'; // ボタンのスタイルバリエーション
}
ここでは、ボタンのプロパティを定義しています。variant
プロパティで、ボタンのスタイルを切り替えられるようにしています。
次に、ボタンコンポーネント本体を実装します。
typescript// ボタンコンポーネントの実装
export const Button: React.FC<ButtonProps> = ({
label,
onClick,
variant = 'primary',
}) => {
// スタイルをバリエーションに応じて切り替え
const style: React.CSSProperties = {
padding: '10px 20px',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
backgroundColor:
variant === 'primary' ? '#007bff' : '#6c757d',
color: '#fff',
fontSize: '14px',
};
return (
<button style={style} onClick={onClick}>
{label}
</button>
);
};
シンプルなボタンコンポーネントですが、variant
によってスタイルが変わることがわかります。
最後に、packages/ui/src/index.ts
でエクスポートします。
typescript// ui パッケージのエントリーポイント
export { Button } from './Button';
export type { ButtonProps } from './Button';
これで、UI パッケージの準備が整いました。
ステップ 4: Storybook パッケージの作成
次に、Storybook 専用のパッケージ packages/storybook
を作成します。
まず、packages/storybook/package.json
を作成してください。
json{
"name": "storybook",
"version": "1.0.0",
"private": true,
"scripts": {
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": {
"ui": "workspace:*"
},
"devDependencies": {
"@storybook/react": "^7.6.0",
"@storybook/react-vite": "^7.6.0",
"@storybook/addon-essentials": "^7.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"storybook": "^7.6.0",
"vite": "^5.0.0"
}
}
ここでは、dependencies
に "ui": "workspace:*"
を指定することで、Workspace 内の ui
パッケージを参照しています。
また、Storybook 7 系と Vite を使った構成を採用しています。Vite はビルドが高速で、開発体験が向上します。
次に、Storybook の設定ファイル packages/storybook/.storybook/main.ts
を作成します。
typescriptimport type { StorybookConfig } from '@storybook/react-vite';
// Storybook のメイン設定
const config: StorybookConfig = {
// ストーリーファイルの場所を指定
stories: ['../stories/**/*.stories.@(ts|tsx|js|jsx)'],
// 使用するアドオンを指定
addons: [
'@storybook/addon-essentials', // 基本的なアドオン群
],
// 使用するフレームワークを指定
framework: {
name: '@storybook/react-vite', // React + Vite を使用
options: {},
},
// ドキュメント生成の設定
docs: {
autodocs: 'tag', // @docs タグがあるストーリーは自動でドキュメント生成
},
};
export default config;
この設定により、stories
ディレクトリ配下のストーリーファイルが自動で読み込まれます。
次に、プレビュー設定 packages/storybook/.storybook/preview.ts
を作成します。
typescriptimport type { Preview } from '@storybook/react';
// Storybook プレビュー画面の設定
const preview: Preview = {
parameters: {
// アクション(イベントログ)の設定
actions: { argTypesRegex: '^on[A-Z].*' },
// コントロールパネルの設定
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
};
export default preview;
この設定により、onClick
などのイベントハンドラが自動的にアクションパネルに表示されます。
ステップ 5: ストーリーファイルの作成
次に、ボタンコンポーネント用のストーリーファイル packages/storybook/stories/Button.stories.tsx
を作成します。
まず、インポート部分を記述します。
typescriptimport type { Meta, StoryObj } from '@storybook/react';
import { Button } from 'ui';
// Button コンポーネントのメタデータ設定
const meta: Meta<typeof Button> = {
title: 'Components/Button', // Storybook 上での表示カテゴリ
component: Button, // 対象のコンポーネント
tags: ['autodocs'], // 自動ドキュメント生成を有効化
};
export default meta;
ここでは、ui
パッケージから Button
をインポートし、Storybook のメタデータを設定しています。
次に、具体的なストーリーを定義します。
typescript// Story の型定義
type Story = StoryObj<typeof Button>;
// Primary バリエーションのストーリー
export const Primary: Story = {
args: {
label: 'Primary Button',
variant: 'primary',
},
};
// Secondary バリエーションのストーリー
export const Secondary: Story = {
args: {
label: 'Secondary Button',
variant: 'secondary',
},
};
これにより、Storybook 上で Primary
と Secondary
の 2 つのバリエーションが表示されます。
さらに、クリックイベント付きのストーリーも作成しましょう。
typescript// クリックイベント付きのストーリー
export const WithClick: Story = {
args: {
label: 'Click Me',
variant: 'primary',
onClick: () => alert('Button clicked!'),
},
};
このストーリーでは、ボタンをクリックするとアラートが表示されます。Storybook のアクションパネルでイベントの発火も確認できます。
ステップ 6: Storybook の起動
すべての設定が完了したら、Storybook を起動してみましょう。
ルートディレクトリで以下のコマンドを実行してください。
bashyarn install
これにより、すべての Workspace の依存関係がインストールされます。
次に、UI パッケージをビルドします。
bashyarn workspace ui build
このコマンドで、TypeScript のコンパイルが実行され、packages/ui/dist
にビルド結果が出力されます。
最後に、Storybook を起動します。
bashyarn storybook
ブラウザで http://localhost:6006
にアクセスすると、Storybook が表示されます。
左側のサイドバーに Components/Button
が表示され、Primary
、Secondary
、WithClick
の各ストーリーを切り替えて確認できるはずです。
ステップ 7: Turborepo との統合
Turborepo を使って、ビルドと Storybook の起動を一括管理しましょう。
ルートの package.json
に以下のスクリプトを追加します(すでに追加済みの場合はスキップしてください)。
json{
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"storybook": "yarn workspace storybook storybook"
}
}
これにより、yarn build
で全パッケージのビルドが、yarn storybook
で Storybook の起動が行えます。
また、turbo.json
の設定により、依存関係が自動的に解決されるため、UI パッケージのビルドを忘れる心配もありません。
以下の図は、Turborepo のタスク実行フローを示しています。
mermaidflowchart TD
start["yarn storybook"]
start --> turbo["Turborepo タスク解決"]
turbo --> dep_check["依存関係チェック"]
dep_check --> build_ui["ui パッケージ<br/>ビルド"]
build_ui --> run_sb["Storybook 起動"]
run_sb --> done["http://localhost:6006<br/>で閲覧可能"]
このフローにより、常に最新のコンポーネントが Storybook に反映されます。
よくあるエラーと対処法
Monorepo 環境で Storybook を導入する際、以下のようなエラーが発生することがあります。
Error: Cannot find module 'ui'
エラーコード: Error: Cannot find module 'ui'
発生条件: Workspace の依存関係が正しく解決されていない場合に発生します。
解決方法:
packages/storybook/package.json
のdependencies
に"ui": "workspace:*"
が記載されているか確認する- ルートディレクトリで
yarn install
を再実行する yarn.lock
を削除して、再度yarn install
を実行する
これにより、Workspace 間の参照が正しく解決されます。
Error: Build failed - TypeScript compilation errors
エラーコード: Error: Build failed
エラーメッセージ:
luaerror TS2307: Cannot find module 'ui' or its corresponding type declarations.
発生条件: UI パッケージのビルドが完了していない、または型定義が生成されていない場合に発生します。
解決方法:
yarn workspace ui build
を実行して、UI パッケージをビルドするpackages/ui/tsconfig.json
のdeclaration
がtrue
になっているか確認するpackages/ui/dist
に.d.ts
ファイルが生成されているか確認する
型定義が正しく生成されていれば、エラーは解消されます。
Storybook が起動するが、コンポーネントが表示されない
発生条件: ストーリーファイルのパスが正しく設定されていない場合に発生します。
解決方法:
packages/storybook/.storybook/main.ts
のstories
フィールドを確認する- ストーリーファイルが
packages/storybook/stories
配下に配置されているか確認する - ファイル名が
*.stories.tsx
または*.stories.ts
の形式になっているか確認する
パスとファイル名が正しければ、Storybook が自動的にストーリーを検出します。
さらなる改善ポイント
以下の表は、基本構成からさらに改善できるポイントをまとめたものです。
# | 改善項目 | 内容 | 効果 |
---|---|---|---|
1 | CSS-in-JS の導入 | Emotion や styled-components を導入 | スタイル管理の一元化 |
2 | Addon の追加 | a11y、viewport、interactions などのアドオン | テスト・アクセシビリティ向上 |
3 | テーマの共通化 | デザイントークンを別パッケージで管理 | デザインの一貫性向上 |
4 | ビルド最適化 | Vite のキャッシュ設定を調整 | ビルド時間の短縮 |
5 | CI/CD 統合 | GitHub Actions で Storybook を自動デプロイ | チーム共有の効率化 |
これらの改善により、Storybook の活用範囲がさらに広がります。
まとめ
本記事では、Yarn Workspaces と Turborepo を使った Monorepo 環境に Storybook を導入する手順を、具体的なコード例とともに解説しました。
Monorepo 環境では、依存関係の管理やビルドプロセスの複雑さがネックになりがちですが、適切な設定を行うことで、Storybook をスムーズに統合できます。特に、Workspace の依存関係を明示し、Turborepo のタスク管理を活用することで、開発体験を大きく向上させられるでしょう。
また、エラー発生時の対処法も併せてご紹介しましたので、つまずいた際にはぜひ参考にしてください。Storybook を活用することで、コンポーネントの可視化やチーム間の共有がスムーズになり、開発効率が飛躍的に向上するはずです。
今回ご紹介した構成をベースに、ぜひ皆さんのプロジェクトに合わせてカスタマイズしてみてくださいね。
関連リンク
- article
Storybook を Monorepo に導入:Yarn Workspaces/Turborepo の最短レシピ
- article
Storybook Builder 徹底比較:Vite vs Webpack vs Rspack の速度と互換性
- article
Storybook が真っ白!起動しない/ビルド失敗の原因と 15 の対処チェック
- article
Storybook アーキテクチャ完全図解:Preview/Manager/Builder が噛み合う瞬間
- article
Storybook で学ぶコンポーネントテスト戦略
- article
Storybook × CI/CD:自動化時代の UI 開発
- article
GitHub Copilot を macOS で最短導入:VS Code・Neovim・JetBrains の横断設定
- article
Vue.js を macOS + yarn で最短セットアップ:ESLint/Prettier/TS/パスエイリアス
- article
Tailwind CSS を macOS で最短導入:Yarn PnP・PostCSS・ESLint 連携レシピ
- article
GitHub Actions を macOS ランナーで使いこなす:Xcode/コード署名/キーチェーン設定
- article
Svelte を macOS + yarn + TypeScript で最短構築:ESLint/Prettier まで一気通貫
- article
Git の部分取得を徹底比較:sparse-checkout/partial clone/shallow の違いと使い分け
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来