はじめに
いつもはes6のモジュール管理方式であるimport/export
(ES modules)を使って、npmで手に入れたライブラリや自分で作成したモジュールをロードしているが、たまに思った挙動にならないことがある。また、export default
したモジュールをテストしようとしてkarmaでrequire()
するとエラーになったりした。そういう場合は、とりあえずmodule.exports
を使ってみるとうまくいったりすることが多いのだが、なぜなのかはあまり考えていなかった。
ということで、実際にはどうなっているのか、またモジュールシステムとは何なのかという点で基本的なことから理解を深めて、es6のimport/export
との違いを知り、より正しく実装できるようになれば良いと思っている。
結論としては
方針としては基本的にはES6形式で記述するが、CommonJS形式の読み書きが必要なケースは、次の対比表を使って理解する。
対比表
CommonJS | ES modules | 備考 |
---|---|---|
exports.method = method |
export { method } |
|
module.exports = function() {...} |
export default function() {...} |
|
export.default = function() {...} |
export default function() {...} |
CommonJS的には、いちプロパティの default に対して関数を設定するのと同じこと |
var a = require('module') |
import a from 'module' |
|
var a = require('module').a |
import { a } from 'module' |
|
var a = require('module').default |
import a from 'module' |
export default でエクスポートされたモジュールの場合 |
もう少し詳しく
Modules
Node.js にはシンプルなモジュールローディングシステムがある。ファイルとモジュールとは、一対一の関係がある。例えば次のようなsampleA.js
は、sampleA
モジュールとみなすことができる。
const PI = Math.PI;
exports.area = (r) => PI * r * r;
exports.circumferenct = (r) => 2 * PI * r;
exports
という特別なオブジェクトのプロパティとして、エクスポートしたい関数を追加すれば良い。
module.exports
もし、関数そのもの(例えばコンストラクタ)をエクスポートしたかったり、オブジェクトをエクスポートしたい場合は、module.exports
キーワードを用いる。なお、モジュールシステム自体は、require('module')
にて実装されている。
The module object
module
変数は、現在のモジュールを表現しているオブジェクトを参照している。便宜上、module.exports
はexports
経由でもアクセス可能と なっているが、続く項にあるようにexports
オブジェクト自体の参照を更新すると問題が生じる。
module.exports
module.exports
オブジェクトはモジュールシステムによって作られる。我慢できないことはないが、ライブラリ作成時にあるクラスのインスタンスをモジュールとしてエクスポートしたいニーズは多くある(new
する、の意)。そうするためにはmodule.exports
に望みのオブジェクトを代入する。この時exports
変数にオブジェクトを代入しても、エクスポートしたことにならないので注意する。
exports alias
exports
変数はモジュールの内部で利用可能であり、module.exports
の参照としてそのライフサイクルが始まる。もし新しい値を代入すれば前の値はバインドされることはないし、何もエクスポートされなくなる。ガイドとしては、もしこの関係性が奇妙に映るようならexports
は無視してmodule.exports
を使え、ということだ。
Export(ES6)
エキスポート文は、関数やオブジェクトやプリミティブ型の値をモジュールやファイルからエクスポートするときに使う。
Named exports
Named exports は、幾つかの値をエクスポートするときに便利で、インポートする側は、参照する際に同じ名前を使うことができる。
// module "my-module.js"
export function cube(x) {
return x * x * x;
}
const foo = Math.PI + Math.SQRT2;
export { foo };
別のスクリプトから、次のように読み込むことができる。
import { cube, foo } from 'my-module';
console.log(cube(3)); // 27
console.log(foo); // 4.555806215962888
Default export(only one per script)
default export においては、1モジュールにつき一つの初期エクスポートが存在している。これには、関数やクラス、オブジェクトなど何でも設定できる。
// module "my-module.js"
export default function cube(x) {
return x * x * x;
}
import cube from 'my-module';
console.log(cube(3)); // 27
export default / require()
この組み合わせでエクスポート・インポートする場合は、次のように考える。
var a = require('./module').default
index.js
にて、import
するならこう。
import a from './module'
a
に対して、default
を明示しているかどうかの違いがある。
この部分の取り扱いの違いが、karmaでテストする際のエラー(冒頭に記述した問題点)の原因であり、export default
と、module.exports =
は等価なものだという思い込みが諸悪の根源だった。
参考URL
Node.js v6.2.2 Documentation
JavaScriptとECMAScript、CommonJS/AMD/Browserify/RequireJS/Webpack、TypeScriptに関する覚え書き