pnpm の workspaces と catalogs でモノレポの packages を管理する

node.js

pnpm は高速かつ省ディスクで多機能な Node.js のパッケージマネージャ。 デフォルトで postinstall スクリプトが無効で、minimumReleaseAge によって publish されたばかりのバージョンはインストールしないようにできたりと、 最近 axios で起こったようなサプライチェーン攻撃に対して頑強という長所もある。

$ corepack enable pnpm
$ pnpm --version
10.33.0

pnpm-workspace.yaml で packages を定義して –filter でコマンドを実行したり、catalog で依存パッケージのバージョンを統一するモノレポのための仕組みも備える。

$ vi pnpm-workspace.yaml
packages:
  - "packages/*"
  - "apps/*"

catalog:
  react: "^19.2.4"

minimumReleaseAge: 1440

$ pnpm add --filter lib-a react@catalog:
Progress: resolved 2, reused 0, downloaded 0, added 0, done
Done in 312ms using pnpm v10.33.0

$ cat packages/lib-a/package.json 
{
  ...
  "dependencies": {
    "react": "catalog:"
  }
}

$ tree .
.
├── apps
│   └── app-x
│       └── package.json
├── node_modules
├── package.json
├── packages
│   └── lib-a
│       ├── node_modules
│       │   └── react -> ../../../node_modules/.pnpm/[email protected]/node_modules/react
│       └── package.json
├── pnpm-lock.yaml
└── pnpm-workspace.yaml

8 directories, 5 files

npm -w はルートの node_modules に可能な限りフラットにインストールするのに対して、pnpm は各 package にリンクを貼る。 これにより速度やディスク効率を犠牲にせず依存に書かれていないパッケージが読めてしまうことを防ぐ。

$ cat package.json 
{
  ...
  "workspaces": ["apps/*", "packages/*"]
}

$ npm install -w apps/app-x [email protected]

added 2 packages, and audited 4 packages in 755ms

found 0 vulnerabilities

$ tree -L 2
.
├── apps
│   └── app-x
├── node_modules
│   ├── app-x -> ../apps/app-x
│   └── react
├── package-lock.json
├── package.json
├── packages
│   └── lib-a
└── pnpm-workspace.yaml

8 directories, 3 files

モノレポ内の他パッケージをインストールする際は workspace: を指定する。

$ npm install -w apps/app-x lib-a
$ pnpm add --filter app-x lib-a@workspace:

pnpm deploy ですべての依存の実体を含めた単体で動くパッケージを出力できる。injectWorkspacePackages を有効にするか –legacy を渡して実行する必要がある。

$ cat pnpm-workspace.yaml
...
injectWorkspacePackages: true

$ pnpm deploy --filter app-x --prod ./out

$ tree -a -L 3 out/
out/
├── main.js
├── node_modules
│   ├── .modules.yaml
│   ├── .pnpm
│   │   ├── lib-a@file++++home+godgo+pnpmtest+packages+lib-a
│   │   ├── lock.yaml
│   │   └── [email protected]
│   ├── .pnpm-workspace-state-v1.json
│   ├── lib-a -> .pnpm/lib-a@file++++home+godgo+pnpmtest+packages+lib-a/node_modules/lib-a
│   └── react -> .pnpm/[email protected]/node_modules/react
├── package.json
└── pnpm-lock.yaml