Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

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

目次はこちら

モジュール

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

その1:簡単なサンプルモジュールのロード

早速、自作のモジュールを作り、ロードしてみようと思います。
下記のような構成です。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));

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

実行結果
$ 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はマジックディレクトリです。ここに宣言されたモジュールはパスの指定を省略して検索されます。細かいルールは下記にまとめます。

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

その1のファイルを指定するやり方だけではモジュールが複雑化してきた場合の整理が大変そうです。
そこで、構造的にモジュールを管理できるディレクトリ形式のモジュールを作ってみましょう。
下記のような構成です。
やはり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の中身はその1で登場した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の中身もその1で登場したmain.jsとまったく同じにします。

main.js
var myMath = require('my_math');
console.log(myMath.double(8));
console.log(myMath.square(8));
実行結果
$ 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 を見て行きます。
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を作成し、利用してみます。
配列のそれぞれの値を2乗して表示します。

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);
実行結果
$ 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の仕様にも組み込まれたようですし、
ブラウザもサーバも問わず、今後の為には押さえておいた方が良さそうです。
ブラウザに実装された場合も「どこからどこまでがこの部品」というカテゴリーの判別という意味では充分機能すると思います。

今回もお付き合いいただきありがとう御座いました。
次回はWebサービスを爆速で提供してみようと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした