Menu
日本語

package.jsonとは?scripts・依存関係・semverを解説

package.jsonの中身を一通り整理。押さえるべきフィールド、scriptsの動き、^や~のsemver指定でnpmが入れるバージョンがどう決まるのかをまとめます。

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 の中でも一番働くのが、dependenciesdevDependencies の2つのフィールドです。どちらもパッケージ名とバージョン範囲を対応付けたマップになっています。

index.js
Output
Click Run to see the output here.

この使い分けには明確な理由があります。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.04.19.5 に解決されるかもしれません。ここで登場するのが package-lock.json です。実際に解決された 厳密な バージョンを記録してくれるので、誰がインストールしても同じ依存ツリーになります。必ずコミットしましょう。

package.json scripts:プロジェクトのコマンド集約地

scripts フィールドは、よく使うコマンドにショートカットを定義するための場所です。ここに書いたものは npm run <名前> で実行できます。

index.js
Output
Click Run to see the output here.

scripts を扱う上で押さえておきたいポイントをいくつか挙げます。

  • npm startnpm test など一部の名前は run を省略して実行できます。それ以外は npm run <name> の形で呼び出す必要があります。
  • scripts は node_modules/.binPATH に通った状態のシェルで実行されるため、インストール済みパッケージのバイナリを直接呼べます。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 devnpm test ですぐに動かせます。

エントリーポイント: main, exports, type

これらのフィールドは、Node やバンドラーに対してパッケージの読み込み方を伝える役割を持ちます。

index.js
Output
Click Run to see the output here.
  • 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 は実際こんな感じになります。

index.js
Output
Click Run to see the output here.

engines.node にも注目してください。これはあくまでアドバイザリ(推奨)で、ユーザーの Node バージョンが一致しないと npm が警告を出し、engine-strict が有効ならエラーになります。公開するパッケージには設定しておくのが行儀の良いやり方です。

知っておきたいフィールド

他にもよく目にするフィールドをいくつか紹介します。

  • private: true — うっかり npm に publish してしまうのを防ぎます。公開しないプロジェクトには必ず付けておきましょう。
  • license"MIT""ISC" のような SPDX 識別子。公開するものでは重要になります。
  • repositorybugshomepage — npm レジストリのページに表示されます。
  • bin — パッケージが CLI を提供する場合、コマンド名とスクリプトファイルの対応をここで書きます。インストール後、それらのコマンドがそのまま実行できるようになります。
  • workspaces — monorepo 用。サブディレクトリをリンクされたパッケージとして扱うよう npm に伝えます。

全部埋める必要はありません。自分のプロジェクトに必要なものだけでOKです。

よくあるハマりどころ

みんながつまずきやすいポイントをまとめておきます。

  • node_modules をコミットしてしまう。やめましょう。.gitignore に追加します。package.jsonpackage-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は実行時に必要なパッケージで、expressreactなどが該当します。一方のdevDependenciesは開発・ビルド時にしか使わないもの——テストランナー、バンドラー、Linterなどです。自分のパッケージが他人のプロジェクトに依存としてインストールされたとき、npmはdevDependenciesをスキップしてくれます。

バージョン指定の^や~ってどういう意味?

semverのレンジ演算子です。^1.2.31.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。必須フィールドはnameversionの2つだけです。

Coddyでコードを学ぼう

始める