Node プロジェクトのマニフェストファイル
Node.js プロジェクトのルートには、必ず package.json が置かれています。これはプロジェクトの名前・バージョン・依存パッケージ・実行できるコマンドなどを記述したただの JSON ファイルで、npm が何かしら動くときに毎回読みに行くファイルです。これを消してしまうと、npm はそのプロジェクトが何者なのかまったく分からなくなります。
いちばん手っ取り早く package.json を作る方法が npm init コマンドです。
npm init -y
-y フラグを付けると対話形式の入力がスキップされて、デフォルト値で一気に作成されます。できあがるのはだいたいこんな感じのファイルです。
{
"name": "my-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
これが最低限のひな形です。ただ、このままだとほとんどのフィールドは単独では大した役割を持ちません。依存パッケージやスクリプトを追加していくうちに、初めて意味を持ってくるという感じです。
dependencies と devDependencies の違い
package.json の中でも一番働くのが、dependencies と devDependencies の2つのフィールドです。どちらもパッケージ名とバージョン範囲を対応付けたマップになっています。
この使い分けには明確な理由があります。dependencies はアプリの実行時に必要なパッケージ、devDependencies は開発中だけ使うパッケージ(テストランナー、Linter、ビルドツール、型チェッカーなど)です。誰かがあなたのパッケージを依存関係としてインストールしたとき、npm は dependencies だけをダウンロードし、devDependencies はスキップします。
この2つのフィールドは npm が自動で更新してくれます。npm install express を実行すれば dependencies に1行追加され、npm install --save-dev vitest なら devDependencies に追加されます。手書きで編集する機会はほとんどありません。
バージョン範囲の指定:^、~、固定バージョンの違い
^4.19.0 のようなバージョン表記は、実は「ぴったりそのバージョン」ではなく範囲指定です。npm は semver(セマンティックバージョニング)に従っており、バージョンは MAJOR.MINOR.PATCH(メジャー.マイナー.パッチ)の3つに分かれています。
- MAJOR:後方互換性のない変更が入ったときに上がる
- MINOR:互換性を保ったまま機能追加されたときに上がる
- PATCH:バグ修正が入ったときに上がる
よく見かける2つの演算子はこちらです。
"express": "^4.19.0" // >= 4.19.0 かつ < 5.0.0 (4.19.0 以上の任意の 4.x.x)
"express": "~4.19.0" // >= 4.19.0 かつ < 4.20.0 (4.19.0 以上の任意の 4.19.x)
"express": "4.19.0" // 4.19.0 のみ
^ は npm がパッケージをインストールするときに付けるデフォルトの記号で、マイナーバージョンとパッチのアップデートまでは互換性を保つ前提で許容します。~ はもう少し保守的で、パッチアップデートのみを許可。バージョン番号だけを書いた場合は、そのバージョンに完全固定されます。
ただ注意したいのは、「今インストールしたバージョン」と「レンジが許可するバージョン」は必ずしも一致しないという点です。たとえば今日 express@4.19.0 をインストールして、1か月後にチームメンバーが同じプロジェクトをインストールすると、^4.19.0 は 4.19.5 に解決されるかもしれません。ここで登場するのが package-lock.json です。実際に解決された 厳密な バージョンを記録してくれるので、誰がインストールしても同じ依存ツリーになります。必ずコミットしましょう。
package.json scripts:プロジェクトのコマンド集約地
scripts フィールドは、よく使うコマンドにショートカットを定義するための場所です。ここに書いたものは npm run <名前> で実行できます。
scripts を扱う上で押さえておきたいポイントをいくつか挙げます。
npm start、npm testなど一部の名前はrunを省略して実行できます。それ以外はnpm run <name>の形で呼び出す必要があります。- scripts は
node_modules/.binがPATHに通った状態のシェルで実行されるため、インストール済みパッケージのバイナリを直接呼べます。vitestをグローバルに入れていなくても"test": "vitest"で問題なく動きます。 - スクリプトはチェーンできます。例えば
"build": "npm run lint && npm run compile"のように書けばOKです。「順番に実行して、失敗したら止める」には&&を使います。 pre<name>とpost<name>のスクリプトは自動で走ります。prebuildを定義しておけば、特別な設定なしにbuildの前に実行されます。
scripts はプロジェクトの「コマンドの入り口」です。package.json がきちんと整っていれば、新しくジョインしたメンバーもリポジトリをクローンして npm install したあと、wiki を読まなくても npm run dev や npm test ですぐに動かせます。
エントリーポイント: main, exports, type
これらのフィールドは、Node やバンドラーに対してパッケージの読み込み方を伝える役割を持ちます。
typeは.jsファイルをどう解釈するかを決めるフィールドです。"module"にすれば ESM(import/export)として扱われ、省略するか"commonjs"を指定すると CommonJS(require)になります。詳しくは CommonJS と ESM の解説ドキュメントを参照してください。mainは昔ながらのエントリーポイントで、require("my-lib")したときに読み込まれるファイルを指します。古いツールでは今も有効です。exportsはその現代版で、より厳密な指定ができます。利用者がどのファイルを、どのサブパスから import できるかをピンポイントで定義でき、ここに載っていないファイルは import しようとしても失敗します。これはバグではなく仕様で、公開 API を自分でコントロールできるということです。
ライブラリを公開するのではなく普通にアプリを作るだけなら、この中で気にすべきなのは type くらいです。
実際の package.json の例
ここまでの内容をまとめると、小さめの Node アプリの package.json は実際こんな感じになります。
engines.node にも注目してください。これはあくまでアドバイザリ(推奨)で、ユーザーの Node バージョンが一致しないと npm が警告を出し、engine-strict が有効ならエラーになります。公開するパッケージには設定しておくのが行儀の良いやり方です。
知っておきたいフィールド
他にもよく目にするフィールドをいくつか紹介します。
private: true— うっかり npm に publish してしまうのを防ぎます。公開しないプロジェクトには必ず付けておきましょう。license—"MIT"や"ISC"のような SPDX 識別子。公開するものでは重要になります。repository、bugs、homepage— npm レジストリのページに表示されます。bin— パッケージが CLI を提供する場合、コマンド名とスクリプトファイルの対応をここで書きます。インストール後、それらのコマンドがそのまま実行できるようになります。workspaces— monorepo 用。サブディレクトリをリンクされたパッケージとして扱うよう npm に伝えます。
全部埋める必要はありません。自分のプロジェクトに必要なものだけでOKです。
よくあるハマりどころ
みんながつまずきやすいポイントをまとめておきます。
node_modulesをコミットしてしまう。やめましょう。.gitignoreに追加します。package.jsonとpackage-lock.jsonさえあれば、誰でもnpm installで同じ環境を再現できます。package-lock.jsonをコミットしない。こちらは必ずコミットしてください。ロックファイルがないと、semver の範囲指定が時間とともに別バージョンに解決されうるため、「自分の環境では動くのに…」という事態が現実に起こります。- ランタイムの依存を
devDependenciesに入れてしまう。ローカルでは dev 依存もインストールされるので動きますが、本番では dev 依存がスキップされるので壊れます。出荷するコードが使っているなら、それはdependencies側です。 - バージョンを手で書き換えたまま再インストールしない。
package.jsonのバージョンをいじったらnpm installを実行してください。そうしないとnode_modulesとロックファイルがズレていきます。
次は Node ランタイム
package.json は Node に対して「このプロジェクトは 何なのか」を伝えるものです。一方、「どう動かすか」を決めているのは Node ランタイム本体 — モジュール解決、組み込みモジュール、グローバル、裏で回っているイベントループなどです。次のページではそこを見ていきます。
よくある質問
package.jsonは何のためのファイル?
Node.jsプロジェクトのマニフェストファイルです。プロジェクト名やバージョン、依存パッケージ、npm runで叩けるスクリプト、エントリポイントやモジュール形式といったメタ情報を記録します。npm installはこのファイルを読んで何をダウンロードするかを決めています。
dependenciesとdevDependenciesは何が違う?
dependenciesは実行時に必要なパッケージで、expressやreactなどが該当します。一方のdevDependenciesは開発・ビルド時にしか使わないもの——テストランナー、バンドラー、Linterなどです。自分のパッケージが他人のプロジェクトに依存としてインストールされたとき、npmはdevDependenciesをスキップしてくれます。
バージョン指定の^や~ってどういう意味?
semverのレンジ演算子です。^1.2.3は1.2.3以上の1.x.x(メジャーが同じ範囲)を許可。~1.2.3はもう少し厳しく、1.2.3以上の1.2.x(マイナーまで固定)だけOK。プレフィックスなしの1.2.3は完全固定です。実際に解決されたバージョンはpackage-lock.jsonに記録されるので、インストール結果の再現性が保たれます。
package.jsonはどうやって作る?
空のディレクトリでnpm initを実行して対話形式で答えるか、npm init -yでデフォルト値のまま一発生成できます。ただのJSONなので手書きでも全然OK。必須フィールドはnameとversionの2つだけです。