pnpm の monorepo でコンポーネントライブラリとそれを使うカタログサイトを同時に開発していると、「ライブラリを変更するたびにビルドが必要」という問題に直面します。
この記事では、package.json の Custom Condition(カスタムコンディション) を使って、ビルドなしで TypeScript ソースを直接参照できるようにする方法を紹介します。今回は Astro(SSR あり)の構成を例に説明します。
次のような構成を考えます。
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 ← 毎回これが必要
↓
カタログサイトで変更が反映される
これが開発体験を大きく損ないます。
Node.js / Vite には 条件付きエクスポート(Conditional Exports) という仕組みがあります。
package.json の exports フィールドで「どの条件のときにどのファイルを返すか」を定義できます。
この仕組みを使って、開発時だけ TypeScript ソースを直接参照させます。
コンポーネントライブラリを変更
↓
即座に反映される ✅(ビルド不要)
exports にカスタムコンディションを追加packages/component-lib/package.json の exports フィールドに、@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 は条件を上から順に評価し、最初にマッチしたものを使います。import や types より下に置くと先にマッチしてしまい、カスタムコンディションが無視されます。
{
"exports": {
".": {
"@lib/source": "./src/index.ts", // ✅ 一番上に置く
"import": "./dist/index.mjs", // 下にあるので @lib/source が優先される
"default": "./dist/index.mjs"
}
}
}
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 を持つため、クライアント / サーバーの両側で条件を設定する必要があります。
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 ステップです。
package.json の exports にカスタムコンディションを追加する(一番上に配置すること)"@lib/source": "./src/index.ts"
conditions: ["@lib/source"]
ssr.noExternal と ssr.resolve.conditions も設定する設定量は少なく、本番ビルドへの影響もありません。monorepo で複数パッケージを並行開発している方はぜひ試してみてください。