- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに:なぜモノレポなのか
- 1. モノレポ vs ポリレポ
- 2. 大規模(だいきぼ)企業(きぎょう)のモノレポ戦略(せんりゃく)
- 3. ツール比較(ひかく): Nx vs Turborepo vs Lerna vs Rush
- 4. pnpmワークスペース設定(せってい)
- 5. Nx詳細分析(しょうさいぶんせき)
- 6. Turborepo詳細分析(しょうさいぶんせき)
- 7. 共有(きょうゆう)ライブラリ戦略(せんりゃく)
- 8. バージョン管理(かんり)とChangesets
- 9. CI/CD最適化(さいてきか)
- 10. CODEOWNERSとチーム境界(きょうかい)
- 11. ポリレポからモノレポへのマイグレーション
- 12. 一般的(いっぱんてき)な落(お)とし穴(あな)と解決策(かいけつさく)
- 13. 面接(めんせつ)質問集(しつもんしゅう)(10問(もん))
- Q1. モノレポとポリレポの違(ちが)いと長所短所(ちょうしょたんしょ)を説明(せつめい)せよ
- Q2. NxとTurborepoの核心的(かくしんてき)な違(ちが)いは?
- Q3. Affectedコマンドの動作原理(どうさげんり)を説明(せつめい)せよ
- Q4. リモートキャッシュがCIパフォーマンスを改善(かいぜん)する原理(げんり)は?
- Q5. pnpmワークスペースのworkspace:*プロトコルとは?
- Q6. Changesetsのワークフローを説明(せつめい)せよ
- Q7. モノレポでCODEOWNERSが重要(じゅうよう)な理由(りゆう)は?
- Q8. モノレポでDockerビルドを最適化(さいてきか)する方法(ほうほう)は?
- Q9. ポリレポからモノレポへのマイグレーション時(じ)の注意点(ちゅういてん)は?
- Q10. モノレポでのNxモジュール境界(きょうかい)の役割(やくわり)は?
- 14. 実践(じっせん)クイズ(5問(もん))
- 15. 参考(さんこう)資料(しりょう)
はじめに:なぜモノレポなのか
Googleは20億行(おくぎょう)のコードを1つのリポジトリで管理(かんり)しています。Meta、Microsoft、Uber、Airbnbもモノレポを使用(しよう)しています。モノレポはもはや実験的(じっけんてき)な戦略(せんりゃく)ではなく、大規模(だいきぼ)ソフトウェア開発(かいはつ)の実証済(じっしょうず)みのパターンです。
しかしモノレポの運用(うんよう)を誤(あやま)ると、CIが30分以上(いじょう)かかり、依存性(いぞんせい)地獄(じごく)に陥(おちい)り、コード所有権(しょゆうけん)が不明確(ふめいかく)になります。この記事(きじ)ではモノレポの正(ただ)しい導入戦略(どうにゅうせんりゃく)からNx、Turborepo、Lernaの詳細比較(しょうさいひかく)、CI/CD最適化(さいてきか)、チーム運営戦略(うんえいせんりゃく)まで全(すべ)てをカバーします。
1. モノレポ vs ポリレポ
1.1 定義(ていぎ)
- モノレポ(Monorepo): 複数(ふくすう)のプロジェクト/パッケージを単一(たんいつ)リポジトリで管理(かんり)
- ポリレポ(Polyrepo): 各(かく)プロジェクトを個別(こべつ)のリポジトリで管理(かんり)(=マルチレポ)
1.2 比較表(ひかくひょう)
| 項目(こうもく) | モノレポ | ポリレポ |
|---|---|---|
| コード共有(きょうゆう) | 即時(そくじ)(同(おな)じリポジトリ) | npm/レジストリ経由(けいゆ) |
| 依存性管理(いぞんせいかんり) | 統合管理(とうごうかんり) | 各(かく)リポジトリ独立(どくりつ) |
| アトミックな変更(へんこう) | 可能(かのう)(1つのPR) | 不可能(ふかのう)(複数(ふくすう)のPR必要(ひつよう)) |
| CIの複雑(ふくざつ)さ | 高(たか)い(最適化(さいてきか)必要(ひつよう)) | 低(ひく)い(独立(どくりつ)リポジトリ) |
| コードの可視性(かしせい) | 全(すべ)てのコードを検索可能(けんさくかのう) | リポジトリごとに別々(べつべつ)に検索(けんさく) |
| リリース管理(かんり) | 複雑(ふくざつ)(Changesets等(とう)) | シンプル(個別(こべつ)バージョン) |
| 権限管理(けんげんかんり) | CODEOWNERSが必要(ひつよう) | リポジトリ単位(たんい)で分離(ぶんり) |
| 初期(しょき)クローン | 遅(おそ)くなり得(う)る | 高速(こうそく) |
| リファクタリング | コードベース全体(ぜんたい)で可能(かのう) | リポジトリ境界(きょうかい)で困難(こんなん) |
1.3 モノレポを選(えら)ぶべき場合(ばあい)
モノレポが適切(てきせつ)な場合(ばあい):
- パッケージ間(かん)の依存性(いぞんせい)が密接(みっせつ)
- 共有(きょうゆう)ライブラリが多(おお)い
- アトミックな変更(へんこう)が必要(ひつよう)
- コード再利用(さいりよう)を最大化(さいだいか)したい
- チーム間(かん)のコード可視性(かしせい)が重要(じゅうよう)
ポリレポが適切(てきせつ)な場合(ばあい):
- 完全(かんぜん)に独立(どくりつ)したプロジェクト
- チーム間(かん)で技術(ぎじゅつ)スタックが大(おお)きく異(こと)なる
- 厳格(げんかく)なアクセス制御(せいぎょ)が必要(ひつよう)
- オープンソースとプライベートコードの分離(ぶんり)が必要(ひつよう)
2. 大規模(だいきぼ)企業(きぎょう)のモノレポ戦略(せんりゃく)
2.1 Google(Piper)
- 規模(きぼ): 20億行以上(おくぎょういじょう)、86TB
- ツール: Piper(独自(どくじ)VCS)+ Blaze/Bazel(ビルド)
- キーポイント: 全(すべ)てのコードを1つのリポジトリで、トランクベース開発(かいはつ)
- 教訓(きょうくん): 適切(てきせつ)なツールなしでは不可能(ふかのう)。カスタムVCSとビルドシステムが必須(ひっす)
2.2 Meta(Buck2)
- ツール: Mercurial + Buck2(ビルドシステム)
- キーポイント: 仮想(かそう)ファイルシステムで必要(ひつよう)なファイルのみロード
- 教訓(きょうくん): 大規模(だいきぼ)ではファイルシステムレベルの最適化(さいてきか)が必要(ひつよう)
2.3 Microsoft(1JS)
- ツール: Git + Rush(ビルドオーケストレーション)
- キーポイント: JavaScript/TypeScriptプロジェクトの統合管理(とうごうかんり)
- 教訓(きょうくん): 段階的(だんかいてき)なマイグレーションが現実的(げんじつてき)な戦略(せんりゃく)
2.4 Uber
- ツール: Goモノレポ + Buck(ビルド)
- キーポイント: 5000以上(いじょう)のマイクロサービスを単一(たんいつ)リポジトリで
- 教訓(きょうくん): マイクロサービスとモノレポは共存(きょうぞん)可能(かのう)
3. ツール比較(ひかく): Nx vs Turborepo vs Lerna vs Rush
3.1 機能比較表(きのうひかくひょう)
| 機能(きのう) | Nx | Turborepo | Lerna | Rush |
|---|---|---|---|---|
| タスクパイプライン | O | O | O (v7+) | O |
| ローカルキャッシュ | O | O | X | O |
| リモートキャッシュ | O (Nx Cloud) | O (Vercel) | X | O (セルフホスト) |
| Affected検出(けんしゅつ) | O(プロジェクトグラフ) | O(ファイルハッシュ) | O (v7+) | O |
| コードジェネレータ | O (generators) | X | X | X |
| プロジェクトグラフ可視化(かしか) | O | X | X | X |
| フレームワークプラグイン | O (React, Angular等(とう)) | X | X | X |
| 分散実行(ぶんさんじっこう) | O (Nx Agents) | X | X | O |
| パッケージマネージャ | npm, yarn, pnpm | npm, yarn, pnpm | npm, yarn, pnpm | pnpm |
| 学習曲線(がくしゅうきょくせん) | 高(たか)い | 低(ひく)い | 低(ひく)い | 中程度(ちゅうていど) |
3.2 ツール選択(せんたく)ガイド
プロジェクト開始?
├── 小規模(パッケージ10個未満)
│ ├── 素早く始めたい → Turborepo
│ └── コード生成が必要 → Nx
├── 中規模(パッケージ10-50個)
│ ├── Vercel/Next.jsエコシステム → Turborepo
│ ├── 豊富なプラグインが必要 → Nx
│ └── 既にLernaを使用中 → Lerna v7
└── 大規模(パッケージ50個以上)
├── 分散実行が必要 → Nx
└── Microsoftスタイル → Rush
4. pnpmワークスペース設定(せってい)
4.1 基本構造(きほんこうぞう)
my-monorepo/
├── pnpm-workspace.yaml
├── package.json
├── .npmrc
├── apps/
│ ├── web/
│ │ └── package.json
│ └── api/
│ └── package.json
├── packages/
│ ├── ui/
│ │ └── package.json
│ ├── utils/
│ │ └── package.json
│ └── config/
│ └── package.json
└── tooling/
├── eslint/
│ └── package.json
└── typescript/
└── package.json
4.2 pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
- "tooling/*"
4.3 ルートpackage.json
{
"name": "my-monorepo",
"private": true,
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"test": "turbo run test",
"clean": "turbo run clean"
},
"devDependencies": {
"turbo": "^2.0.0"
},
"packageManager": "pnpm@9.0.0"
}
4.4 パッケージ間参照(かんさんしょう)
{
"name": "@myorg/web",
"dependencies": {
"@myorg/ui": "workspace:*",
"@myorg/utils": "workspace:*"
}
}
workspace:*はローカルパッケージを直接参照(ちょくせつさんしょう)します。npmに公開(こうかい)する際(さい)、pnpmが実際(じっさい)のバージョンに置換(ちかん)します。
4.5 .npmrc設定(せってい)
# ホイスティング設定
shamefully-hoist=false
strict-peer-dependencies=false
# ワークスペース設定
link-workspace-packages=true
prefer-workspace-packages=true
5. Nx詳細分析(しょうさいぶんせき)
5.1 Nx初期設定(しょきせってい)
# 新しいNxワークスペース作成
npx create-nx-workspace@latest my-monorepo --preset=ts
# 既存のモノレポにNxを追加
npx nx@latest init
5.2 nx.json設定(せってい)
{
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"cache": true
},
"test": {
"inputs": ["default", "^production"],
"cache": true
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"cache": true
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/**/*.spec.ts",
"!{projectRoot}/tsconfig.spec.json"
],
"sharedGlobals": ["{workspaceRoot}/tsconfig.base.json"]
}
}
5.3 プロジェクトグラフ
# プロジェクト依存関係グラフを可視化
npx nx graph
# 特定プロジェクトにフォーカス
npx nx graph --focus=my-app
# 影響を受けるプロジェクトを確認
npx nx affected:graph
プロジェクトグラフの例:
web-app ───▶ ui-lib ───▶ utils
│ │
▼ ▼
api-app ───▶ shared-types
5.4 Affectedコマンド(変更検出(へんこうけんしゅつ))
# 変更に影響を受けるプロジェクトのみビルド
npx nx affected -t build
# 影響を受けるプロジェクトのみテスト
npx nx affected -t test
# ベースブランチを指定
npx nx affected -t build --base=main --head=HEAD
Affectedの動作(どうさ)原理(げんり):
- Git diffで変更(へんこう)されたファイルを検出(けんしゅつ)
- プロジェクトグラフで該当(がいとう)ファイルが属(ぞく)するプロジェクトを特定(とくてい)
- 依存関係(いぞんかんけい)グラフをたどり影響(えいきょう)を受(う)ける全(すべ)てのプロジェクトを識別(しきべつ)
- 該当(がいとう)プロジェクトのみタスクを実行(じっこう)
5.5 Generators(コードジェネレータ)
# Reactコンポーネント生成
npx nx generate @nx/react:component Button --project=ui
# ライブラリ生成
npx nx generate @nx/js:library shared-utils
# カスタムGenerator作成
npx nx generate @nx/plugin:generator my-generator --project=tools
5.6 Nx Cloud(リモートキャッシュ + 分散実行(ぶんさんじっこう))
# Nx Cloudに接続
npx nx connect
# NxでのCI設定
name: CI
on: [push]
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
- run: pnpm install --frozen-lockfile
- uses: nrwl/nx-set-shas@v4
- run: npx nx affected -t lint test build
6. Turborepo詳細分析(しょうさいぶんせき)
6.1 Turborepo初期設定(しょきせってい)
# 新しいTurborepoプロジェクト作成
npx create-turbo@latest
# 既存のモノレポにTurborepoを追加
pnpm add -D turbo -w
6.2 turbo.json設定(せってい)
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"globalEnv": ["NODE_ENV"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"],
"env": ["DATABASE_URL"]
},
"test": {
"dependsOn": ["build"],
"inputs": ["$TURBO_DEFAULT$"],
"outputs": ["coverage/**"]
},
"lint": {
"dependsOn": ["^build"],
"cache": true
},
"dev": {
"cache": false,
"persistent": true
},
"clean": {
"cache": false
}
}
}
6.3 タスクパイプラインの動作原理(どうさげんり)
turbo run build実行時:
1. 依存関係グラフ分析
utils(依存なし)→ ui(utilsに依存)→ web(ui、utilsに依存)
2. 並列実行
[utils: build] ──完了──▶ [ui: build] ──完了──▶ [web: build]
[api: build] ──────────┘
(utilsにのみ依存)
3. キャッシュ
- 入力ファイルのハッシュを計算
- 前回のビルドとハッシュが同じならキャッシュから復元
- ビルド時間: 5分 → 0.1秒(キャッシュヒット)
6.4 キャッシュメカニズム
# ローカルキャッシュの場所
ls node_modules/.cache/turbo/
# キャッシュ状態確認
turbo run build --dry-run
# キャッシュ無効化
turbo run build --force
# リモートキャッシュ有効化
npx turbo login
npx turbo link
6.5 フィルタリングとスコーピング
# 特定パッケージのみビルド
turbo run build --filter=@myorg/web
# 変更されたパッケージのみビルド
turbo run build --filter=...[HEAD^1]
# 特定パッケージとその依存関係のみビルド
turbo run build --filter=@myorg/web...
# 特定ディレクトリのパッケージ
turbo run build --filter=./apps/*
6.6 リモートキャッシュ設定(せってい)
# Vercel Remote Cache(公式)
npx turbo login
npx turbo link
環境変数(かんきょうへんすう)設定(せってい):
TURBO_TOKEN=your-token
TURBO_TEAM=your-team
TURBO_API=https://your-cache-server.com
7. 共有(きょうゆう)ライブラリ戦略(せんりゃく)
7.1 パッケージ分類(ぶんるい)
packages/
├── ui/ # UIコンポーネント(Button, Modal, Form)
├── utils/ # ユーティリティ(日付、文字列、バリデーション)
├── types/ # 共有TypeScriptタイプ
├── config/ # 共有設定(ESLint, TypeScript, Prettier)
├── hooks/ # 共有React hooks
├── api-client/ # APIクライアント(型安全なfetch)
└── constants/ # 定数(エラーコード、ルートパス)
7.2 Internalパッケージパターン
ビルドなしでTypeScriptソースを直接参照(ちょくせつさんしょう)するパターン:
{
"name": "@myorg/ui",
"private": true,
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts",
"./*": "./src/*.ts"
}
}
利用側(りようがわ)アプリのtsconfig.jsonでパスエイリアス設定(せってい):
{
"compilerOptions": {
"paths": {
"@myorg/ui": ["../../packages/ui/src"],
"@myorg/ui/*": ["../../packages/ui/src/*"]
}
}
}
7.3 ビルド済(ず)みパッケージパターン
npmに公開(こうかい)するパッケージは別途(べっと)ビルドが必要(ひつよう):
{
"name": "@myorg/utils",
"version": "1.0.0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts"
}
}
8. バージョン管理(かんり)とChangesets
8.1 Changesetsの紹介(しょうかい)
Changesetsはモノレポでのバージョン管理(かんり)とチェンジログ生成(せいせい)を自動化(じどうか)するツールです。
# インストール
pnpm add -D @changesets/cli -w
# 初期化
pnpm changeset init
8.2 ワークフロー
# 1. 変更説明を追加
pnpm changeset
# ? どのパッケージが変更されましたか? → @myorg/ui, @myorg/utils
# ? 変更タイプは? → minor(新機能)
# ? 説明は? → Added new Button variant
# 2. .changeset/ディレクトリにファイル生成
cat .changeset/brave-dogs-run.md
# ---
# "@myorg/ui": minor
# "@myorg/utils": patch
# ---
#
# Added new Button variant
# 3. バージョン更新(CIで実行)
pnpm changeset version
# 4. 公開
pnpm changeset publish
8.3 設定(せってい)(.changeset/config.json)
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [["@myorg/ui", "@myorg/hooks"]],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["@myorg/web", "@myorg/api"]
}
- linked: 一緒(いっしょ)にバージョンアップするパッケージグループ
- fixed: 常(つね)に同(おな)じバージョンを維持(いじ)するパッケージグループ
- ignore: 公開対象(こうかいたいしょう)から除外(じょがい)するパッケージ(アプリ等(とう))
8.4 Independent vs Fixed バージョン管理(かんり)
| 戦略(せんりゃく) | 説明(せつめい) | 例(れい) |
|---|---|---|
| Independent | 各(かく)パッケージ独立(どくりつ)バージョン | ui@2.3.0, utils@1.5.0 |
| Fixed | 全(すべ)てのパッケージ同一(どういつ)バージョン | ui@3.0.0, utils@3.0.0 |
| Linked | 関連(かんれん)パッケージ連動(れんどう)バージョン | ui@2.3.0変更(へんこう)でhooks@2.3.0も |
9. CI/CD最適化(さいてきか)
9.1 Affectedビルド戦略(せんりゃく)
変更(へんこう)されたファイルに影響(えいきょう)を受(う)けるプロジェクトのみビルド/テストします。
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v2
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- run: pnpm install --frozen-lockfile
# Turborepo: 変更されたパッケージのみビルド
- run: turbo run build test lint --filter=...[origin/main]
9.2 リモートキャッシュによるCI時間短縮(じかんたんしゅく)
キャッシュなし:
lint(2分) + test(5分) + build(8分) = 15分
リモートキャッシュヒット:
lint(0.1秒) + test(0.2秒) + build(0.3秒) = 0.6秒
実際に変更されたパッケージのみ:
lint(20秒) + test(1分) + build(2分) = 3分20秒
9.3 並列実行(へいれつじっこう)戦略(せんりゃく)
# マトリックス戦略で並列実行
jobs:
detect:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.filter.outputs.packages }}
steps:
- uses: actions/checkout@v4
- id: filter
run: echo "packages=$(turbo run build --filter=...[origin/main] --dry-run=json | jq -c '.packages')" >> $GITHUB_OUTPUT
build:
needs: detect
runs-on: ubuntu-latest
strategy:
matrix:
package: ${{ fromJson(needs.detect.outputs.packages) }}
steps:
- uses: actions/checkout@v4
- run: turbo run build --filter=${{ matrix.package }}
9.4 Dockerビルド最適化(さいてきか)
# モノレポでのDockerビルド
FROM node:20-slim AS base
RUN corepack enable
FROM base AS pruned
WORKDIR /app
COPY . .
# turbo prune: 特定のアプリとその依存関係のみ抽出
RUN npx turbo prune @myorg/api --docker
FROM base AS installer
WORKDIR /app
# 依存関係のみ先にインストール(キャッシュレイヤー)
COPY /app/out/json/ .
COPY /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm install --frozen-lockfile
# ソースをコピーしてビルド
COPY /app/out/full/ .
RUN pnpm turbo run build --filter=@myorg/api
FROM base AS runner
WORKDIR /app
COPY /app/apps/api/dist ./dist
COPY /app/node_modules ./node_modules
CMD ["node", "dist/main.js"]
10. CODEOWNERSとチーム境界(きょうかい)
10.1 CODEOWNERSファイル
# .github/CODEOWNERS
# グローバルデフォルトオーナー
* @org/platform-team
# アプリ別オーナー
/apps/web/ @org/frontend-team
/apps/api/ @org/backend-team
/apps/mobile/ @org/mobile-team
# パッケージ別オーナー
/packages/ui/ @org/design-system-team
/packages/utils/ @org/platform-team
/packages/auth/ @org/security-team
# 設定ファイル
/tooling/ @org/dx-team
/.github/ @org/dx-team
/turbo.json @org/dx-team
10.2 Nxモジュール境界(きょうかい)によるチーム境界(きょうかい)設定(せってい)
// .eslintrc.json
{
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"depConstraints": [
{
"sourceTag": "scope:web",
"onlyDependOnLibsWithTags": ["scope:shared", "scope:web"]
},
{
"sourceTag": "scope:api",
"onlyDependOnLibsWithTags": ["scope:shared", "scope:api"]
},
{
"sourceTag": "type:app",
"onlyDependOnLibsWithTags": ["type:lib", "type:util"]
}
]
}
]
}
}
11. ポリレポからモノレポへのマイグレーション
11.1 段階的(だんかいてき)マイグレーション
ステップ1: 準備(じゅんび)
# 新しいモノレポリポジトリ作成
mkdir my-monorepo && cd my-monorepo
git init
pnpm init
ステップ2: Gitヒストリーを保存(ほぞん)して移動(いどう)
# 既存リポジトリをサブディレクトリに移動(ヒストリー保存)
git remote add -f web-repo https://github.com/org/web-app.git
git merge web-repo/main --allow-unrelated-histories
# git-filter-repoでディレクトリ構造を変更
git filter-repo --to-subdirectory-filter apps/web
ステップ3: ワークスペース設定(せってい)
# pnpmワークスペース設定
cat > pnpm-workspace.yaml << 'EOF'
packages:
- "apps/*"
- "packages/*"
EOF
ステップ4: 共有(きょうゆう)パッケージの抽出(ちゅうしゅつ)
# 重複コードを共有パッケージに抽出
mkdir -p packages/shared-utils/src
# 共通ユーティリティを移動してパッケージを設定
ステップ5: CI/CDの更新(こうしん)
# TurborepoまたはNxを設定
pnpm add -D turbo -w
# turbo.jsonを設定(6.2節参照)
11.2 段階的(だんかいてき)マイグレーション戦略(せんりゃく)
一度(いちど)に全(すべ)てのリポジトリを移動(いどう)しないでください。推奨(すいしょう)順序(じゅんじょ):
- 共有(きょうゆう)ライブラリを最初(さいしょ)に移動(いどう)
- 最(もっと)も依存(いぞん)されているアプリを移動(いどう)
- 残(のこ)りのアプリを順次(じゅんじ)移動(いどう)
- 各(かく)ステップでCI/CDを検証(けんしょう)
12. 一般的(いっぱんてき)な落(お)とし穴(あな)と解決策(かいけつさく)
12.1 遅(おそ)いCI
問題(もんだい): 毎回(まいかい)全(すべ)てのパッケージをビルド/テスト、CIが30分以上(いじょう)
解決(かいけつ):
- Affectedコマンドを使用(しよう)(変更(へんこう)されたもののみビルド)
- リモートキャッシュを有効化(ゆうこうか)
- 並列実行(へいれつじっこう)を設定(せってい)
# Before: 全パッケージビルド(15分)
pnpm -r run build
# After: 変更されたパッケージのみ(2分)
turbo run build --filter=...[origin/main]
12.2 依存性地獄(いぞんせいじごく)
問題(もんだい): パッケージAがlodash@4を使(つか)い、パッケージBがlodash@3を使(つか)う
解決(かいけつ):
- pnpmのstrictモードを使用(しよう)(phantom dependency防止(ぼうし))
- 共有(きょうゆう)依存性(いぞんせい)はルートで管理(かんり)
syncpackでバージョン同期(どうき)
# syncpackで依存性バージョン確認
npx syncpack list-mismatches
npx syncpack fix-mismatches
12.3 コード所有権(しょゆうけん)の不明確(ふめいかく)さ
問題(もんだい): 誰(だれ)がどのコードに責任(せきにん)を持(も)つか不明確(ふめいかく)
解決(かいけつ):
- CODEOWNERSファイルを設定(せってい)(10.1節(せつ)参照(さんしょう))
- Nxモジュール境界(きょうかい)で依存性(いぞんせい)を制限(せいげん)
- チーム別(べつ)パッケージ名前空間(なまえくうかん)
12.4 初期(しょき)クローン時間(じかん)
問題(もんだい): 大規模(だいきぼ)モノレポのgit cloneが10分以上(いじょう)
解決(かいけつ):
# Shallow clone
git clone --depth 1 https://github.com/org/monorepo.git
# Partial clone(blobなし)
git clone --filter=blob:none https://github.com/org/monorepo.git
# Sparse checkout(特定ディレクトリのみ)
git clone --sparse https://github.com/org/monorepo.git
cd monorepo
git sparse-checkout set apps/web packages/ui
12.5 ビルド順序(じゅんじょ)の問題(もんだい)
問題(もんだい): パッケージAがパッケージBに依存(いぞん)しているがBがまだビルドされていない
解決(かいけつ): タスクパイプラインのdependsOnを設定(せってい)
{
"tasks": {
"build": {
"dependsOn": ["^build"]
}
}
}
^buildは現在(げんざい)のパッケージの依存関係(いぞんかんけい)のbuildタスクを先(さき)に実行(じっこう)するという意味(いみ)です。
13. 面接(めんせつ)質問集(しつもんしゅう)(10問(もん))
Q1. モノレポとポリレポの違(ちが)いと長所短所(ちょうしょたんしょ)を説明(せつめい)せよ
模範(もはん)回答(かいとう): モノレポは複数(ふくすう)のプロジェクトを単一(たんいつ)リポジトリで管理(かんり)し、コード共有(きょうゆう)が容易(ようい)でアトミックな変更(へんこう)が可能(かのう)ですが、CIの最適化(さいてきか)が必要(ひつよう)です。ポリレポは各(かく)プロジェクトを独立(どくりつ)リポジトリで管理(かんり)し、独立性(どくりつせい)が高(たか)いですが、コード共有(きょうゆう)が困難(こんなん)でクロスリポジトリの変更(へんこう)が複雑(ふくざつ)です。
Q2. NxとTurborepoの核心的(かくしんてき)な違(ちが)いは?
模範(もはん)回答(かいとう): Nxはプロジェクトグラフの可視化(かしか)、コードジェネレータ、フレームワークプラグイン、分散実行(ぶんさんじっこう)など豊富(ほうふ)な機能(きのう)を提供(ていきょう)しますが、学習曲線(がくしゅうきょくせん)が高(たか)いです。Turborepoはキャッシュとタスクパイプラインに集中(しゅうちゅう)しており、設定(せってい)がシンプルですが、コード生成(せいせい)や可視化機能(かしかきのう)はありません。
Q3. Affectedコマンドの動作原理(どうさげんり)を説明(せつめい)せよ
模範(もはん)回答(かいとう): Git diffで変更(へんこう)されたファイルを検出(けんしゅつ)し、プロジェクトグラフで該当(がいとう)ファイルが属(ぞく)するプロジェクトとそれに依存(いぞん)する全(すべ)てのプロジェクトを特定(とくてい)し、それらのプロジェクトのみビルド/テストします。これによりCI時間(じかん)を90%以上(いじょう)削減(さくげん)できます。
Q4. リモートキャッシュがCIパフォーマンスを改善(かいぜん)する原理(げんり)は?
模範(もはん)回答(かいとう): ビルドの入力(にゅうりょく)(ソースコード、設定(せってい)等(とう))をハッシュし、同一(どういつ)の入力(にゅうりょく)であれば以前(いぜん)のビルド結果(けっか)をクラウドキャッシュから復元(ふくげん)します。複数(ふくすう)の開発者(かいはつしゃ)が同(おな)じコードをビルドしたりCIで繰(く)り返(かえ)しビルドする場合(ばあい)、ビルドをスキップできます。
Q5. pnpmワークスペースのworkspace:*プロトコルとは?
模範(もはん)回答(かいとう): workspace:*はローカルワークスペースのパッケージを直接参照(ちょくせつさんしょう)するプロトコルです。シンボリックリンクで接続(せつぞく)され、ビルドなしでソースの変更(へんこう)が即座(そくざ)に反映(はんえい)されます。npmに公開(こうかい)する際(さい)、pnpmが自動的(じどうてき)に実際(じっさい)のバージョン番号(ばんごう)に置換(ちかん)します。
Q6. Changesetsのワークフローを説明(せつめい)せよ
模範(もはん)回答(かいとう): 開発者(かいはつしゃ)がpnpm changesetで変更(へんこう)を記述(きじゅつ)すると、.changeset/ディレクトリにマークダウンファイルが生成(せいせい)されます。PRマージ後(ご)、CIでchangeset versionでパッケージバージョンを更新(こうしん)し、changeset publishでnpmに公開(こうかい)します。
Q7. モノレポでCODEOWNERSが重要(じゅうよう)な理由(りゆう)は?
模範(もはん)回答(かいとう): モノレポでは複数(ふくすう)チームのコードが1つのリポジトリにあるため、コード所有権(しょゆうけん)が不明確(ふめいかく)になりやすいです。CODEOWNERSファイルでディレクトリ別(べつ)の担当(たんとう)チームを指定(してい)すると、PRに自動的(じどうてき)にレビュアーが割(わ)り当(あ)てられ、コード品質(ひんしつ)と責任(せきにん)の所在(しょざい)を保証(ほしょう)します。
Q8. モノレポでDockerビルドを最適化(さいてきか)する方法(ほうほう)は?
模範(もはん)回答(かいとう): Turborepoのturbo pruneで特定(とくてい)のアプリとその依存関係(いぞんかんけい)のみを抽出(ちゅうしゅつ)し、Dockerでビルドします。マルチステージビルドで依存関係(いぞんかんけい)のインストールとソースビルドを分離(ぶんり)し、Dockerレイヤーキャッシュを活用(かつよう)します。
Q9. ポリレポからモノレポへのマイグレーション時(じ)の注意点(ちゅういてん)は?
模範(もはん)回答(かいとう): Gitヒストリーの保存(ほぞん)が重要(じゅうよう)です。git-filter-repoでサブディレクトリに移動(いどう)する際(さい)にヒストリーを維持(いじ)できます。一度(いちど)に全(すべ)てのリポジトリを移行(いこう)せず、共有(きょうゆう)ライブラリから段階的(だんかいてき)に移動(いどう)し、各(かく)ステップでCI/CDを検証(けんしょう)すべきです。
Q10. モノレポでのNxモジュール境界(きょうかい)の役割(やくわり)は?
模範(もはん)回答(かいとう): ESLintルールでパッケージ間(かん)の依存性(いぞんせい)を制限(せいげん)します。プロジェクトにタグを付与(ふよ)し、特定(とくてい)のタグのプロジェクトのみ参照(さんしょう)できるようルールを設定(せってい)します。例(たと)えば、フロントエンドアプリがバックエンド専用(せんよう)パッケージをインポートすることを防止(ぼうし)します。
14. 実践(じっせん)クイズ(5問(もん))
Q1. turbo.jsonの "dependsOn": ["^build"] の意味(いみ)は?
正解(せいかい): ^プレフィックスは、現在(げんざい)のパッケージの**依存関係(いぞんかんけい)**のbuildタスクを先(さき)に実行(じっこう)するという意味(いみ)です。例(たと)えばパッケージAがパッケージBに依存(いぞん)する場合(ばあい)、Bのビルドが完了(かんりょう)してからAのビルドが開始(かいし)されます。^なしで["build"]と書(か)くと、同(おな)じパッケージ内(ない)の他(ほか)のタスク依存性(いぞんせい)を意味(いみ)します。
Q2. pnpmのshamefully-hoist=falseがモノレポで重要(じゅうよう)な理由(りゆう)は?
正解(せいかい): pnpmはデフォルトでパッケージを分離(ぶんり)されたnode_modules構造(こうぞう)にインストールし、phantom dependency(宣言(せんげん)していない依存性(いぞんせい)の使用(しよう))を防止(ぼうし)します。shamefully-hoist=falseはこの厳格(げんかく)な分離(ぶんり)を維持(いじ)します。ホイスティングを有効(ゆうこう)にすると宣言(せんげん)していないパッケージにアクセスできてしまい、デプロイ時(じ)に問題(もんだい)が発生(はっせい)する可能性(かのうせい)があります。
Q3. Changesetsのlinkedとfixedの違(ちが)いは?
正解(せいかい): linkedはグループ内(ない)の1つのパッケージのバージョンが上(あ)がると、残(のこ)りも同(おな)じレベルで上(あ)がります(例(れい): 1つがminor上(あ)がると他(ほか)もminor)。fixedはグループ内(ない)の全(すべ)てのパッケージが常(つね)に同一(どういつ)のバージョン番号(ばんごう)を維持(いじ)します(例(れい): 全(すべ)て3.0.0から3.1.0へ)。
Q4. git clone --filter=blob:noneがモノレポで有用(ゆうよう)な理由(りゆう)は?
正解(せいかい): Partial cloneでblob(ファイル内容(ないよう))を初期(しょき)にダウンロードせず、必要(ひつよう)な時(とき)にのみ取得(しゅとく)します。大規模(だいきぼ)モノレポで初期(しょき)クローン時間(じかん)を大幅(おおはば)に短縮(たんしゅく)できます。Gitはチェックアウトするファイルのみblobを取得(しゅとく)するため、sparse checkoutと組(く)み合(あ)わせると必要(ひつよう)なファイルのみ最小限(さいしょうげん)にダウンロードします。
Q5. リモートキャッシュでキャッシュキーが決定(けってい)される仕組(しく)みは?
正解(せいかい): ソースファイル、設定(せってい)ファイル、環境変数(かんきょうへんすう)、依存関係(いぞんかんけい)のビルド結果(けっか)などタスクの全(すべ)ての入力(にゅうりょく)をハッシュしてキャッシュキーを生成(せいせい)します。同一(どういつ)の入力(にゅうりょく)であれば同一(どういつ)のハッシュが出力(しゅつりょく)されるため、以前(いぜん)のビルド結果(けっか)をキャッシュから復元(ふくげん)できます。Turborepoはturbo.jsonのinputsとenv設定(せってい)でキャッシュキーに含(ふく)む要素(ようそ)を定義(ていぎ)します。
15. 参考(さんこう)資料(しりょう)
- Nx Documentation — Nx公式(こうしき)ドキュメント
- Turborepo Documentation — Turborepo公式(こうしき)ドキュメント
- Changesets Documentation — Changesets公式(こうしき)ドキュメント
- pnpm Workspace — pnpmワークスペース公式(こうしき)ドキュメント
- Google Monorepo Paper — Why Google Stores Billions of Lines of Code in a Single Repository
- Lerna Documentation — Lerna公式(こうしき)ドキュメント
- Rush Documentation — Rush公式(こうしき)ドキュメント(Microsoft)
- Monorepo Explained — モノレポツール比較(ひかく)サイト
- Turborepo Caching — キャッシュメカニズムの詳細(しょうさい)
- Nx Affected — Affectedコマンドの動作原理(どうさげんり)
- Git Sparse Checkout — 大規模(だいきぼ)リポジトリの最適化(さいてきか)
- CODEOWNERS Syntax — GitHub CODEOWNERS構文(こうぶん)