T-CREATOR

Cursor × Monorepo 構築:Yarn Workspaces/Turborepo/tsconfig path のベストプラクティス

Cursor × Monorepo 構築:Yarn Workspaces/Turborepo/tsconfig path のベストプラクティス

複数のアプリケーションやパッケージを一つのリポジトリで管理する Monorepo は、コードの共有や一貫性の維持に優れていますが、設定の複雑さが課題になりがちです。

Cursor を活用すれば、Yarn Workspaces、Turborepo、TypeScript の path 設定といった Monorepo 特有の構成を効率的に構築できます。本記事では、Cursor と Monorepo を組み合わせた開発環境の構築方法から、実務で役立つベストプラクティスまでを詳しく解説いたします。

初めて Monorepo に挑戦される方も、既存プロジェクトの改善を検討されている方も、きっと参考になる内容をお届けします。

背景

Monorepo が求められる理由

近年のフロントエンド開発では、Web アプリ、モバイルアプリ、管理画面など複数のアプリケーションを並行して開発するケースが増えています。

従来の Multi-repo(リポジトリを分割する方式)では、共通ロジックやコンポーネントを npm パッケージ化して別リポジトリで管理する必要がありました。しかし、この方式には以下のような課題があります。

#課題具体例
1変更の影響範囲が見えにくい共通パッケージを更新しても、依存する全アプリの動作確認が漏れやすい
2バージョン管理の煩雑さパッケージごとに異なるバージョンを管理し、依存関係の整合性を保つ手間がかかる
3CI/CD の複雑化リポジトリごとにパイプラインを構築し、テスト・デプロイの統合が困難

これに対して Monorepo では、複数のアプリケーションとパッケージを一つのリポジトリで管理します。コードの共有が容易になり、変更の影響範囲も追跡しやすくなるでしょう。

Monorepo を支える主要ツール

Monorepo を実現するには、複数のツールを組み合わせる必要があります。

以下に、主要なツールの役割を整理します。

mermaidflowchart TB
  pkg_mgr["パッケージ管理<br/>Yarn Workspaces"]
  build_tool["ビルド最適化<br/>Turborepo"]
  type_sys["型解決<br/>tsconfig paths"]

  pkg_mgr -->|依存関係を一元管理| build_tool
  build_tool -->|並列ビルド・キャッシュ| type_sys
  type_sys -->|型安全なパス解決| dev["開発環境"]

  dev -->|Cursor で統合| editor["効率的な開発"]

図で理解できる要点:

  • Yarn Workspaces がパッケージ依存を一元管理
  • Turborepo が並列ビルドとキャッシュで高速化
  • tsconfig paths が型安全なモジュール解決を実現
#ツール役割
1Yarn Workspaces複数パッケージの依存関係を一元管理し、node_modules を共有
2Turborepoタスクの並列実行とキャッシュにより、ビルド時間を大幅に短縮
3tsconfig pathsTypeScript のモジュール解決をカスタマイズし、相対パスの複雑化を防止

これらのツールを正しく組み合わせることで、大規模な Monorepo でも快適な開発体験が得られます。

Cursor が Monorepo 開発にもたらす価値

Monorepo の設定は初回が特に大変です。

Yarn Workspaces の package.json 設定、Turborepo の turbo.json 構成、各パッケージの tsconfig.json チェーンなど、多数のファイルを正確に編集しなければなりません。

Cursor を活用すれば、これらの設定ファイルを AI の支援を受けながら効率的に作成できます。設定ミスの検出、ベストプラクティスの提案、コード補完など、Monorepo 特有の複雑さを解消してくれるでしょう。

さらに、Cursor のコンテキスト理解機能により、パッケージ間の依存関係を考慮した提案が受けられます。これにより、初心者でも安心して Monorepo 構築に取り組めるのです。

課題

Monorepo を構築する際、多くの開発者が直面する課題を整理します。

設定ファイルの複雑さ

Monorepo では、ルート直下と各パッケージに複数の設定ファイルが必要です。

以下の表に、主要な設定ファイルとその配置場所を示します。

#ファイル配置場所役割
1package.jsonルート・各パッケージワークスペース定義・依存関係管理
2turbo.jsonルートタスク定義・キャッシュ設定
3tsconfig.jsonルート・各パッケージTypeScript 設定・path エイリアス
4.cursorrulesルートCursor 用プロジェクトルール

これらの設定ファイルには依存関係があり、一つの変更が他のファイルに影響を及ぼすこともあります。例えば、Yarn Workspaces で新しいパッケージを追加したら、Turborepo の依存グラフや TypeScript の参照設定も更新が必要です。

パッケージ間の依存関係管理

Monorepo では、パッケージ A がパッケージ B に依存するような構造が頻繁に発生します。

この依存関係を正しく設定しないと、ビルドエラーや型解決の失敗につながります。

mermaidflowchart LR
  app_web["apps/web"]
  app_mobile["apps/mobile"]
  pkg_ui["packages/ui"]
  pkg_utils["packages/utils"]

  app_web -->|依存| pkg_ui
  app_web -->|依存| pkg_utils
  app_mobile -->|依存| pkg_ui
  pkg_ui -->|依存| pkg_utils

図で理解できる要点:

  • 複数のアプリが共通パッケージに依存
  • パッケージ間にも依存関係が存在
  • 依存グラフの整合性維持が重要

依存関係が複雑になると、以下の問題が起こりやすくなります。

#問題影響
1循環依存ビルドが無限ループし、完了しない
2バージョン不整合同じパッケージの異なるバージョンが混在し、予期しないエラーが発生
3ビルド順序の誤り依存先が未ビルドの状態で参照され、型エラーとなる

これらの問題を防ぐには、依存関係を可視化し、適切な順序でビルドする仕組みが必要です。

ビルド時間の増大

Monorepo が大規模化すると、ビルド時間が大きな課題になります。

すべてのパッケージを毎回フルビルドすると、開発のイテレーションが遅くなってしまいます。特に CI/CD 環境では、この時間コストが開発速度に直結するでしょう。

Turborepo のようなビルドキャッシュツールを導入しても、設定が不適切だとキャッシュが効かず、期待した高速化が得られないケースもあります。

型解決とエディタの IntelliSense

TypeScript で Monorepo を構築する場合、パッケージ間の型解決が正しく動作しないと、エディタの IntelliSense が機能しません。

相対パスで ..​/​..​/​..​/​packages​/​ui のように記述すると、コードの可読性が下がり、リファクタリングも困難になります。

tsconfig.jsonpaths 設定を使えばエイリアスを定義できますが、ルートとパッケージごとの設定が矛盾していると、型エラーやインポートの失敗が発生します。

これらの課題を解決するには、設定の一貫性とベストプラクティスの理解が不可欠です。

解決策

Cursor、Yarn Workspaces、Turborepo、TypeScript を組み合わせて、これらの課題を解決します。

Yarn Workspaces でパッケージを一元管理

Yarn Workspaces は、複数パッケージの依存関係を一つの node_modules で管理する仕組みです。

ルート直下の package.json にワークスペースを定義することで、すべてのパッケージが同じ依存バージョンを共有できます。

ルート package.json の設定

以下のコードで、Yarn Workspaces を有効化します。

json{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["apps/*", "packages/*"],
  "packageManager": "yarn@4.0.0"
}

設定のポイント:

  • private: true でルートパッケージの公開を防止
  • workspaces で管理対象のディレクトリパターンを指定
  • packageManager で Yarn のバージョンを固定

この設定により、apps フォルダと packages フォルダ配下のすべてのパッケージが Yarn Workspaces の管理対象になります。

パッケージの依存関係記述

各パッケージの package.json で、他のワークスペースへの依存を workspace:* プロトコルで記述します。

json{
  "name": "@my-monorepo/web",
  "version": "1.0.0",
  "dependencies": {
    "@my-monorepo/ui": "workspace:*",
    "@my-monorepo/utils": "workspace:*",
    "next": "^14.0.0",
    "react": "^18.2.0"
  }
}

依存記述のポイント:

  • workspace:* で同じ Monorepo 内のパッケージを参照
  • 外部パッケージは通常のバージョン指定
  • パッケージ名には @スコープ名​/​パッケージ名 の命名を推奨

これにより、ローカルパッケージと外部パッケージが明確に区別され、依存関係の追跡が容易になります。

Turborepo でビルドを高速化

Turborepo は、タスクの並列実行とキャッシュにより、Monorepo のビルド時間を劇的に短縮します。

変更のないパッケージはキャッシュから結果を取得し、再ビルドをスキップするため、大規模プロジェクトでも高速な開発サイクルが維持できるでしょう。

Turborepo のインストール

以下のコマンドで Turborepo を導入します。

bashyarn add turbo --dev -W

コマンドの説明:

  • --dev で開発依存としてインストール
  • -W でワークスペースルートに追加

turbo.json の基本設定

ルート直下に turbo.json を作成し、タスクの依存関係とキャッシュ設定を定義します。

json{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {
      "dependsOn": ["^build"]
    },
    "test": {
      "dependsOn": ["^build"]
    }
  }
}

pipeline 設定の解説:

以下の表で、各タスクの設定内容を整理します。

#タスク設定項目意味
1builddependsOn: ["^build"]依存パッケージのビルドを先に実行
2buildoutputsキャッシュ対象のディレクトリを指定
3devcache: false開発サーバーはキャッシュしない
4devpersistent: true長時間実行プロセスとして扱う
5lint/testdependsOn: ["^build"]ビルド完了後に実行

^build^ 記号は、「依存する他のパッケージのビルドタスク」を意味します。これにより、正しいビルド順序が自動的に保たれます。

Turborepo コマンドの実行

以下のコマンドで、すべてのパッケージのビルドを並列実行できます。

bashyarn turbo run build

初回実行後は、変更のないパッケージがキャッシュから復元されるため、ビルド時間が大幅に短縮されます。

特定のパッケージのみを対象にする場合は、--filter オプションを使用します。

bashyarn turbo run build --filter=@my-monorepo/web

これにより、指定したパッケージとその依存のみがビルドされ、さらに効率的な開発が可能になります。

tsconfig paths でモジュール解決を最適化

TypeScript の paths 設定を活用すれば、長い相対パスを短いエイリアスで記述できます。

これにより、コードの可読性が向上し、ディレクトリ構造の変更にも柔軟に対応できるでしょう。

ルート tsconfig.json の設定

まず、ルート直下に基本となる tsconfig.json を作成します。

json{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "baseUrl": ".",
    "paths": {
      "@my-monorepo/ui": ["./packages/ui/src/index.ts"],
      "@my-monorepo/ui/*": ["./packages/ui/src/*"],
      "@my-monorepo/utils": [
        "./packages/utils/src/index.ts"
      ],
      "@my-monorepo/utils/*": ["./packages/utils/src/*"]
    }
  }
}

paths 設定のポイント:

  • baseUrl をルートに設定
  • パッケージごとにエントリーポイントとワイルドカードパスを定義
  • パッケージ名とパスを明示的にマッピング

この設定により、パッケージ内のファイルを @my-monorepo​/​ui​/​Button のように簡潔にインポートできます。

パッケージごとの tsconfig.json

各パッケージでは、ルートの設定を継承しつつ、個別の設定を上書きします。

json{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "composite": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

パッケージ設定のポイント:

  • extends でルート設定を継承
  • composite: true でプロジェクト参照を有効化
  • includeexclude でコンパイル対象を制御

composite: true は、パッケージ間の型チェックを高速化する重要な設定です。

TypeScript のプロジェクト参照

複数パッケージの型チェックを効率化するため、プロジェクト参照を設定します。

ルートの tsconfig.jsonreferences を追加します。

json{
  "files": [],
  "references": [
    { "path": "./apps/web" },
    { "path": "./apps/mobile" },
    { "path": "./packages/ui" },
    { "path": "./packages/utils" }
  ]
}

これにより、TypeScript が依存関係を理解し、変更箇所のみを再チェックするインクリメンタルビルドが可能になります。

Cursor のカスタムルールで開発効率を向上

Cursor の .cursorrules ファイルを活用すれば、Monorepo 特有の規約やベストプラクティスを AI に学習させられます。

プロジェクトルート直下に .cursorrules を作成し、以下の内容を記述します。

markdown# Monorepo 開発ルール

# パッケージ構造

- apps/: アプリケーション本体(web, mobile など)
- packages/: 共有パッケージ(ui, utils など)

# 依存関係の記述

- Monorepo 内パッケージは `workspace:*` を使用
- パッケージ名は `@my-monorepo/パッケージ名` 形式

# インポートルール

- 相対パスは同一パッケージ内のみ使用
- パッケージ間は `@my-monorepo/パッケージ名` でインポート
- 深い相対パス(../../ 以上)は禁止

# コマンド

- ビルド: `yarn turbo run build`
- 開発: `yarn turbo run dev`
- テスト: `yarn turbo run test`
- 特定パッケージのみ: `--filter=@my-monorepo/パッケージ名`

この設定により、Cursor がコード提案時に Monorepo の規約を考慮してくれます。新しいインポート文を追加する際、自動的にエイリアスパスを提案してくれるでしょう。

具体例

実際に Cursor を使って Monorepo を構築する手順を、段階的に解説します。

プロジェクト初期化

まず、プロジェクトのディレクトリ構造を作成します。

以下のコマンドで、必要なフォルダを一括生成しましょう。

bashmkdir -p my-monorepo/{apps/{web,mobile},packages/{ui,utils}}
cd my-monorepo

ディレクトリ構造の説明:

  • apps​/​web: Next.js などの Web アプリ
  • apps​/​mobile: React Native などのモバイルアプリ
  • packages​/​ui: 共通 UI コンポーネント
  • packages​/​utils: 共通ユーティリティ関数

次に、Yarn の初期化を行います。

bashyarn init -y

これで package.json が生成されます。

ルート設定ファイルの作成

Cursor で package.json を開き、以下の内容に編集します。

json{
  "name": "my-monorepo",
  "version": "1.0.0",
  "private": true,
  "workspaces": ["apps/*", "packages/*"],
  "packageManager": "yarn@4.0.0",
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint",
    "test": "turbo run test"
  },
  "devDependencies": {
    "turbo": "^1.10.0",
    "typescript": "^5.2.0"
  }
}

Cursor のチャット機能を使えば、この設定ファイルの生成を支援してもらえます。「Yarn Workspaces と Turborepo を使った package.json を作成して」と依頼するだけで、適切な設定が提案されるでしょう。

続いて、Turborepo の設定ファイルを作成します。

json{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**", "build/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {
      "dependsOn": ["^build"],
      "outputs": []
    },
    "test": {
      "dependsOn": ["^build"],
      "outputs": []
    }
  }
}

これらの設定ファイルにより、Monorepo の基盤が整います。

共通パッケージの作成

共通 UI コンポーネントパッケージを作成します。

packages​/​ui​/​package.json を以下の内容で作成しましょう。

json{
  "name": "@my-monorepo/ui",
  "version": "1.0.0",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "scripts": {
    "build": "tsc",
    "dev": "tsc --watch"
  },
  "dependencies": {
    "react": "^18.2.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.0",
    "typescript": "^5.2.0"
  }
}

次に、packages​/​ui​/​tsconfig.json を作成します。

json{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "composite": true,
    "jsx": "react-jsx"
  },
  "include": ["src/**/*"]
}

TypeScript の設定により、型安全な開発が可能になります。

サンプルコンポーネントの実装

packages​/​ui​/​src​/​Button.tsx にシンプルなボタンコンポーネントを作成します。

typescriptimport React from 'react';

export interface ButtonProps {
  /** ボタンのラベルテキスト */
  label: string;
  /** クリック時のコールバック */
  onClick?: () => void;
}

以下で、コンポーネントの実装を記述します。

typescript/**
 * 共通ボタンコンポーネント
 * Monorepo 全体で使用される基本ボタン
 */
export const Button: React.FC<ButtonProps> = ({
  label,
  onClick,
}) => {
  return (
    <button
      onClick={onClick}
      style={{
        padding: '10px 20px',
        fontSize: '16px',
        borderRadius: '4px',
        border: '1px solid #ccc',
        cursor: 'pointer',
      }}
    >
      {label}
    </button>
  );
};

エクスポート用のインデックスファイル packages​/​ui​/​src​/​index.ts を作成します。

typescript// すべてのコンポーネントをここからエクスポート
export { Button } from './Button';
export type { ButtonProps } from './Button';

これで、共通パッケージが完成しました。

アプリケーションでの利用

Web アプリケーションで共通パッケージを使用します。

apps​/​web​/​package.json を作成し、依存関係を定義します。

json{
  "name": "@my-monorepo/web",
  "version": "1.0.0",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "@my-monorepo/ui": "workspace:*",
    "next": "^14.0.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "@types/react": "^18.2.0",
    "typescript": "^5.2.0"
  }
}

apps​/​web​/​tsconfig.json を作成します。

json{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "jsx": "preserve",
    "incremental": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

Next.js のページコンポーネントで共通ボタンを使用します。

apps​/​web​/​src​/​pages​/​index.tsx を作成しましょう。

typescriptimport { Button } from '@my-monorepo/ui';

export default function HomePage() {
  const handleClick = () => {
    alert('Monorepo のボタンがクリックされました!');
  };

以下で、ページのレンダリング部分を実装します。

typescript  return (
    <div style={{ padding: '50px' }}>
      <h1>Monorepo デモアプリ</h1>
      <p>共通パッケージのボタンコンポーネントを使用しています。</p>
      <Button label="クリックしてください" onClick={handleClick} />
    </div>
  );
}

この実装により、共通パッケージのコンポーネントが Web アプリで利用できます。

ビルドと動作確認

まず、すべての依存関係をインストールします。

bashyarn install

共通パッケージをビルドします。

bashyarn turbo run build --filter=@my-monorepo/ui

Web アプリケーションの開発サーバーを起動します。

bashyarn turbo run dev --filter=@my-monorepo/web

ブラウザで http:​/​​/​localhost:3000 にアクセスすると、共通ボタンが表示されます。

ボタンをクリックすると、アラートが表示されることを確認できるでしょう。

Cursor での開発体験

Cursor を使えば、パッケージ間の移動やコード補完が非常にスムーズです。

例えば、apps​/​web@my-monorepo​/​ui からコンポーネントをインポートする際、以下のような体験が得られます。

mermaidsequenceDiagram
  participant dev as 開発者
  participant cursor as Cursor
  participant ts as TypeScript

  dev->>cursor: "import { B" と入力
  cursor->>ts: 型定義を検索
  ts-->>cursor: Button の型情報を返却
  cursor-->>dev: "Button" を候補表示
  dev->>cursor: 候補を選択
  cursor-->>dev: インポート文を自動補完

Cursor の支援機能:

  • パッケージ名の自動補完
  • 型定義に基づいた IntelliSense
  • インポート文の自動追加
  • 未使用インポートの警告

Cursor のチャット機能で「このボタンに loading 状態を追加して」と依頼すれば、Props の拡張から実装まで一貫して支援してもらえます。

typescriptexport interface ButtonProps {
  label: string;
  onClick?: () => void;
  /** ローディング中かどうか */
  loading?: boolean;
}

export const Button: React.FC<ButtonProps> = ({
  label,
  onClick,
  loading,
}) => {
  return (
    <button
      onClick={onClick}
      disabled={loading}
      style={{
        padding: '10px 20px',
        fontSize: '16px',
        borderRadius: '4px',
        border: '1px solid #ccc',
        cursor: loading ? 'not-allowed' : 'pointer',
        opacity: loading ? 0.6 : 1,
      }}
    >
      {loading ? 'Loading...' : label}
    </button>
  );
};

このように、Cursor は Monorepo 開発のあらゆる場面で強力なサポートを提供してくれます。

Turborepo のキャッシュ検証

Turborepo のキャッシュが正しく動作しているか確認します。

初回ビルドを実行します。

bashyarn turbo run build

実行結果に以下のようなログが表示されます。

less@my-monorepo/ui:build: cache miss, executing...
@my-monorepo/web:build: cache miss, executing...

続けて、何も変更せずに再度ビルドを実行します。

bashyarn turbo run build

今度は、以下のようにキャッシュがヒットします。

less@my-monorepo/ui:build: cache hit, replaying output...
@my-monorepo/web:build: cache hit, replaying output...

ビルド時間が大幅に短縮され、数秒で完了することが確認できるでしょう。

共通パッケージのコードを変更すると、そのパッケージと依存するアプリのみが再ビルドされます。これにより、効率的な開発サイクルが実現できます。

まとめ

Cursor と Monorepo を組み合わせることで、複数のアプリケーションとパッケージを効率的に管理できます。

本記事で紹介した構成を整理します。

#要素役割主なメリット
1Yarn Workspacesパッケージ管理依存関係の一元化・重複排除
2Turborepoビルド最適化並列実行・キャッシュによる高速化
3tsconfig pathsモジュール解決型安全なエイリアス・可読性向上
4Cursor開発支援AI による設定支援・コード補完

これらのツールを適切に組み合わせれば、初心者でも Monorepo の複雑さを克服できます。

Cursor の AI 支援により、設定ファイルの作成やベストプラクティスの適用が容易になるでしょう。

Monorepo は初期構築に時間がかかるものの、長期的には開発効率を大きく向上させます。コードの共有、一貫性の維持、変更の影響範囲の追跡など、多くのメリットが得られるのです。

ぜひ本記事の手順を参考に、Cursor を活用した Monorepo 構築に挑戦してみてください。きっと、開発体験の向上を実感できるはずです。

関連リンク