2011年3月、Node.jsのエコシステムにおいて極めて重要な転換点となるnpm 1.0のリリース候補版(RC)が発表されました。
今回のメジャーアップデートにおける最大の目的は、パッケージのインストール先となるディレクトリ構造を大幅に簡素化し、開発者が迷いなく依存関係を管理できるようにすることにあります。
これまでnpm 0.x系を利用してきたユーザーにとって、パッケージの管理手法がどのように変わるのか、そしてなぜその変更が必要だったのかを詳しく解説します。
npm 1.0における再設計の背景
npm 1.0の開発を牽引した主な動機は、パッケージのインストールディレクトリ構造をよりシンプルに整理したいという強い要望でした。
従来のnpm 0.x系では、プロジェクトごとに依存関係をローカルにインストールするためのbundleというコマンドが存在していましたが、これは本質的にはハックに近い実装であり、信頼性の面で課題を抱えていました。
また、パッケージの「有効化(activation)」や「無効化(deactivation)」といった概念も存在していましたが、これらは多くの開発者にとって混乱を招く要因となっていました。
npm 1.0ではこれらの複雑な仕組みを排し、より直感的で堅牢な「グローバル」と「ローカル」という2つの明確なパスに整理されました。
グローバルとローカル:2つのインストールパス
新しくなったnpm 1.0では、パッケージのインストール先が以下の2つの方法に集約されています。
グローバルインストール (-g)
コマンドラインで -g スイッチを使用する場合のインストール方法です。
- モジュールは
{prefix}/lib/node_modulesに配置されます。 - 実行ファイル(バイナリ)は
{prefix}/binに配置されます。 - マニュアルページ(man pages)が提供されている場合は、
{prefix}/share/manにインストールされます。
ここで、{prefix} は通常 /usr/local などのシステムディレクトリを指します。
ローカルインストール
オプションを指定せずに実行した場合のデフォルトの動作です。
- パッケージは現在の作業ディレクトリ内の
./node_modulesにインストールされます。 - 実行ファイルは
./node_modules/.bin/に配置されます。 - ローカルインストールの場合、マニュアルページはインストールされません。
どちらを選択すべきか:推奨される使い分けのルール
グローバル変数がプログラミングにおいて慎重に扱われるべきであるのと同様に、npmパッケージにおいてもグローバルインストールは必要な場合に限定することが推奨されます。
基本的には以下の「親指の法則(Rule of Thumb)」に従って判断してください。
| インストール対象 | 推奨される場所 | 理由 |
|---|---|---|
プログラム内で require() するもの | ローカル | プロジェクトごとの依存関係を独立させるため |
| シェルで直接コマンドとして実行するもの | グローバル | 実行ファイルが PATH に追加されるため |
つまり、アプリケーションのコードの一部として利用するライブラリ(例: redis, request)はローカルに、開発ツール(例: coffee-script, expressのジェネレータ)はグローバルにインストールするのが基本です。
両方の用途を持つパッケージの扱い
Coffee-scriptやExpressのように、コマンドラインインターフェース(CLI)とライブラリの両方の側面を持つパッケージの場合はどうすべきでしょうか。
npm 1.0では、以下の2つのアプローチが提示されています。
両方にインストールする:
これが最もシンプルで推奨される方法です。JavaScriptプログラムはサイズが小さいため、ディスク容量を気にする必要はほとんどありません。明示的でわかりやすい管理が可能になります。グローバルにインストールしてリンクする:
グローバルにインストールした後、npm link coffee-scriptコマンドを実行してシンボリックリンクを作成する方法です。複数のプロジェクトで同じライブラリを共有したい場合に便利ですが、環境によってはサポートされていない場合があります。
ディレクトリ探索の仕組みとテスト実行
npm 1.0のローカルインストールは、必ずしも現在の作業ディレクトリ(cwd)に限定されるわけではありません。
npmはGitのようにディレクトリ構造を遡って探索します。
例えば、以下のようなプロジェクト構造でコマンドを実行した場合を考えてみましょう。
// ~/projects/foo/node_modules が既に存在すると仮定します
// 開発者が ~/projects/foo/lib/subdir に移動して以下のコマンドを実行
// npm install redis
この場合、npmはカレントディレクトリから親ディレクトリへと遡り、node_modules フォルダを見つけるとそこをインストール先として認識します。
その結果、redis は ~/projects/foo/node_modules/redis にインストールされることになります。
テストランナーの実行
ローカルにインストールしたツールの実行についても、便利な仕組みが導入されました。
パッケージの package.json に記述された scripts.test などのライフサイクルスクリプトを実行する際、npmは自動的に ./node_modules/.bin を環境変数 PATH の先頭に追加します。
これにより、テストツールをグローバルにインストールしていなくても、以下のようにスムーズに実行できます。
// package.json の抜粋
{
"name": "my-project",
"dependencies": {
"vows": "0.5.x" // テストフレームワークをローカルに定義
},
"scripts": {
"test": "vows test/*.js" // 直接コマンド名を指定可能
}
}
この仕組みのおかげで、npm test を実行するだけで、ローカルにあるバイナリが優先的に使用されます。
まとめ
npm 1.0における「グローバル」と「ローカル」の分離は、Node.jsプロジェクトのポータビリティと予測可能性を大きく向上させる重要なステップです。
プロジェクト内で使うものはローカル、シェルで使うものはグローバルというシンプルな原則を導入することで、パッケージ管理の混乱は解消されるでしょう。
特に、ローカルな node_modules を優先する設計は、依存バージョンの競合を防ぎ、プロジェクトを「自己完結型」に保つのに役立ちます。
新しいnpmの設計思想を理解し、クリーンな開発環境を構築していきましょう。
