🎃

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