# CommonJS と ES6の import/export で迷うなら

## はじめに
いつもは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`モジュールとみなすことができる。

```js:sampleA.js
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 は、幾つかの値をエクスポートするときに便利で、インポートする側は、参照する際に同じ名前を使うことができる。

```js:my-module.js
// module "my-module.js"
export function cube(x) {
  return x * x * x;
}
const foo = Math.PI + Math.SQRT2;
export { foo };
```

別のスクリプトから、次のように読み込むことができる。

```js:index.js
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モジュールにつき一つの初期エクスポートが存在している。これには、関数やクラス、オブジェクトなど何でも設定できる。

```js:my-module.js
// module "my-module.js"
export default function cube(x) {
  return x * x * x;
}
```

```js:index.js
import cube from 'my-module';
console.log(cube(3)); // 27
```

#### export default / require()
この組み合わせでエクスポート・インポートする場合は、次のように考える。

```js:index.js
var a = require('./module').default
```

`index.js`にて、`import`するならこう。

```js:index.js
import a from './module'
```

`a`に対して、`default`を明示しているかどうかの違いがある。

この部分の取り扱いの違いが、karmaでテストする際のエラー（冒頭に記述した問題点）の原因であり、`export default`と、`module.exports = `は等価なものだという思い込みが諸悪の根源だった。

### 参考URL
[Node.js v6.2.2 Documentation](https://nodejs.org/api/modules.html#modules_modules)
[JavaScriptとECMAScript、CommonJS/AMD/Browserify/RequireJS/Webpack、TypeScriptに関する覚え書き](http://www.ideaxidea.com/archives/2015/02/javascript_history.html)