2017年3月22日水曜日

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; がモジュールの実行前に行われていると考えておけばだいたい合ってる。 (実際は関数の引数だけど)

 

npm と Node.js 上で require を使ったモジュール読み込みの仕組みについてメモ

npm でインストールしたモジュールの行方

まず, npm でインストールしたモジュールの行方についてまとめます.

npm でのインストールには 2種類あります.

ローカルインストール

普通にインストールするとローカルインストールになります. 現在のカレントディレクトリの node_modules フォルダの中にインストールします.

npm install モジュール名  

どこにインストールされるのかちゃんと確認したい場合は bin コマンドを使います.

npm bin  

グローバルインストール

グローバルインストールは全てのプロジェクト共通で使いたいモジュールをインストールする際に使います. やり方は簡単でローカルインストールの方法に -g オプションを加えるだけです.

また, グローバルインストールした場合, そのディレクトリにパスが通ります. coffeescript mocha を直接コマンドとして実行でるようになるのはそのためです.

npm install -g モジュール名  

グローバルインストール用フォルダがどこにあるのかは -g オプションをつけて bin コマンドを実行すると確認できます.

npm bin -g  

ちなみに windows の場合グローバルインストール用フォルダにパスが通っていないので, NODE_PATH 環境変数にパスを登録してから使うようにしましょう.

windows 7 の場合, C:/Users/ユーザー名/AppData/Roaming/npm/node_modules 的な場所にあるかと思います.

require でモジュールをサーチする場所

require でモジュールをサーチするディレクトリについてまとめます.

require で探すモジュールのパスは global.module.paths の中に入っています.

node  
global.module.paths  

デフォルトではカレントディレクトリから下りながら全てのディレクトリの node_modules が登録されています.

[ 'e:/Users/hoge/Documents/work/node_modules',
  'e:/Users/hoge/Documents/node_modules',
  'e:/Users/hoge/node_modules',
  'e:/Users/node_modules',
  'e:/node_modules' ]

それに加えて NODE_PATH 環境変数の中にあるモジュールを探しに行きます.

Reference

参考にしたサイト

 

require()とは何か?何が便利なのか

問題提起

最近のJSのサンプルコードは、だいたい文頭に

var $ = require('jquery');

などとついていますが、このrequire()ってよくわからない。

具体的には、

·         どうやって使えばいいの?

·         普通にscriptタグで読み込む場合とどう違うの?

·         require」でググっても英和辞典とかしかでないんだけど!

ということでざっと調べてみました。

require()とは何か?

commonJS

http://www.slideshare.net/terurou/common-js

以下、要約

そもそもの発端は、「Javascriptって良いよね。ブラウザ以外(ServerSide)でも使おうぜ」ということからだったらしいです。

しかし、scriptタグで読み込むこと前提だったので、HTMLがない(レンダリングエンジンがない)場合に外部ライブラリを参照する方法がない。。。
(さらに言えば、FileNetwork、標準入出力など色々と不便。)

これじゃ困るということで、ServerSideの標準仕様を定めたのがcommonJS

その中で外部ライブラリを参照する方法が、各JSファイルをモジュール化しておいて、使うときはrequire()で読み込むやりかた。

つまり、require()commonJSの仕様の一つであって、特定の実装・ライブラリを指しているわけじゃないようです。

node.js

ServerSideJavascriptを使えるようにしたもの。

当然require()はあるが独自拡張しており、最近はcommonJSを無視しているとかなんとか。(ちょと古い?)

http://nodejs.jp/nodejs.org_ja/api/modules.html

http://meso.hatenablog.com/entry/20110626/1309082158

nodeにはnpmというパッケージマネージャがありますが、これでインストールしたものはrequire()で読み込めます。

RequireJS

http://requirejs.org/docs/commonjs.html

CommonJS defines a module format. Unfortunately, it was defined without giving browsers equal footing to other JavaScript environments. Because of that, there are CommonJS spec proposals for Transport formats and an asynchronous require.

commonJSの仕様はServerSideのために作られたはずなんですが、ClientSideでもrequire()を使いたい、ということで作られたライブラリ。

<script data-main="js/main.js" src="bower_components/requirejs/require.js"></script>

とすればmain.jsentryPointとして動作するらしい。

webpack

http://ameblo.jp/ca-1pixel/entry-11884453208.html

Require.jsをもっとリッチにしたもの。

require()の対象をjsのモジュールだけでなく、

·         altJSのコンパイル

·         CSSHTMLファイル取り込み

·         bower経由などの)jsファイル取り込み

などができるようになっている。

Require.jsと比較すると、

·         Require.jsは、ブラウザ側で、個々のファイル(モジュール)を非同期に読み込むスタイル。

·         webpackは、サーバー側で依存関係を全て見て、一つのjsファイルを生成しておくスタイル。

·         (もちろん、sourceMappingされるので実行時デバッグも問題ない。)

似たものにbrowserifyというのがある。(webpackの方が高機能っぽいので検証してない。)

require()のメリット

require()は、jsをモジュール化する仕組みです。

従来のClientSideJavascriptでは、scriptタグで読み込んでグローバルに転がしておくしかなかった。

一応MVCなどのパターンでjsファイルを分割するものの、各ファイルが密結合する(あるいはうっかり影響を与えてしまう)ことを防げなくて再利用性に乏しかった。

再利用しやすくなる。

モジュール化する、と同義ですね。

単体テストがしやすくなる。

外部依存をrequire()のみに限定することで、それ単体で動かすことができる。

require()の部分にMockを差し込むテストツールもある。
https://facebook.github.io/jest/

特にReact

コンポーネントをたくさん作って組み合わせていくスタイルなので、単体テストがやりやすいrequire()と非常に相性がいい。

http://facebook.github.io/jest/docs/tutorial-react.html#content

require()のデメリット。

·         学習コストがかかる。

·         ブラウザで気軽に試せない。(Require.jswebpack/browserifyが必要)

とはいえ、node.jsでは既に一般的だし、グローバルに転がすのはイケてないので、もはや導入しない理由はない気がします。