[Node.js] モジュール&npmのキホン 〜 JSおくのほそ道 #003

こんにちはほそ道です。
今回はnodeモジュールについて備忘録的に書き倒したいと思います。

[目次はこちら](http://qiita.com/setzz/items/ad9a9c99a4fda041a563)

# モジュール
Node.jsでは`require('モジュール')`という文法を使って機能拡張を行います。
ブラウザのJSでは`<script>タグ`を使って外部JSを読み込んで利用する事ができますが、
こちらは外部ファイルを読み込んで使用するには`require`を使用します。
組み込みでいくつかの **コアモジュール** と呼ばれるモジュール群が提供されています。
コアモジュールはインストール後はビルド済みのバイナリになっており、
いままでの記事で登場した`fs`や`events`はコアモジュールです。
ソースや一覧を確認する場合は[githubの./lib](https://github.com/joyent/node/tree/master/lib)を参照しましょう。

## その１：簡単なサンプルモジュールのロード

早速、自作のモジュールを作り、ロードしてみようと思います。
下記のような構成です。main.jsがnode_modules/my_math.jsをロードします。

```
.
├── main.js
└── node_modules
    └── my_math.js
```

```node_modules/my_math.js
console.log('module load!');
var myMath = {};
myMath.double = function(n) {
  return n * 2;
};
myMath.square = function(n) {
  return n * n;
};
module.exports = myMath;
```

```main.js
var myMath = require('my_math');
console.log(myMath.double(8));
console.log(myMath.square(8));
```

とりあえず実行してみましょう。

```bash:実行結果
$ node main.js
module load!
16
64
```

### ポイント

`require('my_math')`の結果、node_modules/my_math.jsに記述された`console.log('module load!');`が実行されます。
そして`module.exports`に代入されたオブジェクトが`require('my_math')`の戻り値として返ります。
ちなみに`module.exports`に代入せずに宣言した変数や関数は外部から呼び出す事は出来ません。
node_modulesはマジックディレクトリです。ここに宣言されたモジュールはパスの指定を省略して検索されます。細かいルールは下記にまとめます。

## その２：ディレクトリ形式のモジュールでもっとパッケージライクに

その１のファイルを指定するやり方だけではモジュールが複雑化してきた場合の整理が大変そうです。
そこで、構造的にモジュールを管理できるディレクトリ形式のモジュールを作ってみましょう。
下記のような構成です。
やはりmain.jsがmy_mathモジュールをロードします。

```
.
├── main.js
└── node_modules
    └── my_math
        ├── index.js
        ├── lib
        │   └── my_math.js
        └── package.json
```

```node_modules/my_math/package.json
{
  "name": "my_math",
  "version": "0.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
```

```node_modules/my_math/index.js
console.log("call index.js");
module.exports = require('./lib/my_math.js');
```

node_modules/my_math/lib/my_math.jsの中身はその１で登場したmy_math.jsと全く同じにします。

```node_modules/my_math/lib/my_math.js
console.log('module load!');
var myMath = {};
myMath.double = function(n) {
  return n * 2;
};
myMath.square = function(n) {
  return n * n;
};
module.exports = myMath;
```

main.jsの中身もその１で登場したmain.jsとまったく同じにします。

```main.js
var myMath = require('my_math');
console.log(myMath.double(8));
console.log(myMath.square(8));
```

```bash:実行結果
$ node main.js
call index.js
module load!
16
64
```

### ポイント

main.js内の`require('my_math');`がコールされると、
`node_modules`ディレクトリから`package.json`が検索されます。
さらに`package.json`のmainキーの値に指定された`index.js`が実行されます。
今回はお作法的にpackage.jsonには入り口としてのindex.jsを指定しています。
そこから外部のOSや環境に合わせたモジュールを返すようにすると環境スケールできる、という想定にしてみています。
それではモジュールのロードルールを下記にまとめます。

## モジュールのロードルール

モジュールのローディングにはいくつかのルールがあるのでまとめておきます。

- .jsファイルを指定する場合は拡張子の.jsはあっても無くても良い
- json形式のファイルをロードする事も出来、配列内にオブジェクトとして要素が格納される
- ロードするファイルはパスで指定する方法と、パスを指定しない自動探索方式がある
    - パス指定は絶対パスでも相対パスでも可
    - 自動探索方式は下記のようなルールがある
        - カレントディレクトリの`node_modules`ディレクトリから名前の合致するモジュールを探す
        - カレントディレクトリに`node_modules`が存在しなければ、親ディレクトリをたどって`node_modules`ディレクトリを探し、あればそこから名前の合致するモジュールを探す
        - `環境変数NODE_PATH`で指定されたディレクトリから名前の合致するモジュールを探す
        - `$HOME/.node_modules`ディレクトリから名前の合致するモジュールを探す
        - `$HOME/.node_libraries`ディレクトリから名前の合致するモジュールを探す
        - `/usr/local/lib`ディレクトリから名前の合致するモジュールを探す
- 指定モジュール名にはディレクトリを指定する事ができる
    - 指定ディレクトリの`package.json`のmainフィールドに記述されたモジュールをロードする
    - `package.json`が存在しなければ`index.js`ファイルをロードする
    - `index.js`ファイルが無ければ`index.node`ファイルをロードする。無ければ例外発生。

# npm
モジュールの基本は大体網羅出来ました。
こんどはモジュール機能に即して作られたパッケージ管理ツール **[npm](https://www.npmjs.org/)** を見て行きます。
npmはnode v0.6.3から標準パッケージとして提供されており、nodeインストール時に併せてインストールされます。
npmを使うと第三者が作成したサードパーティモジュールを使う事が出来ます。
npmのインストール方式には現在のプロジェクトのみで使用するローカルインストールと、PC全体で使用するグローバルインストールがあります。

## ローカルインストール

さっそくサードパーティモジュールをインストールしてみましょう。
今回は試しにasyncという非同期の制御構造を提供してくれるモジュールを入れてみます。
プロジェクトディレクトリに入って下記のコマンドを実行します。

```
$ npm install async
npm http GET https://registry.npmjs.org/async
npm http 200 https://registry.npmjs.org/async
async@0.8.0 node_modules/async
```

node_modules/asyncディレクトリにインストールされました。
構成を見てみましょう。

```
.
└── node_modules
    └── async
        ├── LICENSE
        ├── README.md
        ├── component.json
        ├── lib
        │   └── async.js
        └── package.json
```

`package.json`が存在しておりモジュールの仕組みに即していますね。
それではmain.jsを作成し、利用してみます。
配列のそれぞれの値を２乗して表示します。

```main.js
var async = require('async');
var list = [1,2,3,4,5]
var square = function(n, callback) { callback(null, n * n); };
var finish = function(err, result) { console.log(result); };
async.map(list, square, finish);
```

```text:実行結果
$ node main.js
[ 1, 4, 9, 16, 25 ]
```

うまく動いてくれました。

## グローバルインストール

PC全体からモジュールを使用する場合はグローバルインストールを使います。
やり方は-gオプションをつけます

```
sudo npm install -g async
Password:
npm http GET https://registry.npmjs.org/async
npm http 200 https://registry.npmjs.org/async
async@0.8.0 /usr/local/lib/node_modules/async
```

`/usr/local/lib/node_modules/`にインストールされました。
ここはモジュールのローディングルールでも触れた部分で、必ず参照されるディレクトリです。
ローカルモジュールがあればそちらが優先されますが、無ければ最終的にはココが参照されます。

## 依存モジュールの登録
```
npm install hoge --save-dev
```
package.jsonのdevDependenciesに依存関係のあるパッケージとそのバージョンが登録されます.

# 最後に
パッケージの仕組みはそんなに複雑なものではないですが
知らないと混乱してしまいがちなので基本的な情報をまとめてみました。
モジュールロードの仕組みはEcmaScript6の仕様にも組み込まれたようですし、
ブラウザもサーバも問わず、今後の為には押さえておいた方が良さそうです。
ブラウザに実装された場合も「どこからどこまでがこの部品」というカテゴリーの判別という意味では充分機能すると思います。

今回もお付き合いいただきありがとう御座いました。
[次回](http://qiita.com/setzz/items/1991567e56dea41d1021)はWebサービスを爆速で提供してみようと思います。