Skip to content
Published on

モノレポ戦略ガイド2025:Nx vs Turborepo vs Lerna — 大規模コードベース管理の完全ガイド

Authors

はじめに:なぜモノレポなのか

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 機能比較表(きのうひかくひょう)

機能(きのう)NxTurborepoLernaRush
タスクパイプラインOOO (v7+)O
ローカルキャッシュOOXO
リモートキャッシュO (Nx Cloud)O (Vercel)XO (セルフホスト)
Affected検出(けんしゅつ)O(プロジェクトグラフ)O(ファイルハッシュ)O (v7+)O
コードジェネレータO (generators)XXX
プロジェクトグラフ可視化(かしか)OXXX
フレームワークプラグインO (React, Angular等(とう))XXX
分散実行(ぶんさんじっこう)O (Nx Agents)XXO
パッケージマネージャnpm, yarn, pnpmnpm, yarn, pnpmnpm, yarn, pnpmpnpm
学習曲線(がくしゅうきょくせん)高(たか)い低(ひく)い低(ひく)い中程度(ちゅうていど)

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の動作(どうさ)原理(げんり):

  1. Git diffで変更(へんこう)されたファイルを検出(けんしゅつ)
  2. プロジェクトグラフで該当(がいとう)ファイルが属(ぞく)するプロジェクトを特定(とくてい)
  3. 依存関係(いぞんかんけい)グラフをたどり影響(えいきょう)を受(う)ける全(すべ)てのプロジェクトを識別(しきべつ)
  4. 該当(がいとう)プロジェクトのみタスクを実行(じっこう)

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分) = 320

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 --from=pruned /app/out/json/ .
COPY --from=pruned /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm install --frozen-lockfile

# ソースをコピーしてビルド
COPY --from=pruned /app/out/full/ .
RUN pnpm turbo run build --filter=@myorg/api

FROM base AS runner
WORKDIR /app
COPY --from=installer /app/apps/api/dist ./dist
COPY --from=installer /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 段階的(だんかいてき)マイグレーション戦略(せんりゃく)

一度(いちど)に全(すべ)てのリポジトリを移動(いどう)しないでください。推奨(すいしょう)順序(じゅんじょ):

  1. 共有(きょうゆう)ライブラリを最初(さいしょ)に移動(いどう)
  2. 最(もっと)も依存(いぞん)されているアプリを移動(いどう)
  3. 残(のこ)りのアプリを順次(じゅんじ)移動(いどう)
  4. 各(かく)ステップで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.jsoninputsenv設定(せってい)でキャッシュキーに含(ふく)む要素(ようそ)を定義(ていぎ)します。


15. 参考(さんこう)資料(しりょう)

  1. Nx Documentation — Nx公式(こうしき)ドキュメント
  2. Turborepo Documentation — Turborepo公式(こうしき)ドキュメント
  3. Changesets Documentation — Changesets公式(こうしき)ドキュメント
  4. pnpm Workspace — pnpmワークスペース公式(こうしき)ドキュメント
  5. Google Monorepo Paper — Why Google Stores Billions of Lines of Code in a Single Repository
  6. Lerna Documentation — Lerna公式(こうしき)ドキュメント
  7. Rush Documentation — Rush公式(こうしき)ドキュメント(Microsoft)
  8. Monorepo Explained — モノレポツール比較(ひかく)サイト
  9. Turborepo Caching — キャッシュメカニズムの詳細(しょうさい)
  10. Nx Affected — Affectedコマンドの動作原理(どうさげんり)
  11. Git Sparse Checkout — 大規模(だいきぼ)リポジトリの最適化(さいてきか)
  12. CODEOWNERS Syntax — GitHub CODEOWNERS構文(こうぶん)