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

この使い分けには明確な理由があります。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 <名前> で実行できます。

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 やバンドラーに対してパッケージの読み込み方を伝える役割を持ちます。

  • 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 識別子。公開するものでは重要になります。
  • 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 programming languages illustration

Coddyでコードを学ぼう

始める