npm run devは、パッケージのインストールではなくnode_modules/.bin/へのパスを通すことでローカルにインストールしたバイナリを実行する仕組みです。- 直接コマンド名を打つと PATH が通っていないため失敗しますが、
npm run経由なら動きます。ツール名をpackage.jsonのscriptsに書くことで、開発ツールを変えてもコマンドを統一できます。 - 2014年頃に Grunt や Gulp より npm scripts のシンプルさが再評価され、現在は Turborepo のようなツールがさらに高度なタスク管理を担うようになっています。
1. npm run が実際にやっていること
npm run dev と打つたびに、「なぜパッケージ管理ツールでスクリプトを実行しているんだろう」と思ったことはないでしょうか。
答えから言うと、npm run はパッケージマネージャとしての機能ではなく、スクリプトランナーとして動いています。
package.json の scripts フィールドに、こんな記述があります。
{
"scripts": {
"dev": "vite"
}
}
Code language: JSON / JSON with Comments (json)
npm run dev を実行すると、npm は vite というコマンドを node_modules/.bin/ の中から探して実行します。
グローバルにインストールしていなくても動くのはこのためです。
では、なぜ直接 vite と打たないのか。node_modules/.bin/ はデフォルトでは PATH に含まれていないので、ターミナルから vite とだけ打っても「コマンドが見つからない」と怒られます。npm run 経由で実行するとこのパスが一時的に通り、ローカルインストールしたバイナリをそのまま呼び出せます。
もう一つの理由は抽象化です。
開発ツールを Vite から Webpack に変えたとき、チームメンバー全員のコマンドを変えずに済みます。package.json の "dev" の値を書き換えるだけでよく、npm run dev というインターフェイスは変わりません。
1.1. make と npmの違い
npm scripts はよく make の代替のように見えます。
Makefile に build: や test: を並べるのと、package.json の scripts に同じキーを並べるのは感覚的に近いです。
ただ、make は最初からビルドオーケストレーターとして設計されていて、ファイルの依存関係やタスクの前後関係を記述できるなど、本格的なビルドシステムです。
しかし、make は Windows との相性が悪く、Node.js がクロスプラットフォームを重視していた文脈では採用しにくかったという経緯もあります。
npm scripts は「このコマンドをこの名前で呼ぶ」程度のシンプルさで、それが Node.js エコシステムでは十分だったので広まりました。
2. npm の複数の顔
npm は一つのツールに複数の役割が混在しています。
- パッケージの取得と
node_modulesへの配置がパッケージマネージャとしての顔、 package.jsonやpackage-lock.jsonで依存バージョンを記録・解決するのが依存関係マネージャとしての顔、npm runがスクリプトランナーとしての顔です。
Unix の哲学からすると「一つのツールに一つのことをさせろ」なので、設計としては太りすぎです。
ただ Node.js エコシステムが npm を中心に育ったので、分離するコストより一か所に集めておく利便性が勝った結果こうなっています。
3. npm scripts が広まった経緯
npm は2010年に Isaac Schlueter が公開しました。
当初の目的は Node.js のパッケージをインストールして node_modules に置くことで、scripts フィールドは npm test や npm start といった決まった名前のフックが中心でした。
転換点は2013年前後です。
Grunt や Gulp といったタスクランナーが流行しましたが、設定ファイルが複雑になりがちで、プラグインへの依存も増えました。
また、2014年に Keith Cirkel が「Why I Left Gulp and Grunt for npm Scripts」という記事を書き、npm run だけで十分だと主張します。
これが広く読まれて、シンプルに scripts を使う流れが加速しました。