JavaScriptでのimport/export方法

JavaScriptでは、exportやimportを使って関数やクラスを他のファイルから呼び出し/読み込みすることができますが、いろんな書き方があって何がどう違うのかごちゃごちゃしませんか？
少なくとも私はごちゃごちゃしちゃうので、備忘録も兼ねてまとめたいと思います。

# es2015とNode.jsでの書き方の違い

まず最初の混乱ポイントは、
`export` なのか `exports(module.exports)` なのか？
読み込みは `import`、`require` どっちなの？

めちゃくちゃややこしいですよね。けどこれ実は別物で、
`export/import` は、ES2015(ES6)での書き方
`exports/require` は、Node.js(CommonJS)での書き方なんです。

参考：CommonJSって何？という方は @naoki_mochizuki さんが書かれた [JavaScriptが辿った変遷](https://qiita.com/naoki_mochizuki/items/cc6ef57d35ba6a69117f#commonjs)という記事を読んでみてください！JavaScriptの歴史がとてもわかりやすく説明されていて、CommonJSの正体もスッと理解できると思います！

以下に書き方を例を使ってまとめていきます。

# export 方法一覧

### ES2015(ES6)での書き方

１. 個々の機能をエクスポート

```js
export const name = "xxx";
export function funcName() {...}
export class className {...}
```

２. 事前に宣言された機能をまとめてエクスポート

```js
export { name, funcName, className };
```

３. デフォルトとして個別の機能をエクスポート

```js
export default function () { … } 
// or
export default { name, funcName, className }
```

### Node.js(CommonJS)での書き方

４. 単一のモジュールをエクスポート

```js
module.exports = xxx;
```

５. エクスポートしたい個別の機能を指定し、オブジェクトとしてエクスポート

```js 
module.exports = { xxx, yyy };
module.exports = { xxxName: xxx, yyyName: yyy };
```


# import 方法一覧

### ES2015(ES6)での書き方

１. モジュールから特定のエクスポートをインポートする

```js
import { xxx } from "./lib"
import { xxx, yyy } from "./lib"
```

２. モジュールのコンテンツすべてをインポート

```js
import * as lib from "./lib"
```

３. デフォルトをインポート

```js
import xxx from "./lib"
```


### Node.js(CommonJS)での書き方

４. エクスポートされたものをまとめてインポート

```js
const xxx = require("./lib")
```

５. エクスポートされたものを個別にプロパティ名を指定してインポート

```js
const { xxx, yyy } = require("./lib")
```


# 使用例
それでは、実際のコードの例を見て見ましょう。

## ES2015(ES6)の場合

### 例１

```js:export1.js
export const add = (num1, num2) => {
  return num1 + num2;
};
export const minus = (num1, num2) => {
  return num1 - num2;
};
```

```js:import1.js
import { add, minus } from './export1.js';

add(1, 2); // => 3
minus(1, 2); // => -1
```

個々の機能をエクスポートする場合、インポートしたい物を {} で括って個別に呼び出します。

### 例２

```js:export2
const add = (num1, num2) => {
  return num1 + num2;
};
const minus = (num1, num2) => {
  return num1 - num2;
};

export { add, minus };
```

```js:import2.js
import { add, minus } from './export2.js';

add(1, 2); // => 3
minus(1, 2); // => -1
```

事前に宣言された機能をまとめてエクスポートした場合も同じ。
インポートしたい物を {} で括って個別に呼び出します。

### 例３

```js:export3
const add = (num1, num2) => {
  return num1 + num2;
};
const minus = (num1, num2) => {
  return num1 - num2;
};

export { add, minus };
```

```js:import3.js
import * as calc from './export3.js';

console.log(calc); // => [Module] { add: [Function: add], minus: [Function: minus] }
calc.add(1, 2); // => 3
calc.minus(1, 2); // => -1
```

これは、`calc` という名前で export3.js のファイルのモジュールからのエクスポートすべてインポートします、という書き方です。

### 例４

```js:export4
const add = (num1, num2) => {
  return num1 + num2;
};
const minus = (num1, num2) => {
  return num1 - num2;
};

export default { add, minus };
```

```js:import4.js
import calc from './export4.js';

console.log(calc); // => { add: [Function: add], minus: [Function: minus] }
calc.add(1, 2); // => 3
calc.minus(1, 2); // => -1
```

`export default` でエクスポートすると、インポート時に {} で括る必要がなく、まとめてインポートされます。

### 例５

```js:export5.js
export default function add(num1, num2) {
  return num1 + num2;
}
```

```js:import5.js
import add from './export5.js';

console.log(add(1, 2)); // => 3
```

`export default` する時には、個別の機能をダイレクトにエクスポートすると、インポートしたものをそのまま使用できます。


:warning: 注意 :warning:
コマンドでこれらのファイルを実行する時に、下記のように `node` コマンドで実行しても、エラーが出て期待通りに実行できません。

```bash
$ node import5.js
/User/xxx/import5.js:1
import add from './export5.js';
^^^^^^

SyntaxError: Cannot use import statement outside a module
(エラーが続きます...)
```

なぜなら、ES2015(ES6) で書かれたコードは node コマンドでそのまま実行できないからです。

実行したい場合は、babel などのトランスパイラを使って ES2015(ES6) で書かれた JavaScript を Node.js が理解してくれるようにトランスパイルするなどの一手間を加える必要があります。

けど、今回はその環境を作るのがちょっとめんどくさかったので、下記オプションを付けて確認しました。
（`--experimental-modules` の説明は割愛します。）

```bash
$ node --experimental-modules import5.js 
(node:10746) ExperimentalWarning: The ESM module loader is experimental.
3
```


## Node.js(CommonJS)の場合

### 例６

```js:export6.js
const add = (num1, num2) => {
  return num1 + num2;
};
const minus = (num1, num2) => {
  return num1 - num2;
};

module.exports = add;
```

```js:import6.js
const add = require('./export6');

add(2, 3); // => 5
```

import6.js でインポートする時に
`const { add } = require('./export');` という風に{} で括ってしまうと、
`add(2, 3)` 実行時にエラーになってしまいます。

なぜでしょうか？？

exportされているのは `add`。
すなわち、関数自体が export されているのですが、`add` というプロパティ名でエクスポートされたものをインポートしようとしているので、そんなプロパティは存在しないよ、とエラーが発生してしまいます。

では次の例はどうなるでしょうか。

### 例７

```js:export7.js
const add = (num1, num2) => {
  return num1 + num2;
};
const minus = (num1, num2) => {
  return num1 - num2;
};

module.exports = { add, minus };
```

```js:import7-1.js
const add = require('./export7');

add.add(2, 3) // => 5
add.minus(2, 3) // => -1
add(2, 3); // エラー
```

exports7.js では個々の関数をオブジェクトとしてエクスポートしているので、import7-1.jsのaddには、
`{ add: [Function: add], minus: [Function: minus] }`
というオブジェクトが代入されているのです。

ではもし、下記のように {} で括ってインポートしたらどうなるでしょうか。

```js:import7-2.js
const { add, minus } = require('./export7');

add(2, 3) // => 5
minus(2, 3) // => -1
```

それぞれの変数には対応するプロパティの値、すなわち関数が代入されるので、そのまま関数として実行できるようになります。

最後に、これは少し極端な例を見てみましょう。

### 例８

```js:export8.js
const add = (num1, num2) => {
  return num1 + num2;
};
const minus = (num1, num2) => {
  return num1 - num2;
};

module.exports = { addName: add, minusName: minus };
```

```js:import8.js
const { addName, minusName } = require('./export8');

addName(2, 3) // => 5
minusName(2, 3) // => -1
```

もう何が起こっているかはわかりますね！
export8.js で、`add` という名前の関数を `addName` というプロパティ名でエクスポートしているので、`{ addName }` でインポートしてあげる必要がありますね。

もしこれを `const func = require('./export');` とインポートしたら、
`func.addName(2, 3)` といった感じで呼び出してあげればOKです！


# 参考

- [export - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/export)
- [import - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import)
- [module.exports | Node.js](https://nodejs.org/docs/latest/api/modules.html#modules_module_exports)
- [require | Node.js](https://nodejs.org/docs/latest/api/modules.html#modules_require_id)