CommonJS方式 exports と module.exports

古い記事だが、https://jovi0608.hatenablog.com/entry/20111226/1324879536 に、node.jsソースの一部が載っていた。

src/node.js(v0.6.6)
   526    NativeModule.wrap = function(script) {
   527      return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
   528    };
   529
   530    NativeModule.wrapper = [
   531      '(function (exports, require, module, __filename, __dirname) { ',
   532      '\n});'
   533    ];
   534
   535    NativeModule.prototype.compile = function() {
   536      var source = NativeModule.getSource(this.id);
   537      source = NativeModule.wrap(source);
   538
   539      var fn = runInThisContext(source, this.filename, true);
   540      fn(this.exports, NativeModule.require, this, this.filename);
   541
   542      this.loaded = true;
   543    };

一番分かりやすい気がする。

これはモジュールローダーの一部だろう。thisはモジュールオブジェクトで、ロードされたモジュールのメモリ内表現だと思ってよい。

モジュールコードをコンパイルする手順を読み取れる。

  1. テキスト(文字列)としてのソース source をファイルから取得する。
  2. テキストソースをラップする。これは、前後に定形テキストを連接すること。
  3. ラップされたテキストソース soruce (同名変数に再代入)は1個の無名関数定義になる。
  4. runInThisContext関数にラップされたテキストソース srouce を渡すと、関数オブジェクト fn が帰ってくる。
  5. この fun を実行する。
  6. モジュールオブジェクト this の loaded フラグを true にして、ロード作業終了を記録する。

モジュールとみなされたソースコード(テキスト=文字列)は、ロージ作業の一環として実行されるが、実行は関数ボディとして実行される。その関数は:

(function (exports, require, module, __filename, __dirname) { 
  // ここに ソースコード
});

ソースコードからは、無名関数の引数である exports, require, module, __filename, __dirname が見えている。これらの引数変数は、予約名の特殊変数のように見える。

モジュールから作られた無名関数の実行呼び出しを見ろと: fn(this.exports, NativeModule.require, this, this.filename); つまり、

引数名
exports this.exports
require NativeModule.require
module this
__filename this.filename
__dirname 無し

愕然とすることは:

  1. exports <= this.exports, module <= this と、違った変数が同一オブジェクトの別な場所を指すようにセットしている。
  2. __dirname に何も入れてない。

ガーン。

指している場所は、module.exports = exports 。変数 module はまさに自分が表現されているモジュールオブジェクトそのもの。module にアクセスすれば、module.filename として __filename を取れる。__dirname も module.dirname かも知れない。

  1. module からモジュール情報は得られる。
  2. module.exports には、そのモジュールがエクポート情報が入るべき。
  3. exports は module.exports の別名。おそらくシンタックスシュガーのつもりだったのだろう。

モジュールコード内では、require が NativeModule.require を指すので、その関数を使える。が、非モジュールコードでは使えないことになる。

互換性から、おそらくCommonJSの基本メカニズムは変わってないのではないだろうか。今考えるとだいぶ酷い


行番号削除バージョン。

src/node.js(v0.6.6) L.526 - L.543 を整形 +2行

NativeModule.wrap = function(script) {
  return (NativeModule.wrapper[0] + 
          script + 
          NativeModule.wrapper[1]);
};

NativeModule.wrapper = [
  '(function (exports, require, module, __filename, __dirname) { ',
  '\n});'
];

NativeModule.prototype.compile = function() {
  var source = NativeModule.getSource(this.id);
  source = NativeModule.wrap(source);

  var fn = runInThisContext(source, this.filename, true);
  fn(this.exports, NativeModule.require, this, this.filename);
  this.loaded = true;
};