pnpm 10リリース - 高速・省容量パッケージマネージャーの進化

2026.04.10

公式ドキュメント

pnpm は Node.js エコシステムで利用される高速・省容量なパッケージマネージャーです。シンボリックリンクと content-addressable store を用いた独自のアーキテクチャにより、npm や yarn に対して高速なインストールとディスク効率を提供しています。pnpm 10 はセキュリティとモノレポ運用の改善にフォーカスしたメジャーリリースです。

pnpm 10の概要

アーキテクチャ

flowchart TB
 subgraph Store["Global Store (~/.pnpm-store)"]
 S1["content-addressable<br/>hash keyed blobs"]
 end

 subgraph Project["Project node_modules"]
 P1[".pnpm/ virtual store"]
 P2["symlinked packages"]
 end

 S1 -->|hardlink| P1
 P1 -->|symlink| P2

 subgraph Features["pnpm 10 Features"]
 F1["secure lifecycle scripts"]
 F2["improved lockfile v9"]
 F3["workspace protocol"]
 F4["catalogs"]
 end

背景

Node.js エコシステムにおける supply chain 攻撃の増加により、postinstall をはじめとするライフサイクルスクリプトの自動実行が長年問題視されてきました。pnpm 10 はこの課題に真正面から取り組み、デフォルトで信頼されたパッケージ以外のスクリプト実行をブロックします。

主要な新機能

1. Lifecycle scripts がデフォルトで無効

pnpm 10 からは、インストール時に任意のパッケージの preinstall / install / postinstall スクリプトがデフォルトで実行されません。明示的に許可したパッケージだけが実行されます。

// package.json
{
 "pnpm": {
 "onlyBuiltDependencies": [
 "esbuild",
 "sharp",
 "@prisma/client"
 ]
 }
}

許可されていないパッケージのスクリプトは警告とともにスキップされ、一覧を確認できます。

pnpm approve-builds
# インタラクティブにビルドを許可するパッケージを選択

2. Lockfile のフォーマット改善

pnpm-lock.yaml v9 は差分の少なさと決定論的な並び替えが強化され、レビュー時の diff ノイズが削減されました。

# pnpm-lock.yaml (v9)
lockfileVersion: '9.0'

settings:
 autoInstallPeers: true
 excludeLinksFromLockfile: false

importers:
 .:
 dependencies:
 react:
 specifier: ^19.0.0
 version: 19.0.0
 react-dom:
 specifier: ^19.0.0
 version: 19.0.0(react@19.0.0)

3. Catalogs: 依存バージョンの一元管理

モノレポで依存バージョンを統一するための catalog 機能が安定化しました。

# pnpm-workspace.yaml
packages:
 - 'apps/*'
 - 'packages/*'

catalog:
 react: ^19.0.0
 react-dom: ^19.0.0
 typescript: ^5.6.0

catalogs:
 legacy:
 react: ^18.3.0
 react-dom: ^18.3.0
// apps/web/package.json
{
 "dependencies": {
 "react": "catalog:",
 "react-dom": "catalog:"
 }
}
// apps/legacy/package.json
{
 "dependencies": {
 "react": "catalog:legacy"
 }
}

4. Workspace protocol の拡張

{
 "dependencies": {
 "@acme/ui": "workspace:*",
 "@acme/utils": "workspace:^",
 "@acme/types": "workspace:~"
 }
}
  • workspace:* : 常に現在のワークスペース内のバージョンを利用
  • workspace:^ : publish 時に ^x.y.z に変換
  • workspace:~ : publish 時に ~x.y.z に変換

5. pnpm dlx の高速化

# npx 相当のワンショット実行
pnpm dlx create-vite@latest my-app --template react-ts

# キャッシュ済みの場合は即起動
pnpm dlx tsx script.ts

実践サンプル

モノレポのセットアップ

mkdir my-monorepo && cd my-monorepo
pnpm init

# workspace 定義
cat > pnpm-workspace.yaml <<'EOF'
packages:
 - 'apps/*'
 - 'packages/*'

catalog:
 react: ^19.0.0
 react-dom: ^19.0.0
 typescript: ^5.6.0
 vitest: ^3.0.0
EOF

mkdir -p apps/web packages/ui packages/utils
// apps/web/package.json
{
 "name": "@acme/web",
 "version": "0.0.0",
 "private": true,
 "scripts": {
 "dev": "vite",
 "build": "vite build",
 "test": "vitest"
 },
 "dependencies": {
 "@acme/ui": "workspace:*",
 "@acme/utils": "workspace:*",
 "react": "catalog:",
 "react-dom": "catalog:"
 },
 "devDependencies": {
 "typescript": "catalog:",
 "vitest": "catalog:"
 }
}
// packages/ui/package.json
{
 "name": "@acme/ui",
 "version": "0.0.1",
 "main": "./src/index.ts",
 "types": "./src/index.ts",
 "peerDependencies": {
 "react": "catalog:"
 }
}
// packages/ui/src/index.ts
import { type ReactNode } from 'react';

export interface ButtonProps {
 children: ReactNode;
 onClick?: () => void;
 variant?: 'primary' | 'secondary';
}

export function Button({ children, onClick, variant = 'primary' }: ButtonProps) {
 return (
 <button
 className={`btn btn-${variant}`}
 onClick={onClick}
 >
 {children}
 </button>
 );
}

フィルタリングによるビルド

# 特定のパッケージとその依存関係だけをビルド
pnpm --filter @acme/web... build

# 変更されたパッケージだけをテスト
pnpm --filter '...[origin/main]' test

# 依存するパッケージを含めてテスト
pnpm --filter '@acme/ui...' test

CI 設定例

# .github/workflows/ci.yml
name: CI

on:
 push:
 branches: [main]
 pull_request:

jobs:
 test:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v4
 with:
 fetch-depth: 0

 - uses: pnpm/action-setup@v4
 with:
 version: 10

 - uses: actions/setup-node@v4
 with:
 node-version: 22
 cache: 'pnpm'

 - name: Install dependencies
 run: pnpm install --frozen-lockfile

 - name: Approve trusted builds
 run: pnpm approve-builds --all

 - name: Type check
 run: pnpm -r run typecheck

 - name: Test changed packages
 run: pnpm --filter '...[origin/main]' test

Docker での利用

# Dockerfile
FROM node:22-alpine AS base
RUN corepack enable && corepack prepare pnpm@10 --activate

FROM base AS deps
WORKDIR /app
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY apps/web/package.json ./apps/web/
COPY packages/ui/package.json ./packages/ui/
RUN pnpm install --frozen-lockfile

FROM base AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm --filter @acme/web... build

FROM base AS runner
WORKDIR /app
COPY --from=build /app/apps/web/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]

比較表

パッケージマネージャー比較

項目pnpm 10npm 10yarn 4 (berry)
インストール方式hardlink + symlinkフラットコピーフラット / PnP
ディスク効率非常に高い
モノレポ対応ネイティブworkspacesworkspaces
Lockfilepnpm-lock.yamlpackage-lock.jsonyarn.lock
デフォルト postinstallブロック実行実行
Catalogありなしなし

インストール速度の傾向

pnpm は store からの hardlink を多用するため、特に CI でのキャッシュヒット時に高速に動作します。実測値はプロジェクトや環境によって変わるため、自身のプロジェクトで測定することをおすすめします。

ベストプラクティス

1. onlyBuiltDependencies の明示

実践メモ: onlyBuiltDependenciesにはesbuild/sharp/prismaなどネイティブバイナリが必要なもののみを許可し、それ以外はブロックしたままにしましょう。

必要なネイティブ依存のみに限定することで、未知のスクリプト実行を防ぎます。

{
 "pnpm": {
 "onlyBuiltDependencies": ["esbuild", "sharp"],
 "neverBuiltDependencies": ["core-js"]
 }
}

2. Peer dependencies の自動インストール

# .npmrc
auto-install-peers=true
strict-peer-dependencies=false

3. フィルタを活用した差分ビルド

# ルートpackage.jsonに定義
{
 "scripts": {
 "build": "pnpm -r --filter='...[origin/main]' build",
 "test": "pnpm -r --filter='...[origin/main]' test"
 }
}

4. Workspace 間の依存を workspace protocol に揃える

外部バージョンが紛れ込むと monorepo 内で重複が発生します。workspace:* を徹底しましょう。

5. --frozen-lockfile を CI で必ず使う

lockfile のずれによるビルド失敗を早期発見できます。

注意点

注意: pnpm 10ではデフォルトでpostinstallがブロックされるため、初回セットアップ時にapprove-buildsを忘れないようにしましょう

  • 既存の yarn / npm プロジェクトから移行する場合、node_modules の構造が異なるため、ツールによってはパッチが必要です。特に __dirname ベースで兄弟パッケージを直接参照するコードは動かないことがあります。
  • pnpm patch を用いた依存パッチはリポジトリ内の patches/ に保存されるため、Git 管理が必須です。
  • shamefully-hoist=true はあくまで互換性のための設定であり、長期的には避けるべきです。
  • pnpm 10 ではデフォルトで postinstall がブロックされるため、approve-builds を初回に忘れないようにします。
  • corepack を使ってバージョンを固定すると開発者間の齟齬を防げます。

導入手順

新規プロジェクト

corepack enable
corepack prepare pnpm@10 --activate
pnpm init
pnpm add react react-dom
pnpm add -D typescript vitest

既存プロジェクトからの移行

# npm / yarn から
rm -rf node_modules package-lock.json yarn.lock
corepack prepare pnpm@10 --activate
pnpm import # 既存 lockfile から pnpm-lock.yaml を生成
pnpm install

Corepack による固定

// package.json
{
 "packageManager": "pnpm@10.0.0",
 "engines": {
 "node": ">=20",
 "pnpm": ">=10"
 }
}

パフォーマンス

pnpm は content-addressable store を使っているため、同じパッケージバージョンはマシン内で 1 度しかダウンロード・展開されません。これはモノレポや複数のプロジェクトを並行管理する開発者にとって、ディスク占有量を大きく削減します。またインストールは並列化されており、特に CI でのキャッシュを組み合わせるとインストール時間が安定します。

計測のヒント

pnpm install --reporter=ndjson | tee install.log
pnpm store status
du -sh ~/.local/share/pnpm/store

FAQ

Q: npm / yarn と混在できますか? A: 混在は非推奨です。packageManager フィールドと only-allow を使って pnpm のみを強制しましょう。

{
 "scripts": {
 "preinstall": "npx only-allow pnpm"
 }
}

Q: Node.js のバージョンはどれを使うべきですか? A: Node.js 20 以上の LTS を推奨します。

Q: postinstall を許可するパッケージをどう選べばよいですか? A: ネイティブバイナリが必要なもの (esbuild, sharp, prisma, puppeteer など) のみを許可し、それ以外はブロックしたままにします。

Q: shamefully-hoist は使うべき? A: 互換性のためにやむを得ない場合のみ使用してください。可能なら依存元のパッケージに修正を送る方が健全です。

Q: pnpm dlxpnpm exec の違いは? A: dlx はパッケージを一時的に取得して実行、exec はローカルの node_modules/.bin を実行します。

まとめ

pnpm 10 は「安全で速く、モノレポに強い」パッケージマネージャーとして成熟しました。特にデフォルトで lifecycle scripts をブロックする変更は、supply chain セキュリティ強化に大きく貢献します。モノレポにおいては catalogs と workspace protocol の組み合わせが非常に強力で、依存バージョンの一元管理と高速な差分ビルドを両立できます。

Node.js エコシステムで新しくプロジェクトを始める、あるいはモノレポの運用で npm/yarn に限界を感じている場合、pnpm 10 への移行を強く推奨します。

参考リソース

← 一覧に戻る
PR
PR
PR
PR