コンテンツにスキップ

0001. モノレポ構成 (pnpm workspaces + Turborepo)

コンテキストと課題

waste-link は Web・モバイル・(後から追加された) API・共有ロジック・DB スキーマを単一プロダクトとして 開発する。これらは型 (Zod スキーマ) や定数を共有し、リリースのタイミングも近い。リポジトリを分割すると 型の共有・横断的な変更・CI が煩雑になる。1 つのリポジトリで複数パッケージを管理しつつ、 ビルド/型検査/テストを依存関係順に効率良く回せる構成が必要だった。

決定

pnpm workspaces によるモノレポ + Turborepo によるタスクオーケストレーション を採用する。

ワークスペースは 3 グロブで構成する:

# pnpm-workspace.yaml
packages:
  - "apps/*"
  - "packages/*"
  - "scripts"

含まれるパッケージ:

パッケージ name 役割
apps/web @waste-link/web Web フロント (Vite + React)
apps/api @waste-link/api API (Hono on Cloudflare Workers)
apps/mobile @waste-link/mobile モバイル (Expo / React Native)
packages/shared @workspace/shared 共有 Zod スキーマ・定数・純関数
packages/supabase @workspace/supabase Supabase クライアント / 自動生成型
  • パッケージマネージャは pnpm を固定: package.jsonpackageManager: "pnpm@9.15.0" を Single Source of Truth とし、Node は >=22 (.nvmrc = 22)。
  • タスクは Turborepo 経由: turbo.jsonbuild / typecheck / test^build (依存パッケージのビルド) に依存し、build 出力は dist/** 等をキャッシュ、devcache: false かつ persistent: true。ルート package.jsondev/build/typecheck/testturbo run ... に委譲する (lint のみ biome check . を直接実行)。
  • 共有パッケージはビルドレス: @workspace/shared / @workspace/supabasemain./src/index.ts のソース直参照で、TS の path alias (tsconfig.base.json) で解決する。
  • node_modules は hoisted レイアウト: .npmrcnode-linker=hoisted。これは Expo / Metro / React Native が pnpm の厳格な isolated レイアウトを完全サポートせず @expo/metro-runtime 等の解決に失敗するため、Expo 公式推奨に合わせた判断。

影響 (Consequences)

良い点

  • 共有スキーマ (@workspace/shared) を Web / API / Mobile が workspace:* 依存で直接参照でき、 型と検証ロジックが 1 箇所に集約される。
  • Turborepo の ^build 依存とキャッシュにより、CI (pnpm turbo run typecheck test build) が 依存順に走り、未変更パッケージはキャッシュで短縮される。
  • packageManager フィールド固定で、ローカルと CI (pnpm/action-setup@v4 がこの値を参照) の pnpm バージョンずれを防ぐ。

トレードオフ / 注意点

  • hoisted node_modules は pnpm の隔離 (phantom dependency 防止) という利点を一部捨てている。 宣言していない依存にアクセスできてしまうリスクは、Expo 互換のため受け入れている。
  • スコープ名が二系統 (@waste-link/*@workspace/*) に分かれている。apps は @waste-link/*、共有パッケージは @workspace/*。新規パッケージ追加時に取り違えやすい。
  • ビルドレスな共有パッケージは TS path alias 前提のため、各アプリの tsconfig / バンドラ (Vite・wrangler・Metro) でエイリアス解決を揃える必要がある。

根拠 (典拠)

  • pnpm-workspace.yaml — ワークスペースグロブ (apps/* / packages/* / scripts)。
  • turbo.jsondev(cache:false, persistent) / build(^build, outputs dist/**) / typecheck / lint / test(outputs coverage/**) / clean
  • package.json(ルート) — packageManager: "pnpm@9.15.0"engines.node: ">=22"、scripts (dev/build/lint=biome check ./typecheck/test)、devDeps (@biomejs/biome 1.9.4 / turbo ^2.3.3 / typescript ^5.7.2 / @changesets/cli)。
  • .nvmrc22
  • .npmrcnode-linker=hoisted(Expo/RN 互換のコメント付き) / auto-install-peers=true / strict-peer-dependencies=false
  • apps/web/package.jsonapps/api/package.jsonapps/mobile/package.jsonpackages/shared/package.jsonpackages/supabase/package.json — 各 name
  • tsconfig.base.json — ES2022 / moduleResolution: bundler / strict / noUncheckedIndexedAccess / path alias @workspace/shared@workspace/supabase
  • .github/workflows/ci.yml:26-36 — pnpm バージョンは packageManager を参照する旨のコメント。