Vue.js Monorepo 構築:pnpm/Turborepo でアプリとパッケージを一元管理
Vue.js を使った複数のプロジェクトを管理していると、共通コンポーネントやユーティリティ関数を各プロジェクトで重複して作成してしまうことはありませんか。そんな課題を解決してくれるのが「Monorepo」という手法です。
本記事では、pnpm と Turborepo を組み合わせて、Vue.js の複数アプリケーションと共有パッケージを一元管理する Monorepo 構築方法を、初心者の方にも分かりやすく解説していきます。実際に手を動かしながら学べるよう、具体的なコマンドと設定ファイルを段階的に紹介していきますね。
背景
Monorepo とは何か
Monorepo(モノレポ)は、複数のプロジェクトやパッケージを 1 つのリポジトリで管理する開発手法です。従来の Polyrepo(ポリレポ)では、各プロジェクトごとに個別のリポジトリを作成していましたが、Monorepo ではすべてを 1 つのリポジトリ内で管理します。
この手法は、Google や Facebook といった大規模な組織で採用されており、コードの共有や依存関係の管理が格段に効率化されます。
Vue.js プロジェクトにおける Monorepo の需要
Vue.js でフロントエンド開発を行う際、以下のようなケースで Monorepo が威力を発揮します。
- 複数の Web アプリケーションを運用している
- 共通の UI コンポーネントを複数プロジェクトで使いたい
- ユーティリティ関数や型定義を一元管理したい
- 管理画面とユーザー向けサイトを同時に開発している
これらの状況では、コードの重複を避け、変更を一箇所で管理できる Monorepo が理想的な解決策となります。
下図は、Monorepo と Polyrepo の構造比較を示しています。
mermaidflowchart TB
subgraph mono["Monorepo 構造"]
direction TB
root["ルートリポジトリ"]
root --> app1["apps/web-app"]
root --> app2["apps/admin-app"]
root --> pkg1["packages/ui-components"]
root --> pkg2["packages/utils"]
app1 -.->|依存| pkg1
app1 -.->|依存| pkg2
app2 -.->|依存| pkg1
end
subgraph poly["Polyrepo 構造"]
direction TB
repo1["リポジトリ 1<br/>web-app"]
repo2["リポジトリ 2<br/>admin-app"]
repo3["リポジトリ 3<br/>ui-components"]
repo4["リポジトリ 4<br/>utils"]
repo1 -.->|npm 依存| repo3
repo2 -.->|npm 依存| repo3
end
Monorepo では、1 つのリポジトリ内で複数のアプリとパッケージを管理でき、ローカル開発時に即座に変更が反映されます。一方、Polyrepo では各リポジトリが独立しているため、パッケージの更新ごとに npm への公開が必要になるのです。
pnpm と Turborepo の役割
pnpm は、高速で効率的なパッケージマネージャーです。ディスク容量を節約し、npm や Yarn よりも高速なインストールを実現します。特に Monorepo 環境では、ワークスペース機能を使って複数パッケージを効率的に管理できます。
Turborepo は、Vercel が開発した Monorepo 向けのビルドシステムです。ビルドやテストなどのタスクを並列実行し、キャッシュ機能によって不要なビルドをスキップすることで、開発効率を大幅に向上させます。
| # | ツール | 主な役割 | 特徴 |
|---|---|---|---|
| 1 | pnpm | パッケージ管理 | ディスク容量節約、高速インストール、ワークスペース対応 |
| 2 | Turborepo | タスク実行・ビルド管理 | 並列実行、インクリメンタルビルド、リモートキャッシュ |
この 2 つを組み合わせることで、Vue.js の Monorepo 開発が驚くほど快適になりますよ。
課題
従来の開発における問題点
Vue.js で複数プロジェクトを開発する際、従来の方法では以下のような課題がありました。
コードの重複問題
各プロジェクトで同じようなコンポーネントやユーティリティを何度も作成してしまうことがあります。例えば、ボタンコンポーネントやフォームバリデーション関数などを、プロジェクトごとにコピー&ペーストしていると、バグ修正や機能追加のたびにすべてのプロジェクトを修正する必要が出てきます。
依存関係の管理の複雑さ
共通パッケージを npm に公開して使う方法もありますが、これには以下の課題があります。
- パッケージの更新ごとにバージョン番号の更新が必要
- npm への公開作業が発生
- ローカル開発時に即座に変更が反映されない
- バージョン管理が煩雑になる
ビルド時間の増大
プロジェクトが増えるにつれて、すべてのビルドやテストを実行する時間が増大します。変更していないパッケージまで毎回ビルドするのは非効率ですよね。
下図は、従来の開発フローにおける問題点を示しています。
mermaidflowchart TB
dev["開発者"] -->|修正| pkg["共通パッケージ"]
pkg -->|1. バージョン更新| version["package.json<br/>バージョン番号変更"]
version -->|2. ビルド| build["npm run build"]
build -->|3. 公開| publish["npm publish"]
publish -->|4. インストール| app1["アプリ 1<br/>npm install"]
publish -->|4. インストール| app2["アプリ 2<br/>npm install"]
app1 -->|5. 動作確認| test1["テスト"]
app2 -->|5. 動作確認| test2["テスト"]
style pkg fill:#ffcccc
style version fill:#ffcccc
style publish fill:#ffcccc
このように、共通パッケージを修正するたびに多くのステップを踏む必要があり、開発速度が低下してしまいます。
Monorepo で解決できること
Monorepo を導入することで、これらの課題を以下のように解決できます。
| # | 課題 | Monorepo による解決策 |
|---|---|---|
| 1 | コードの重複 | 共通パッケージを作成し、複数アプリから参照 |
| 2 | 依存関係管理の複雑さ | ワークスペース機能で即座に変更を反映 |
| 3 | ビルド時間の増大 | Turborepo のキャッシュで不要なビルドをスキップ |
| 4 | バージョン管理の煩雑さ | 単一リポジトリで統一的に管理 |
次のセクションでは、これらの課題を解決する具体的な構築方法を見ていきましょう。
解決策
pnpm Workspace による Monorepo の基盤構築
pnpm の Workspace 機能を使うことで、複数のパッケージを 1 つのリポジトリ内で管理できます。各パッケージは独立していながらも、相互に依存関係を持つことが可能です。
pnpm Workspace の仕組み
pnpm Workspace では、pnpm-workspace.yaml ファイルでワークスペース内のパッケージを定義します。これにより、pnpm は各パッケージを認識し、依存関係を適切に解決してくれます。
mermaidflowchart LR
root["ルート<br/>pnpm-workspace.yaml"] --> apps["apps/*<br/>アプリケーション群"]
root --> pkgs["packages/*<br/>共有パッケージ群"]
apps --> app1["web-app"]
apps --> app2["admin-app"]
pkgs --> ui["ui-components"]
pkgs --> utils["utils"]
app1 -.->|workspace依存| ui
app1 -.->|workspace依存| utils
app2 -.->|workspace依存| ui
この構造により、各アプリケーションは共有パッケージを直接参照でき、パッケージの変更が即座に反映されます。
Workspace のメリット
pnpm Workspace を使うことで、以下のメリットが得られます。
- シンボリックリンクによる高速な依存関係解決
- ディスク容量の節約(同じパッケージは 1 度だけダウンロード)
- ローカル開発の効率化(パッケージの変更が即座に反映)
- 依存関係の一元管理
Turborepo によるタスク管理の最適化
Turborepo は、Monorepo 内のタスク(ビルド、テスト、リント等)を効率的に実行するためのツールです。以下の機能により、開発体験を大幅に向上させます。
インクリメンタルビルド
Turborepo は、変更されたパッケージとその依存パッケージのみをビルドします。変更がないパッケージはキャッシュから結果を取得するため、ビルド時間が劇的に短縮されます。
タスクの並列実行
複数のパッケージのビルドやテストを並列で実行できます。依存関係を考慮しながら、可能な限り並列化することで、全体の実行時間を短縮します。
リモートキャッシュ
チーム開発では、リモートキャッシュを使って他の開発者のビルド結果を共有できます。これにより、CI/CD パイプラインでも高速なビルドが可能になります。
| # | 機能 | 効果 | 削減時間の目安 |
|---|---|---|---|
| 1 | インクリメンタルビルド | 変更部分のみビルド | 70-90% 削減 |
| 2 | 並列実行 | 複数タスクを同時実行 | 40-60% 削減 |
| 3 | リモートキャッシュ | チームでキャッシュ共有 | 80-95% 削減 |
アーキテクチャ設計のベストプラクティス
Monorepo を構築する際は、以下のディレクトリ構造を推奨します。
bashmonorepo-root/
├── apps/ # アプリケーション群
│ ├── web-app/ # ユーザー向けWebアプリ
│ └── admin-app/ # 管理画面
├── packages/ # 共有パッケージ群
│ ├── ui-components/ # UIコンポーネント
│ ├── utils/ # ユーティリティ関数
│ └── types/ # 型定義
├── pnpm-workspace.yaml
├── turbo.json
└── package.json
この構造により、役割が明確に分離され、メンテナンス性が向上します。
- apps/:エンドユーザーが利用するアプリケーション
- packages/:複数のアプリで共有するライブラリやコンポーネント
次のセクションでは、この構造を実際に構築する手順を詳しく見ていきましょう。
具体例
環境構築の準備
まずは、pnpm のインストールから始めます。pnpm は npm や Yarn の代替となるパッケージマネージャーです。
pnpm のインストール
以下のコマンドで pnpm をグローバルにインストールします。
bash# npm を使ってインストール
npm install -g pnpm
# インストール確認
pnpm --version
バージョンが表示されれば、インストールは成功です。執筆時点での最新版は 8.x 系ですが、7.x 以降であれば問題ありません。
プロジェクトルートの作成
新しいディレクトリを作成し、そこを Monorepo のルートとします。
bash# プロジェクトディレクトリを作成
mkdir vue-monorepo
cd vue-monorepo
# Git リポジトリを初期化
git init
この時点で、空のプロジェクトディレクトリが作成されました。
pnpm Workspace の設定
ワークスペース設定ファイルの作成
プロジェクトルートに pnpm-workspace.yaml を作成します。このファイルで、どのディレクトリをワークスペースとして認識するかを定義します。
yamlpackages:
- 'apps/*'
- 'packages/*'
この設定により、apps/ と packages/ 配下のすべてのディレクトリがワークスペースとして認識されます。
ルートの package.json 作成
ルートディレクトリに package.json を作成します。これは Monorepo 全体の設定を管理するファイルです。
json{
"name": "vue-monorepo",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "turbo run dev",
"build": "turbo run build",
"test": "turbo run test",
"lint": "turbo run lint"
}
}
"private": true を設定することで、このルートパッケージが誤って npm に公開されるのを防ぎます。
Turborepo のインストール
Turborepo をプロジェクトに追加します。
bashpnpm add -Dw turbo
-Dw オプションは、ワークスペースのルートに開発依存パッケージとしてインストールすることを意味します。
ディレクトリ構造の作成
アプリケーションとパッケージのディレクトリを作成しましょう。
bash# アプリケーション用ディレクトリ
mkdir -p apps/web-app
mkdir -p apps/admin-app
# 共有パッケージ用ディレクトリ
mkdir -p packages/ui-components
mkdir -p packages/utils
これで、基本的なディレクトリ構造が完成しました。
Vue.js アプリケーションの作成
Web アプリの構築
apps/web-app に Vue.js アプリケーションを作成します。create-vue を使用して、TypeScript 対応のプロジェクトを生成しましょう。
bashcd apps/web-app
pnpm create vue@latest .
対話的なプロンプトが表示されるので、以下のように選択します。
- Project name:
web-app - Add TypeScript? → Yes
- Add JSX Support? → No
- Add Vue Router? → Yes
- Add Pinia? → Yes
- Add Vitest? → Yes
- Add ESLint? → Yes
- Add Prettier? → Yes
プロジェクトが生成されたら、依存パッケージをインストールします。
bashpnpm install
package.json の調整
生成された apps/web-app/package.json を確認し、name フィールドを調整します。
json{
"name": "@vue-monorepo/web-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts"
}
}
@vue-monorepo/ というスコープを付けることで、パッケージ名の衝突を防ぎます。
管理画面アプリの作成
同様の手順で、apps/admin-app にも Vue.js アプリケーションを作成します。
bashcd ../admin-app
pnpm create vue@latest .
設定は web-app と同様にし、name は @vue-monorepo/admin-app とします。
共有パッケージの作成
UI コンポーネントパッケージ
packages/ui-components に共有可能な Vue コンポーネントパッケージを作成します。
まず、packages/ui-components/package.json を作成します。
json{
"name": "@vue-monorepo/ui-components",
"version": "0.1.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"build": "vite build",
"dev": "vite build --watch"
}
}
このパッケージは、ビルドされた JavaScript と型定義ファイルをエクスポートします。
依存パッケージのインストール
UI コンポーネントパッケージに必要な Vue と Vite をインストールします。
bashcd packages/ui-components
pnpm add -D vue vite @vitejs/plugin-vue typescript
-D オプションで開発依存パッケージとしてインストールします。
Vite 設定ファイルの作成
packages/ui-components/vite.config.ts を作成し、ライブラリモードでビルドするよう設定します。
typescriptimport { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'UiComponents',
fileName: 'index',
},
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue',
},
},
},
},
});
この設定により、Vue は外部依存として扱われ、バンドルサイズが最小化されます。
コンポーネントの作成
packages/ui-components/src/components/BaseButton.vue にシンプルなボタンコンポーネントを作成します。
vue<template>
<button
:class="['base-button', variant]"
@click="handleClick"
>
<slot />
</button>
</template>
<script setup lang="ts">
// Props の型定義
interface Props {
variant?: 'primary' | 'secondary' | 'danger';
}
// Props のデフォルト値設定
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
});
// Emits の定義
const emit = defineEmits<{
click: [event: MouseEvent];
}>();
// クリックハンドラー
const handleClick = (event: MouseEvent) => {
emit('click', event);
};
</script>
このコンポーネントは、3 つのバリエーション(primary、secondary、danger)を持つ汎用的なボタンです。
スタイルの追加
同じファイル内にスタイルを追加します。
vue<style scoped>
.base-button {
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
font-size: 1rem;
cursor: pointer;
transition: all 0.2s;
}
.primary {
background-color: #42b983;
color: white;
}
.primary:hover {
background-color: #359268;
}
.secondary {
background-color: #6c757d;
color: white;
}
.secondary:hover {
background-color: #5a6268;
}
.danger {
background-color: #dc3545;
color: white;
}
.danger:hover {
background-color: #c82333;
}
</style>
各バリエーションに応じた色とホバー効果が定義されています。
エントリーポイントの作成
packages/ui-components/src/index.ts を作成し、コンポーネントをエクスポートします。
typescript// コンポーネントのインポート
import BaseButton from './components/BaseButton.vue';
// 名前付きエクスポート
export { BaseButton };
// デフォルトエクスポート(プラグインとして使用可能)
export default {
install(app: any) {
app.component('BaseButton', BaseButton);
},
};
これにより、個別にインポートすることも、Vue プラグインとして一括登録することも可能になります。
ユーティリティパッケージの作成
packages/utils に汎用的なユーティリティ関数パッケージを作成しましょう。
まず、packages/utils/package.json を作成します。
json{
"name": "@vue-monorepo/utils",
"version": "0.1.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"build": "tsup src/index.ts --format esm --dts",
"dev": "tsup src/index.ts --format esm --dts --watch"
}
}
このパッケージでは、TypeScript のビルドに tsup を使用します。
tsup のインストール
bashcd packages/utils
pnpm add -D tsup typescript
tsup は、TypeScript のビルドを高速化するツールです。
ユーティリティ関数の作成
packages/utils/src/format.ts に日付フォーマット関数を作成します。
typescript/**
* 日付を YYYY-MM-DD 形式にフォーマットします
* @param date - フォーマットする日付オブジェクト
* @returns フォーマットされた日付文字列
*/
export function formatDate(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(
2,
'0'
);
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
/**
* 日付を YYYY年MM月DD日 形式にフォーマットします
* @param date - フォーマットする日付オブジェクト
* @returns フォーマットされた日付文字列(日本語形式)
*/
export function formatDateJa(date: Date): string {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
return `${year}年${month}月${day}日`;
}
コメントを充実させることで、IDE の補完機能でも説明が表示されるようになります。
バリデーション関数の追加
packages/utils/src/validation.ts にフォームバリデーション関数を作成します。
typescript/**
* メールアドレスの形式をチェックします
* @param email - チェックするメールアドレス
* @returns メールアドレスとして妥当な場合は true
*/
export function isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* 文字列が空でないかチェックします
* @param value - チェックする文字列
* @returns 空でない場合は true
*/
export function isNotEmpty(value: string): boolean {
return value.trim().length > 0;
}
/**
* 文字列が最小文字数以上かチェックします
* @param value - チェックする文字列
* @param minLength - 最小文字数
* @returns 最小文字数以上の場合は true
*/
export function hasMinLength(
value: string,
minLength: number
): boolean {
return value.length >= minLength;
}
これらの関数は、フォームバリデーションで頻繁に使用される基本的なチェック処理です。
エントリーポイントの作成
packages/utils/src/index.ts ですべての関数をエクスポートします。
typescript// 日付フォーマット関数
export { formatDate, formatDateJa } from './format';
// バリデーション関数
export {
isValidEmail,
isNotEmpty,
hasMinLength,
} from './validation';
これで、utils パッケージから必要な関数を簡単にインポートできるようになりました。
Turborepo の設定
turbo.json の作成
プロジェクトルートに turbo.json を作成し、タスクの依存関係とキャッシュ設定を定義します。
json{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"]
},
"lint": {
"outputs": []
}
}
}
この設定により、各タスクの実行順序とキャッシュ対象が定義されます。
設定の詳細解説
各フィールドの意味を説明します。
- dependsOn: タスクの依存関係を定義
"^build": 依存パッケージの build が先に実行される
- outputs: キャッシュ対象のディレクトリを指定
"dist/**": dist ディレクトリ配下をキャッシュ
- cache: キャッシュの有効/無効を設定
false: 開発サーバーなど常に実行が必要なタスク
- persistent: タスクが継続的に実行されるかを指定
true: dev サーバーなど、終了しないタスク
下図は、Turborepo のタスク実行フローを示しています。
mermaidflowchart TB
start["turbo run build"] --> check1{"packages/utils<br/>変更あり?"}
check1 -->|Yes| build1["utils をビルド"]
check1 -->|No| cache1["キャッシュから取得"]
build1 --> check2{"packages/ui-components<br/>変更あり?"}
cache1 --> check2
check2 -->|Yes| build2["ui-components をビルド<br/>(utils に依存)"]
check2 -->|No| cache2["キャッシュから取得"]
build2 --> parallel["アプリのビルド<br/>(並列実行)"]
cache2 --> parallel
parallel --> app1["web-app ビルド"]
parallel --> app2["admin-app ビルド"]
app1 --> done["完了"]
app2 --> done
style build1 fill:#ccffcc
style build2 fill:#ccffcc
style cache1 fill:#ffcccc
style cache2 fill:#ffcccc
Turborepo は、変更されたパッケージのみをビルドし、それ以外はキャッシュから取得することで、大幅な時間短縮を実現します。
アプリケーションから共有パッケージを使用
パッケージの追加
web-app から ui-components と utils を使用できるようにします。
bashcd apps/web-app
pnpm add @vue-monorepo/ui-components @vue-monorepo/utils --workspace
--workspace オプションにより、ローカルのワークスペースパッケージとしてリンクされます。
コンポーネントの使用例
apps/web-app/src/App.vue で共有コンポーネントを使用します。
vue<script setup lang="ts">
// 共有パッケージからインポート
import { BaseButton } from '@vue-monorepo/ui-components';
import { formatDateJa } from '@vue-monorepo/utils';
import { ref } from 'vue';
// 現在の日付を表示
const currentDate = ref(formatDateJa(new Date()));
// ボタンクリックハンドラー
const handleClick = () => {
alert(`今日は${currentDate.value}です`);
};
</script>
このように、作成した共有パッケージを簡単にインポートして使用できます。
テンプレートの作成
同じファイル内にテンプレートを追加します。
vue<template>
<div class="app">
<h1>Vue.js Monorepo サンプル</h1>
<div class="date-display">
<p>今日の日付: {{ currentDate }}</p>
</div>
<div class="button-group">
<BaseButton variant="primary" @click="handleClick">
Primary ボタン
</BaseButton>
<BaseButton variant="secondary" @click="handleClick">
Secondary ボタン
</BaseButton>
<BaseButton variant="danger" @click="handleClick">
Danger ボタン
</BaseButton>
</div>
</div>
</template>
BaseButton コンポーネントが、まるで同じプロジェクト内にあるかのように自然に使用できていますね。
スタイルの追加
vue<style scoped>
.app {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
.date-display {
margin: 2rem 0;
font-size: 1.2rem;
}
.button-group {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
</style>
これで、共有コンポーネントを使用したアプリケーションが完成しました。
開発とビルドの実行
開発モードの起動
プロジェクトルートから、すべてのアプリケーションを開発モードで起動できます。
bash# ルートディレクトリで実行
pnpm dev
Turborepo が各パッケージの dev スクリプトを並列で実行し、以下のようなログが表示されます。
sql• Packages in scope: @vue-monorepo/admin-app, @vue-monorepo/ui-components,
@vue-monorepo/utils, @vue-monorepo/web-app
• Running dev in 4 packages
各アプリケーションが異なるポートで起動し、共有パッケージの変更も即座に反映されます。
ビルドの実行
本番用のビルドも同様にルートから実行できます。
bashpnpm build
Turborepo が依存関係を解析し、正しい順序でビルドを実行します。
sql• Packages in scope: @vue-monorepo/admin-app, @vue-monorepo/ui-components,
@vue-monorepo/utils, @vue-monorepo/web-app
• Running build in 4 packages
• Remote caching disabled
初回ビルド後、変更がないパッケージは次回からキャッシュが使用されるため、ビルド時間が大幅に短縮されます。
特定のパッケージのみ実行
特定のアプリケーションのみを起動したい場合は、filter オプションを使用します。
bash# web-app のみ開発モードで起動
pnpm --filter @vue-monorepo/web-app dev
# ui-components のみビルド
pnpm --filter @vue-monorepo/ui-components build
このように、柔軟にタスクを実行できます。
TypeScript の型共有
型定義パッケージの作成
packages/types に共通の型定義を管理するパッケージを作成しましょう。
bashmkdir -p packages/types/src
packages/types/package.json を作成します。
json{
"name": "@vue-monorepo/types",
"version": "0.1.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsup src/index.ts --format esm --dts",
"dev": "tsup src/index.ts --format esm --dts --watch"
}
}
共通型の定義
packages/types/src/user.ts にユーザー関連の型を定義します。
typescript/**
* ユーザーの基本情報
*/
export interface User {
id: string;
name: string;
email: string;
createdAt: Date;
updatedAt: Date;
}
/**
* ユーザー作成時の入力データ
*/
export interface CreateUserInput {
name: string;
email: string;
password: string;
}
/**
* ユーザー更新時の入力データ
*/
export interface UpdateUserInput {
name?: string;
email?: string;
}
これらの型定義は、フロントエンドとバックエンドで共有できます。
エントリーポイントの作成
packages/types/src/index.ts で型をエクスポートします。
typescript// ユーザー関連の型
export type {
User,
CreateUserInput,
UpdateUserInput,
} from './user';
型パッケージの使用
アプリケーションから型パッケージを使用します。
bashcd apps/web-app
pnpm add @vue-monorepo/types --workspace
コンポーネント内で型を使用する例です。
typescriptimport { ref } from 'vue';
import type { User } from '@vue-monorepo/types';
// User 型を使用
const currentUser = ref<User | null>(null);
// API からユーザー情報を取得する関数
async function fetchUser(userId: string): Promise<User> {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
型が共有されることで、フロントエンドとバックエンドで一貫性のある開発が可能になります。
CI/CD パイプラインの設定例
GitHub Actions の設定
.github/workflows/ci.yml に CI パイプラインを設定します。
yamlname: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'pnpm'
この部分では、リポジトリのチェックアウトと pnpm のセットアップを行っています。
依存パッケージのインストール
yaml- name: Install dependencies
run: pnpm install --frozen-lockfile
--frozen-lockfile オプションにより、lockfile の変更を防ぎ、再現性を確保します。
Turborepo のキャッシュ設定
yaml- name: Setup Turborepo cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
Turborepo のキャッシュを GitHub Actions でも活用することで、CI 実行時間を短縮できます。
ビルドとテストの実行
yaml- name: Build
run: pnpm build
- name: Lint
run: pnpm lint
- name: Test
run: pnpm test
すべてのタスクが Turborepo によって最適化され、並列実行されます。
この設定により、プルリクエストごとに自動的にビルド、リント、テストが実行され、コード品質が保たれるようになります。
まとめ
本記事では、pnpm と Turborepo を使った Vue.js Monorepo の構築方法を、環境構築から実際のアプリケーション開発まで、段階的に解説してきました。
Monorepo 導入の主なメリット
| # | メリット | 具体的な効果 |
|---|---|---|
| 1 | コードの再利用性向上 | 共通コンポーネントやユーティリティを一元管理 |
| 2 | 開発効率の改善 | パッケージの変更が即座に反映される |
| 3 | ビルド時間の短縮 | Turborepo のキャッシュで 70-90% の時間削減 |
| 4 | 型安全性の向上 | TypeScript の型定義を複数プロジェクトで共有 |
| 5 | メンテナンス性の向上 | すべてのコードが 1 つのリポジトリで管理される |
実装のポイント振り返り
pnpm Workspace により、複数のパッケージを効率的に管理できるようになりました。シンボリックリンクによる依存関係の解決は、npm や Yarn よりも高速でディスク容量も節約できます。
Turborepo の導入により、タスクの並列実行とインクリメンタルビルドが可能になり、開発体験が大幅に向上しました。特にキャッシュ機能は、CI/CD パイプラインでも威力を発揮します。
共有パッケージの作成を通じて、UI コンポーネント、ユーティリティ関数、型定義などを一元管理できるようになりました。これにより、コードの重複が減り、保守性が向上します。
今後の拡張の可能性
構築した Monorepo は、以下のような拡張が可能です。
- Storybook の導入による UI コンポーネントのカタログ化
- Vitest による共有パッケージのテスト強化
- ESLint と Prettier の共通設定によるコード品質の統一
- Changesets によるバージョン管理の自動化
- Vercel や Netlify へのデプロイ設定
Monorepo は初期構築に多少の労力が必要ですが、プロジェクトが成長するにつれて、その恩恵を実感できるようになります。複数の Vue.js プロジェクトを管理している方は、ぜひこの手法を試してみてくださいね。
最初は小規模な構成から始めて、必要に応じてパッケージを追加していくアプローチがおすすめです。本記事で紹介した基本的な構成を土台に、あなたのプロジェクトに最適な Monorepo を構築していってください。
関連リンク
articleVue.js Monorepo 構築:pnpm/Turborepo でアプリとパッケージを一元管理
articleVue.js ルーター戦略比較:ネスト/動的セグメント/ガードの設計コスト
articleVue.js でメモリリーク?watch/effect/イベント登録の落とし穴と検知法
articleVue.js リアクティビティ内部解剖:Proxy/ref/computed を図で読み解く
articleVue.js 本番運用チェックリスト:CSP/SRI/Cache-Control/エラーログの要点
articleVue.js コンポーネント API 設計:props/emit/slot を最小 API でまとめる
articleDeno/Bun/Node のランタイムで共通動く Zod 環境のセットアップ
articleFFmpeg マッピング完全攻略:-map/-disposition/-metadata の黄金レシピ
articleYarn PnP で「モジュールが見つからない」時の解決大全:packageExtensions/patch で対処
articleESLint no-restricted-* 活用レシピ集:API 禁止・依存制限・危険パターン封じ込め
articleWeb Components のポリフィル戦略:@webcomponents 系を最小限で入れる判断基準
articleDify ワークフロー定型 30:分岐・並列・リトライ・サーキットブレーカの型
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来