Node.jsのmoduleとexports、そしてCoffeeScript

ここしばらく、Node.jsを使った開発で忙しくて、ようやく一段落つきました。ということで随分とNodeを使ってきたつもりだったのですが、肝心のexportsの辺りの理解が浅かったことに気づいたので、少しまとめます。

exportsとmodule.exports

exportsとmodule.exportsについては、少々バージョンが古い*1ですが

Node.js : exports と module.exports の違い(解説編) - ぼちぼち日記

が非常に参考になりそうです。まだ詳細は理解できてませんが、一先ずmodule.exportsが強いということが分かれば良いのではないかと思います。

exportsのスニペット*2

ここで紹介するのは、Node.jsとCommon.jsとDOMに対応したexport方法です。underscore.jsのコードを参考、というかほぼそのままです。

ここではtestをexportsすることにします。

test = {foo: "foo", bar: "bar"};

if (typeof exports !== "undefined") {
	if (typeof module !== "undefined" && module.exports)
		exports = module.exports = test // Node.js
	exports.test = test // Common.js
} else {
	this.test = test // DOM
}

Nodeの時は、module.exportsに入るtestがエクスポートされるオブジェクトになりますが、if文を出たところでtest.test = testとなるので、Common.js形式でもエクスポートされているという仕組みです。

CoffeeScriptでの注意点とシンプルなハック

CoffeeScriptでは少し注意が必要です。

まず、上記のコードを普通にCoffeeScriptで書いてみます。

test = foo: "foo", bar: "bar"

if exports?
	if module? and module.exports?
		exports = module.exports = test
	exports.test = test
else
	@test = test

これの変換結果は

var exports, test;

test = {
  foo: "foo",
  bar: "bar"
};

if (typeof exports !== "undefined" && exports !== null) {
  if ((typeof module !== "undefined" && module !== null) && (module.exports != null)) {
    exports = module.exports = test;
  }
  exports.test = test;
} else {
  this.test = test;
}

となる*3わけですが、exportsへの代入処理があるために、exportsがローカル(var)で宣言されてしまいます。その結果、一番目のifがfalseになるので、DOMの方が実行されることになります。これではいけません。

そこで、簡単な解決法を考えました。

test = foo: "foo", bar: "bar"

if exports?
	if module? and module.exports?
		`exports = module.exports = test`
	exports.test = test
else
	@test = test

exportsに代入する部分のみを埋め込みJavaScriptの形にしただけですが、これでvar exportsが現れることはなくなります。

一件落着。

*1:現在の安定版は0.8

*2:「炭ペット」とタイポ&誤変換してまった

*3:実際にはさらに無名関数で囲まれる