[Node.js] モジュールの読み込み(require)・公開(exports)

Node.js学習中。事前知識なしに require とか module.exports だとか出てくるとなんだこれはと思ったが仕組みは単純なもの。

require と exports のまとめ

  • require('ファイル名') でモジュールを実行
    • 実行中のjsファイルから見て node_modules/ファイル名.js を読み込む事が多い。 ファイルパスの解決の手順はやや複雑なため後述。
    • require で同じファイルを複数回読み込んでも2回目は実行されない。 (2回目以降は module.exports を返すだけになる。)
  • require の戻り値は modules.exports に代入したオブジェクト。
    • modules.exports には初期値として空のオブジェクトが入っているので modules.exports.foo = "bar" のような記述でもOK
    • exports というエイリアスが存在
      • this.exports === modules.exports は true
      • exports には modules.exports と同じオブジェクトが入っているだけに過ぎないので… 例えば exports = ""; とすると this.exports と modules.exports は無関係となる。 参考: http://d.hatena.ne.jp/jovi0608/20111226/1324879536
  • モジュール毎にそれぞれ独立したスコープを持つ
    • モジュール間のやり取りは module.exports のオブジェクトを介して行う。
  • 大域変数は global はどのモジュールからもアクセス可能

モジュールの読み込みのカンタンな例

main.js と node_modules/sub.js があるとする。

1
2
3
4
5
6
7
8
9
10
11
/// node_modules/sub.js

// オブジェクトを外部公開するときは
// module.exports にオブジェクトを設定
module.exports = 999;

// fooの値はグローバルスコープ
foo = 123;

// barの値はこのファイル内のみで有効
var bar = 456;
1
2
3
4
5
6
7
8
9
/// main.js

// require で sub.js で module.exports に
// 設定されたオブジェクトを取得できる
var sub = require('sub');
console.log(sub); // 999
console.log(foo); // 123
console.log(global.foo); // 123
console.log(typeof bar); // undefined

$ node main で実行するとbarだけは main.js からアクセスできないため undefined となる。

モジュールのパス解決の順番

ディレクトリ区切り付きのパスかどうかでパス解決手順は異なる。

ディレクトリ区切り付きのパスの場合

ディレクトリ区切り付きのパスとは たとえば requie("./../sub")require("/sub") のようなパス。 拡張子は省略可能。

この場合は指定したパスにファイルがなければそこで探索終了して例外を送出する。

ディレクトリ区切り付きのパスではない場合

requie("sub") のようにファイル名だけを指定した場合は coreモジュール => module.paths => NODE_PATH の順番で探索。

  1. coreモジュールの探索。 ※どのようなモジュールがあるかは module.exports._buildinLibs で確認可能
  2. module.paths 配列に定義されたディレクトリを順番に探索。 デフォルトでは “./node_modules/“ フォルダ内を探索し、 見つかるまで “親フォルダ/node_modules/“ フォルダへと探索。 “/node_modules/“ (ルートディレクトリ)の探索で終了。 ※ただし module.paths は書き換え可能なので 空配列をセットすると node_modules の探索は行なわれなくなる。
  3. 環境変数 NODE_PATH がセットされている場合はそのディレクトリを探索 NODE_PATH に複数の値を設定する場合は”:”(コロン)で区切る。
  4. 例外送出

モジュールの複数回読み込み

requireで読み込んだモジュールは一度しか実行されず、 2回目以降は modules.exports を返すだけとなる。

1
2
/// node_modules/sub.js
console.log("foo");
1
2
3
4
5
6
7
8
/// main.js
var a = require('sub'); // 内部で "foo" が出力される
var b = require('sub'); // 出力は無い

// a も b も実体は同じオブジェクト
a.bar = 123;
console.log(b.bar); // 123
console.log(a === b); // true

modules.exports と exports

exports という modules.exports のエイリアスが存在するが、 この exports はただ modules.exports の初期値を保持しているだけ。

要するに var exports = modules.exports; がモジュールの実行前に行われていると考えておけばだいたい合ってる。 (実際は関数の引数だけど)