コンテンツにスキップ

npmではなくpnpmを使う理由

このリポジトリは pnpm を前提 に設計されています。npm はサポート対象外です。

結論を先に言うと、プラグインシステムを成立させるために「hoisting問題を避ける必要がある」ので、pnpm を採用しています。

このリポジトリが求める前提

プラグインは 独立パッケージとして運用できること が前提です。具体的には以下を満たす必要があります。

  • プラグインごとの依存関係が明示され、未宣言依存が混入しない
  • プラグイン単位で依存を更新し、影響範囲を局所化できる
  • workspace:* による内部参照が確実に解決される
  • CI とローカルで依存解決が一致し、再現性がある

npmのhoistingがプラグインシステムと相性が悪い理由

npm(および Yarn v1)は依存関係を できるだけ上位に持ち上げる(hoist) 方針です。
これにより次の問題が起きやすくなります。

1) phantom dependencies(幽霊依存)

本来は依存として宣言していないパッケージが、
hoist によって 偶然見えてしまう 状態が発生します。

  • プラグインAが lodash を依存に書いていない
  • 別のプラグインBが lodash を依存している
  • hoist によりルートに node_modules/lodash ができる
  • プラグインAから lodash が import できてしまう

結果として「プラグインAは依存を宣言していないのに動く」状態になり、
リポジトリ内では動くが、単体で配布すると壊れるという事態を生みます。

2) プラグイン単位の更新が局所化できない

hoist されると、依存の実体がワークスペース全体で共有されがちです。
そのため、プラグインAの更新がB/Cにも影響しやすくなります。

さらに npm の lockfile は ワークスペース全体で解決されるため、 「Aだけ上げたつもり」が、結果として 全体アップデートに近い挙動になりがちです。

これが、プラグインシステムで避けたい「境界の崩壊」です。

pnpmが提供する解決策

pnpmは依存を content-addressable store に一度だけ保存し、
各パッケージの node_modules には 必要なものだけをシンボリックリンクで見せます。

これにより以下が成立します。

  • 依存を宣言していないパッケージは 見えない
  • プラグイン単位の依存更新がしやすい
  • 依存境界が明確になり、配布・互換性検証が安全になる
  • 依存解決が固定され、CI とローカルの一致が保たれる

プラグインシステムの前提を満たすには、依存境界が厳密であることが必須であり、
pnpm はそれを実現できるため採用しています。

このリポジトリでpnpmが必須な理由(運用面)

技術的な理由に加えて、以下の運用上の事情もあります。

  • pnpm-workspace.yamlplugins/* を含めて運用している
  • pnpm --filterpnpm -r など pnpm固有のコマンドをスクリプトで使用している
  • pnpm-lock.yamlpackageManager により 解決結果とツールバージョンを固定している

npm を使うと、これらが機能しない、または挙動が変わります。

npmを使うと起きやすい問題(まとめ)

  • pnpm前提のスクリプトが動かない
  • package-lock.json が生成され、依存解決が分岐する
  • hoisting により 環境差分・幽霊依存が発生しやすい
  • プラグイン単位の互換性検証が難しくなる

推奨手順

corepack enable
corepack prepare pnpm@9.15.4 --activate
pnpm install

corepack が利用できない場合は、pnpm を別途インストールしてから pnpm install を実行してください。

代替ツールについて

Yarn v2/v3 や Bun でも hoisting 問題を避ける構成は可能ですが、
このリポジトリの 運用スクリプト・ロックファイル・ワークスペース設定は pnpm を前提にしています。
そのため、サポート対象は pnpm のみです。