はじめに
仕事で初めて pnpm を利用したので、理解のために自分でもやってみました。
Qiita用にJavaのエコシステム(MavenやGradle)と比較しながら理解してみました。
フロントエンドのパッケージ管理の全体像
| 概念・役割 | Javaの世界 | フロントエンドの世界 |
|---|---|---|
| パッケージ管理ツール | Maven, Gradle | npm, Yarn, pnpm |
| 設定ファイル |
pom.xml, build.gradle
|
package.json |
| 依存関係の固定 | なし(または各種プラグイン等) |
package-lock.json, pnpm-lock.yaml
|
| ライブラリの実体保存先 |
~/.m2/repository(グローバル) |
node_modules/(プロジェクト毎) |
| 中央リポジトリ | Maven Central | npm registry |
AIからの補足
- package.json: プロジェクトの心臓部。Mavenの pom.xml のように、依存関係やビルドスクリプトを記述します。Javaでいう test のように、本番用(dependencies)と開発用(devDependencies)を分けて書きます
- node_modules: ライブラリの実体が保存されるフォルダ。Javaの .m2 と違い、プロジェクトごとに作成されます
- Lockファイル: チーム開発で環境差異を防ぐための、依存ツリーの正確なバージョン記録ファイル
- 現在使われている主なツールは「npm」「Yarn」「pnpm」の3種類です
「pnpm」がnpmの課題を解決
npmの課題
- ディスク容量の圧迫: プロジェクトごとに node_modules へライブラリを丸ごとコピーするため、同じReactのファイルがPC内に大量に複製され、ストレージを圧迫する
- ファントム依存関係: パスを短くするため依存関係をフラットに配置しており、package.json に明記していない孫依存ライブラリまで誤ってインポートできてしまう
pnpmの解決策(グローバルストアとシンボリックリンク)
pnpmは、PC上の共通領域(グローバルストア)にライブラリの実体を1つだけ保存し、各プロジェクトには「ハードリンク」を張ります。
これは、JavaのMavenが ~/.m2/repository にJarファイルをキャッシュし、各プロジェクトからそれを参照する仕組みと非常に似ています。これにより、ディスク容量を劇的に節約できます。
また、シンボリックリンクを使って本来の依存ツリーを厳格に構築するため、隠れたバグ(ファントム依存関係)も防ぎます。
パッケージマネージャーを管理する「Corepack」
Corepackは、「パッケージ管理ツール(pnpmやYarn)自体のバージョン」をプロジェクトごとに固定・管理するためのツールです。
Javaエンジニアへの一番わかりやすい例えは、「Gradle Wrapper (gradlew)」や「Maven Wrapper (mvnw)」のフロントエンド版です。
チーム開発で「Aさんはpnpm v8、Bさんはv9」のようにバージョンがバラバラだと、Lockファイルのコンフリクトが頻発します。
Corepackを使うと、package.json に以下のようにバージョンを指定できます。
{
"name": "my-project",
"packageManager": "pnpm@9.5.0"
}
【重要】Node.js v25以降の最新事情
かつてCorepackはNode.jsに標準同梱されていましたが、Node.js v25以降では本体から外されました(アンバンドル化)。 そのため、最新環境では最初に npm install -g corepack で手動インストールする必要があります。
pnpmでプロジェクトの初期化(ハンズオン)
pnpm + Corepack + Vite を組み合わせて初期化
Step 1: Corepackの有効化
# Node.js v25以降の場合は事前に npm install -g corepack を実行
corepack enable
Step 2: Viteを使ったプロジェクトの作成
Javaでいう 'mvn archetype:generate' で雛形作成
pnpm create vite@latest my-frontend-app
# (対話形式でReact/Vueなどのフレームワークを選択)
Step 3: プロジェクトへの移動とバージョンの固定(Corepack)
作成したフォルダに移動して、このプロジェクトで使用するpnpmを固定
cd my-frontend-app
# 最新のpnpmバージョンに固定(package.jsonに自動追記される)
corepack use pnpm@latest
Step 4: 依存関係のインストールと起動
# ライブラリのインストール(pnpmの恩恵で超高速!)
pnpm install
# 開発用ローカルサーバーの起動
pnpm dev
起動後、ターミナルに表示される http://localhost:5173/ へアクセスすれば完了。
ここで叩いた 'pnpm dev' などのコマンドは 'package.json' の "scripts" に定義されていて、Mavenの 'mvn spring-boot:run' やGradleのタスクのように機能します。
おわりに
私のようなJava経験者向けに無理やり、JavaのMavenなどと比較して記載してみましたが、一番しっくりきたのはAIに出してもらった下記の 「npmとのコマンド比較」でした。
補足:AI
npmとのコマンド比較
| 目的・利用シーン | npm (従来) | pnpm (モダン) |
|---|---|---|
| プロジェクトの全パッケージをインストール |
npm install (または npm i) |
pnpm install (または pnpm i) |
| 本番用パッケージを追加 | npm install <pkg> |
pnpm add <pkg> |
| 開発用パッケージを追加 (※テストツール等) | npm install -D <pkg> |
pnpm add -D <pkg> |
| パッケージを削除 | npm uninstall <pkg> |
pnpm remove <pkg> (または pnpm rm) |
| パッケージを更新 | npm update <pkg> |
pnpm update <pkg> (または pnpm up) |
| スクリプトの実行 (例: dev, build) | npm run dev |
pnpm dev (※ run は省略可能) |
| 使い捨てコマンドの実行 (※1) | npx <コマンド> |
pnpm dlx <コマンド> |
| ローカルコマンドの実行 (※1) |
npx <コマンド> または npm exec
|
pnpm exec <コマンド> |
Javaエンジニア向けの重要なポイント(コマンドの違い)
install ではなく add を使う
npmではパッケージを追加する際も npm install <パッケージ名> を使っていましたが、pnpmでは pnpm add <パッケージ名> を使います。
これは、Java(Maven)で言うところの「pom.xml に を追加する(add)」作業と、「プロジェクト全体の依存関係をダウンロードして配置する(install)」作業が、意味合いとして明確に分離されたためです。
魔法のコマンド npx の代替(pnpm dlx と pnpm exec)
ネット上のフロントエンドの記事を読んでいると、頻繁に npx というコマンドが登場します(例:npx create-react-app など)。これは「パッケージをインストールせずに、一時的にインターネットからダウンロードして一回だけ実行する」という便利なコマンドです。
pnpm環境でこれに相当することを行いたい場合は、用途に応じて以下のように使い分けます。
- pnpm dlx: 手元のプロジェクトにないパッケージを、一時的にダウンロードして1回だけ実行したい場合(ダウンロードして実行(download and execute)の略)
- pnpm exec: すでにプロジェクト内(node_modules内)にインストールされているパッケージのコマンドを実行したい場合
参考(感謝)
- AIに聞きながら
