📦

Node.js 向けパッケージマネージャ兼ワークスペース管理ツール pnpm を箇条書きで解説

commits11 min read

対象読者

  • pnpm を使ってみたい方、興味がある方、入門したい方。
  • npmyarnlerna よりもワークスペース管理をもっと簡単にしたい方。
  • npmyarn のインストール速度が遅く感じる、もしくは容量を多く取られて困ると感じている方。

前提知識

  • npm もしくは yarn のパッケージマネージャとしての基本的な使い方

pnpm とは

  • yarnlerna のような npm の代替ツールです。
  • 組み込みでワークスペース (モノリシックリポジトリ) へのサポートがあります。
    • よりシンプルな方法でのサポートをしており、やりたいことを実現する方法が非常に明快で、大きな特徴です。
    • (個人的) lerna などでは package.json に書かれた scriptsnpm, yarn, lerna のどれから起動させればよいのかわかりにくいと感じていました。こういった悩みもなくなると考えています。
  • より厳格に依存を管理できます。
    • node_modules/ の構造に仕掛けがあります。
    • 依存の依存、のようなものに対し、pnpm プロジェクトからアクセスできないようにしています。
    • (例外) 一部の prettiereslint などのツールのプラグインは public-hoist-pattern 設定で除外することができます。
    • (例外) 上記含めた、有名なプラグインはデフォルトで除外されています。
    • (例外) この挙動は、設定をすることで最初はゆるくして徐々に強くしていくことも可能です。
    • 更に詳しい解説は pnpm 作者ブログの Flat node_modules is not the only way (日本語訳) も参照してみてください。
  • パッケージインストールはハードリンクで実現されます。
    • あらゆるケースで npm, yarn, Yarnのプラグアンドプレイ より速く、これらは基本コピーで実現されているので、容量も節約できます。
    • グローバルストア (~/.pnpm-store) という場所で一括管理されます。
    • (応用) git のように、ハッシュ値をもとに、ファイル単位でキャッシュされます。
    • (応用) pnpm store status を用いて変更を誤って加えるなどの不整合が起きていないか検証できます。
  • 誰が使っている?

インストール

基本的な使い方

  • pnpm コマンドのみを使います。npm, yarn, lerna を使う必要はありません。
  • (応用) pnpx は非推奨になりました。 pnpm execpnpm dlx を使います。
  • (細かい) devDepedenciespnpm 自体は 入れない ものしか見たことがありません。グローバルインストールしておくことになるでしょう。
  • pnpm 自体の更新も pnpm i -g pnpm で可能です。
  • パッケージインストールは npm と同様の方法で、 pnpm add/pnpm i を用いて行なえます。
  • ロックファイルは pnpm-lock.yaml になります。 package-lock.json は生成されず、無視されます

ワークスペース

ワークスペースになる条件

ワークスペースのディレクトリ構造の例

▾ <ワークスペースのルート>/
  ▾ packages/
      ▸ core/
      ▸ cli/
      ▾ doc/
        ▾ node_modules/
          ▸ .bin/
        ▸ src/  # など
          package.json
  ▸ node_modules/
    pnpm-workspace.yaml
    pnpm-lock.yaml
    .npmrc  # 必須ではない
    package.json

pnpm-workspace.yaml の例

packages:
  # packages/ と components/ のすべてのサブディレクトリを含める
  - 'packages/**'
  - 'components/**'
  # test ディレクトリに含まれるすべてのパッケージを除外する
  - '!**/test/**'

ワークスペース内でのライフサイクル

  • pnpm install はどこで使っても同じ効果です。
    • pnpm install を行うとすべての子パッケージ内の依存をインストールします。
    • 子パッケージの依存を含めたすべての依存の 実体 はワークスペースのルートのバーチャルストア (node_modules/.pnpm/) に集約され、そこからシンボリックリンクが子パッケージの node_modules/ 内に貼られます。
    • (細かい) バーチャルストア (.pnpm/) の中身は、前述したグローバルストアと同じものへのハードリンクです。
    • (例外) recursive-install 設定を使っている場合は、個別にインストールすることもできます。
  • 作業ディレクトリに依存するコマンドは --workspace-root, -w を指定することでルートから実行できます。
  • pnpm run ... は現在のディレクトリにある package.json を見ます。
  • すべてのパッケージの特定の名前のスクリプトを実行する方法はいくつかあります。
    • --recursive, -r を指定する。
    • (例外) "build": "pnpm run build -r" のように書くとループします。以下で対処できます。
    • フィルタリング を用いる。
      • pnpm run build --filter ./packages./packages 配下にある子パッケージ全てで実行します。
      • --filter "*core" で名前 (= packge.json.name) に core と末尾につく子パッケージを指定できます。
      • (応用) さらに変更があったパッケージ、及びその依存、といった指定もできます。詳しくはドキュメントを参照してください。
  • ワークスペース内で依存関係を定義するには ワークスペースプロトコル (workspace:) を使用します。
    • package.json"@my-org/my-package": "workspace:*" と書くと同名の子パッケージを依存として加えます。 node_modules/ 内から対象へ直接シンボリックリンクが張られます。
    • "foo": "workspace:../foo" のように、相対パスを使うこともできます。
    • pnpm publish 時には、ワークスペースプロトコルを使わない形に変換されます

その他

  • モノリポでのリリース管理
    • pnpm と Changesets を組み合わせて使用する ことができます。
      • (個人的) こちらも記事にしたい。
      • (個人的) Changesets はモノリポでなくとも semantic-release の代替として非常に良いライフサイクルと体験をもたらしてくれると感じました。
  • 孫依存含め、複数のバージョン存在している esbuild を統一したい
    • インストール処理でトラブルになるので孫依存に存在しているケースではこの対処をしたほうが良いです。
    • .pnpmfile.cjs が役に立ちます。
    const { esbuild } = require('./package.json').devDependencies;
    
    function readPackage(pkg) {
      for (const section of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
        if (pkg[section].esbuild) {
          pkg[section].esbuild = esbuild;
        }
      }
      return pkg;
    }
    
    module.exports = {
      hooks: {
        readPackage,
      },
    };
    
    
    • その他、似たような問題が起きる場合は、 .pnpmfile.cjs を活用することで対処できるかもしれません。
  • pnpm 以外を使用した際にエラーにする方法。
    • これは非常に役立ちますが、注意点として、公開用のライブラリパッケージでは使用できません。ライブラリ使用者が npm を使用していた際にエラーになるからです。"private": true なら使える、と機械的に判断して良いでしょう。ワークスペースルートでも使えます。
    • only-allow を使用する方法。
    • engine-strcit 設定を true にして package.jsonengines 項を記述する方法。
    ; .npmrc
    engine-strict=true
    
    "engines": {
      "npm": "forbidden, use pnpm",
      "pnpm": ">=6",
      "yarn": "forbidden, use pnpm"
    }
    

他のツールとの連携

GitHub Actions

dependabot

renovate

質問等あれば

  • ドキュメントの一部を翻訳したりしていますが無関係です。twitter や zenn のコメント欄でよければ何でも聞いてください。pnpmChangesetsGitHub Actions との連携など。

PRs are welcome

  • リンク切れ、typo 修正、他なんでも、1bit の変更でもお気軽に PR ください。
GitHubで編集を提案

Discussion

ログインするとコメントできます