Babel と TypeScript の ES6 modules の import の解釈の違い

# 概要

Babel と TypeScript の ES6 modules の import の挙動の違いについて確認する。 Application 内部では Babel ・ TypeScript を同一設定で使うはずなので問題は起きない。しかし、外部の npm package を利用する際には問題が起きる。

各状況ごとにどう書けばいいのかの参考情報をまとめる。

将来的には、このようなまとめが不要になることを期待する。

# 前提

- Babel 6.6.5
    - es2015 preset 
- TypeScript 1.8.7
    - allowSyntheticDefaultImports (`export = foo` を `import foo from 'foo';` できる)

# 各変換結果

## 1. Babel `import ... from '...';`

```js
import foo from 'foo';

foo(1 === 1);
```

```
'use strict';

var _foo = require('foo');

var _foo2 = _interopRequireDefault(_foo);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

(0, _foo2.default)(1 === 1);
```

## 2. Babel `import * as ... from '...';`

```js
import * as foo from 'foo';

foo(1 === 1);
```

```js
'use strict';

var _foo = require('foo');

var foo = _interopRequireWildcard(_foo);

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

foo(1 === 1);
```

## 3. TypeScript `import ... from '...';`

```ts
import foo from 'foo';

foo(1 === 1);
```

```js
"use strict";
var foo_1 = require('foo');
foo_1["default"](1 === 1);
```

## 4. TypeScript `import * as ... from '...';`

```ts
import * as foo from 'foo';

foo(1 === 1);
```

```js
"use strict";
var foo = require('foo');
foo(1 === 1);
```

# 実行結果

npm package の実装ごとに場合わけする。

## 1. ES6 module の考慮なし

```js
function foo() { /* ... */ };
module.exports = foo;
// module.exports.default = foo;
// module.exports.__esModule = true;
```

1. Babel `import foo from 'foo';` は OK
2. Babel `import * as foo from 'foo';` は実行時 Error
3. TypeScript `import foo from 'foo';` は実行時 Error
4. TypeScript `import * as foo from 'foo';` は OK

いきなり矛盾している。ヤバイ。

## 2. ES6 module を考慮し、default に export

```js
function foo() { /* ... */ };
module.exports = foo;
module.exports.default = foo;
// module.exports.__esModule = true;
```

1. Babel `import foo from 'foo';` は OK
2. Babel `import * as foo from 'foo';` は実行時 Error
3. TypeScript `import foo from 'foo';` は OK
4. TypeScript `import * as foo from 'foo';` は OK

これで 3. が OK になった。いい感じだ。

## 3. ES6 module を考慮し、default に export し、 __esModule を truthy に

```js
function foo() { /* ... */ };
module.exports = foo;
module.exports.default = foo;
module.exports.__esModule = true;
```

1. Babel `import foo from 'foo';` は OK
2. Babel `import * as foo from 'foo';` は OK
3. TypeScript `import foo from 'foo';` は OK
4. TypeScript `import * as foo from 'foo';` は OK

`__esModule` が必要って時点でおかしいし、もう ES6 babel modules って呼べばいいんじゃないかな。

# まとめ

ES6 modules を `module.exports = function() { ... };` な外部 npm package に適用するときはよく注意する。`import ... from '...';` と `import * as ... from '...';` のどちらか一方しかきちんと動かないし、 Babel と TypeScript の解釈は矛盾しているため、それぞれ逆の記法が必要だ。

今回は問題にしなかったが TypeScript には .d.ts の問題や記法の解釈を変える option もある。がんばってほしい。→ [TypeScript の ES6 modules の解釈と allowSyntheticDefaultImports の整理 - Qiita](http://qiita.com/bouzuya/items/edf5274241b50f32c621)

# 補足

ES6 modules の仕様を見る限り、 Babel の挙動は間違っているように見える。ただ互換性の都合でそうなっていて、どうしようもないのかもしれない。

- http://www.ecma-international.org/ecma-262/6.0/#table-40
- http://www.ecma-international.org/ecma-262/6.0/#table-42

# 参考

- http://www.ecma-international.org/ecma-262/6.0/#table-40
- http://www.ecma-international.org/ecma-262/6.0/#table-42
- http://www.ecma-international.org/ecma-262/6.0/index.html#sec-imports
- [TypeScript の ES6 modules の解釈と allowSyntheticDefaultImports の整理 - Qiita](http://qiita.com/bouzuya/items/edf5274241b50f32c621)
- [blog.bouzuya.net 2016-03-14](http://blog.bouzuya.net/2016/03/14/)