pnpm monorepo でビルドいらずのパッケージ参照を実現する — Custom Condition という選択肢

作成日: 2026-04-03 /

はじめに

pnpm の monorepo でコンポーネントライブラリとそれを使うカタログサイトを同時に開発していると、「ライブラリを変更するたびにビルドが必要」という問題に直面します。

この記事では、package.jsonCustom Condition(カスタムコンディション) を使って、ビルドなしで TypeScript ソースを直接参照できるようにする方法を紹介します。今回は Astro(SSR あり)の構成を例に説明します。


背景:monorepo でよくある問題

次のような構成を考えます。

packages/
  component-lib/      ← React コンポーネント群
  catalog-site/       ← Astro コンポーネントカタログサイト

カタログサイトは workspace:* でコンポーネントライブラリに依存しています。

{
  "dependencies": {
    "@myorg/component-lib": "workspace:*"
  }
}

コンポーネントライブラリは dist/ をビルド成果物として publish しているため、ディレクトリ構成は以下のようになっています。

component-lib/
  src/        ← TypeScript ソース
  dist/       ← ビルド成果物(.mjs / .cjs / .d.mts)

カタログサイトの Vite はデフォルトで dist/ の成果物を参照するため、ライブラリを変更するたびに次の手順が必要です。

コンポーネントライブラリを変更
    ↓
pnpm --filter component-lib build  ← 毎回これが必要
    ↓
カタログサイトで変更が反映される

これが開発体験を大きく損ないます。


解決策:Custom Condition

Node.js / Vite には 条件付きエクスポート(Conditional Exports) という仕組みがあります。 package.jsonexports フィールドで「どの条件のときにどのファイルを返すか」を定義できます。

この仕組みを使って、開発時だけ TypeScript ソースを直接参照させます。

コンポーネントライブラリを変更
    ↓
即座に反映される ✅(ビルド不要)

設定方法

1. コンポーネントライブラリ側:exports にカスタムコンディションを追加

packages/component-lib/package.jsonexports フィールドに、@lib/source というカスタムコンディションを追加します。

{
  "exports": {
    ".": {
      "@lib/source": "./src/index.ts",
      "types": {
        "import": "./dist/index.d.mts",
        "require": "./dist/index.d.cts"
      },
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    }
  }
}

@lib/source という条件が有効なときだけ ./src/index.ts(TypeScript ソース)を返し、それ以外は従来どおり dist/ の成果物を返します。

⚠️ 条件の順番が重要

カスタムコンディションは exports 内で一番上に配置してください。Node.js / Vite は条件を上から順に評価し、最初にマッチしたものを使います。importtypes より下に置くと先にマッチしてしまい、カスタムコンディションが無視されます。

{
  "exports": {
    ".": {
      "@lib/source": "./src/index.ts",  // ✅ 一番上に置く
      "import": "./dist/index.mjs",     // 下にあるので @lib/source が優先される
      "default": "./dist/index.mjs"
    }
  }
}

2. カタログサイト側:Vite に条件を渡す

packages/catalog-site/astro.config.mjs で、開発時のみ @lib/source コンディションを有効化します。

const resolve =
  process.env.NODE_ENV === "development"
    ? { conditions: ["@lib/source"] }
    : undefined;

export default defineConfig({
  vite: {
    resolve: resolve,
    ssr: {
      noExternal: ["@myorg/component-lib"],
      resolve: resolve,
    },
  },
});

動作の仕組み

import { Button } from "@myorg/component-lib";

開発時NODE_ENV=development

Vite が exports を解決
  → conditions に "@lib/source" が含まれる
  → "./src/index.ts" を返す
  → ビルド不要でソース直参照 ✅

本番ビルド時NODE_ENV=production

Vite が exports を解決
  → conditions に "@lib/source" が含まれない
  → "./dist/index.mjs" を返す
  → 通常のビルド成果物を参照 ✅

本番ビルドの挙動は従来と変わらないため、安心して導入できます。


Astro(SSR)での注意点

Astro は SSR を持つため、クライアント / サーバーの両側で条件を設定する必要があります。

vite: {
  // クライアントサイドバンドル向け
  resolve: { conditions: ["@lib/source"] },

  ssr: {
    // サーバーサイドバンドル向け
    noExternal: ["@myorg/component-lib"],  // ← これも必要
    resolve: { conditions: ["@lib/source"] },
  },
}

ssr.noExternal を指定しないと、SSR バンドル時にコンポーネントライブラリが「外部パッケージ」扱いされ、カスタムコンディションが無視されます。Astro を使う場合は忘れずに設定してください。


メリット・デメリット

内容
ビルド不要で即座にソース変更が反映される
HMR が正しく動作する
本番ビルドは従来と変わらない
⚠️Vite / バンドラ側での設定が必要
⚠️TS ソースをそのまま食わせるため、消費側が TypeScript に対応している必要がある
⚠️カスタム条件名の命名を慣習として統一する必要がある

代替手法との比較

手法ビルド不要設定コスト備考
Custom Condition本記事の手法
exports["."]src/ に直接向ける本番も src 参照になってしまう
vite-plugin-turbo-resolve追加依存が増える
tsconfig pathsバンドラによっては動かない
毎回 buildなし開発体験が辛い

まとめ

Custom Condition を使ったビルドレス参照の設定は、次の 3 ステップです。

  1. コンポーネントライブラリの package.jsonexports にカスタムコンディションを追加する(一番上に配置すること)
    "@lib/source": "./src/index.ts"
    
  2. カタログサイトの Vite 設定で、開発時のみ条件を渡す
    conditions: ["@lib/source"]
    
  3. Astro(SSR あり)の場合は ssr.noExternalssr.resolve.conditions も設定する

設定量は少なく、本番ビルドへの影響もありません。monorepo で複数パッケージを並行開発している方はぜひ試してみてください。