🎃
pnpmモノレポで`workspace:*`のままnpm publishしてハマった話
TL;DR
- pnpmの
workspace:*はローカル専用のプロトコル - そのままnpm publishすると、インストールする人が使えない
-
pnpm publishなら自動で置き換えてくれるが、設定が必要 - シンプルに行くなら最初からnpmワークスペース + 通常のバージョン指定
何が起きたか
pnpmでモノレポを構成していて、パッケージ間の依存をworkspace:*で書いていました。
// packages/react/package.json
{
"name": "@my-lib/react",
"dependencies": {
"@my-lib/core": "workspace:*"
}
}
ローカル開発では問題なく動いていたので、そのままnpm publishしたところ...
npm warn Invalid dependency "@my-lib/core": "workspace:*"
公開されたパッケージをインストールしようとすると:
npm error Could not resolve dependency:
npm error peer workspace:* from @my-lib/react@0.1.0
なぜこうなるのか
workspace:*はpnpm/npm/yarnのワークスペース機能で使えるローカル専用のプロトコルです。
- ローカルでは「同じワークスペース内のパッケージを参照する」という意味
- 公開後は解決できない(npmレジストリは
workspace:を知らない)
解決策
方法1: pnpm publishを使う(設定が必要)
pnpmには公開時にworkspace:*を実際のバージョンに置き換える機能があります。
workspace:* → *
workspace:^ → ^x.y.z
workspace:~ → ~x.y.z
ただし、これはデフォルトでは有効になっていません。以下のような設定が必要です:
# pnpm-workspace.yaml
packages:
- 'packages/*'
そしてpnpm publishを使います(npm publishではダメ)。
方法2: 手動でバージョンを書く
公開前にworkspace:*を実際のバージョンに書き換えます。
{
"dependencies": {
"@my-lib/core": "^1.2.3"
}
}
面倒ですが確実です。
方法3: npmワークスペースに移行する(一番楽)
pnpmをやめてnpmワークスペースを使い、最初から通常のバージョン指定にする方法です。
// package.json (ルート)
{
"workspaces": [
"packages/*"
]
}
// packages/react/package.json
{
"dependencies": {
"@my-lib/core": "^1.2.3"
}
}
npmワークスペースでは、ローカルに該当パッケージがあれば自動的にシンボリックリンクされます。workspace:*のような特殊な記法は不要です。
pnpmとnpmの公開先は同じ
誤解しやすいポイントですが、pnpm publishもnpm publishも同じnpmレジストリに公開されます。
パッケージマネージャーはあくまでクライアントツールの違いで、公開先は共通です。なので:
- pnpmで公開したパッケージをnpmでインストールできる
- npmで公開したパッケージをpnpmでインストールできる
- yarnでも同様
結論
| 方法 | メリット | デメリット |
|---|---|---|
pnpm + workspace:* + pnpm publish
|
ローカル開発が楽 | 設定が複雑、npm publishで事故る |
| pnpm + 手動バージョン指定 | 確実 | バージョン更新が面倒 |
| npm workspaces + 通常バージョン | シンプル、事故りにくい | pnpmの高速さがない |
個人的には、小規模なモノレポならnpmワークスペースで十分だと思います。pnpmの高速インストールが必要な大規模プロジェクトなら、pnpm publishのワークフローをしっかりと整備していくことが必要になります。
以上、なにかの参考になれば幸いです。
Discussion