モノレポとは
モノレポ(Monorepo)は、複数のプロジェクトやパッケージを単一のリポジトリで管理するアーキテクチャパターンです。Google、Meta、Microsoftなどの大企業で採用されています。
flowchart TB
subgraph Polyrepo["ポリレポ (Polyrepo)"]
RA["repo-a<br/>pkg.json"] --> NPMA["npm"]
RB["repo-b<br/>pkg.json"] --> NPMB["npm"]
RC["repo-c<br/>pkg.json"] --> NPMC["npm"]
RD["repo-d<br/>pkg.json"] --> NPMD["npm"]
end
subgraph Monorepo["モノレポ (Monorepo) - single-repo"]
UI["packages/<br/>ui"]
Utils["packages/<br/>utils"]
Web["apps/<br/>web"]
Shared["共有依存関係"]
UI --> Shared
Utils --> Shared
Web --> Shared
end
モノレポのメリット・デメリット
メリット
flowchart TB
subgraph Benefits["モノレポの利点"]
subgraph Share["1. コード共有の容易さ"]
Pkg["packages/shared"] --> App["apps/web"]
Note1["即座にインポート可能<br/>バージョン管理不要"]
end
subgraph Atomic["2. アトミックなコミット"]
Note2["複数パッケージの変更を1コミットで完結<br/>破壊的変更と対応を同時にリリース"]
end
subgraph Unified["3. 統一されたツールチェーン"]
Note3["ESLint, TypeScript, テスト設定を一元管理"]
end
subgraph Visibility["4. 依存関係の可視化"]
Note4["パッケージ間の依存関係が明確に把握可能"]
end
end
デメリットと対策
| デメリット | 対策 |
|---|---|
| リポジトリサイズの肥大化 | Sparse checkout、シャロークローン |
| CIの実行時間増加 | 差分ビルド、並列実行、リモートキャッシュ |
| 権限管理の複雑化 | CODEOWNERS、ディレクトリ単位の権限設定 |
| コンフリクトの増加 | 適切なパッケージ分割、明確な責任範囲 |
ディレクトリ構成パターン
monorepo/
├── apps/ # アプリケーション
│ ├── web/ # フロントエンド
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── api/ # バックエンド
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── mobile/ # モバイルアプリ
│ └── ...
├── packages/ # 共有パッケージ
│ ├── ui/ # UIコンポーネント
│ │ ├── src/
│ │ │ ├── Button/
│ │ │ ├── Modal/
│ │ │ └── index.ts
│ │ └── package.json
│ ├── utils/ # ユーティリティ関数
│ │ └── ...
│ ├── config/ # 共有設定
│ │ ├── eslint/
│ │ ├── typescript/
│ │ └── tailwind/
│ └── types/ # 共有型定義
│ └── ...
├── tools/ # 開発ツール・スクリプト
│ ├── scripts/
│ └── generators/
├── package.json # ルートpackage.json
├── pnpm-workspace.yaml # ワークスペース設定
├── turbo.json # Turborepo設定
└── tsconfig.base.json # ベースTypeScript設定
pnpm Workspacesの設定
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
- "tools/*"
// ルート package.json
{
"name": "monorepo",
"private": true,
"scripts": {
"dev": "turbo run dev",
"build": "turbo run build",
"test": "turbo run test",
"lint": "turbo run lint",
"clean": "turbo run clean && rm -rf node_modules"
},
"devDependencies": {
"turbo": "^2.0.0",
"typescript": "^5.4.0"
},
"packageManager": "pnpm@9.0.0"
}
内部パッケージの参照
// apps/web/package.json
{
"name": "@monorepo/web",
"dependencies": {
"@monorepo/ui": "workspace:*",
"@monorepo/utils": "workspace:*",
"react": "^19.0.0"
}
}
// apps/web/src/App.tsx
import { Button, Modal } from '@monorepo/ui';
import { formatDate, debounce } from '@monorepo/utils';
export function App() {
return (
<div>
<Button onClick={() => console.log(formatDate(new Date()))}>
クリック
</Button>
</div>
);
}
Turborepoによるビルド最適化
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
},
"lint": {
"dependsOn": ["^build"],
"outputs": []
},
"typecheck": {
"dependsOn": ["^build"],
"outputs": []
}
}
}
タスク依存関係の可視化
flowchart TB
subgraph TaskGraph["Turborepoタスクグラフ - turbo run build"]
Types["@repo/types<br/>build"] --> Utils["@repo/utils<br/>build"]
Utils --> UI["@repo/ui<br/>build"]
Utils --> API["@repo/api<br/>build"]
Utils --> Web["@repo/web<br/>build"]
end
Note1["^build: 依存パッケージを先にビルド"]
Note2["並列実行: 依存関係のないタスクは同時実行"]
リモートキャッシュの設定
# Vercel Remote Cache
npx turbo login
npx turbo link
# セルフホストキャッシュ (ducktape)
# turbo.json
{
"remoteCache": {
"signature": true
}
}
sequenceDiagram
participant DevA as 開発者A<br/>(feature-1)
participant Cache as Remote Cache
participant DevB as 開発者B<br/>(feature-2)
DevA->>Cache: build @repo/ui
Cache->>Cache: hash: abc123<br/>artifacts保存
Note over DevA,DevB: 時間経過
DevB->>Cache: build @repo/ui
Cache-->>DevB: キャッシュヒット!<br/>ビルドスキップ
Note over DevA,DevB: 同一入力のビルドは即座に完了 (数秒)
Nxとの比較
| 観点 | Turborepo | Nx |
|---|---|---|
| 学習コスト | 低い | 中〜高い |
| 設定の複雑さ | シンプル | 機能豊富 |
| ジェネレータ | なし | 充実 |
| プラグイン | 限定的 | 豊富 |
| キャッシュ | Vercel連携 | Nx Cloud |
| 依存関係分析 | 基本的 | 詳細 |
| IDE連携 | 基本的 | VSCode拡張あり |
選択基準:
- シンプルさ重視 → Turborepo
- エンタープライズ機能 → Nx
- Vercel使用 → Turborepo
- Angular使用 → Nx
共有設定パッケージ
// packages/config/eslint/index.js
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
},
};
// packages/config/eslint/react.js
module.exports = {
extends: [
'./index.js',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
],
settings: {
react: { version: 'detect' },
},
};
// packages/config/typescript/base.json
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
"declarationMap": true
}
}
// packages/config/typescript/react.json
{
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["DOM", "DOM.Iterable", "ES2022"]
}
}
CI/CD戦略
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # 差分検出のため
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm turbo run build --filter="...[HEAD^1]"
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
- name: Test
run: pnpm turbo run test --filter="...[HEAD^1]"
- name: Lint
run: pnpm turbo run lint --filter="...[HEAD^1]"
フィルタリング構文
# 変更があったパッケージのみビルド
turbo run build --filter="...[HEAD^1]"
# 特定パッケージとその依存先
turbo run build --filter="@repo/web..."
# 特定パッケージとその依存元
turbo run build --filter="...@repo/ui"
# 特定ディレクトリ配下のみ
turbo run build --filter="./apps/*"
ベストプラクティス
- パッケージの責任を明確に: 1パッケージ1責任の原則
- 循環依存を避ける: 依存グラフは常にDAG(有向非巡回グラフ)に
- バージョニング戦略: Changesets等で統一的なバージョン管理
- ドキュメントの整備: 各パッケージにREADMEを配置
- 適切な粒度: 細かすぎず、大きすぎないパッケージ分割