概要
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';
できる)
- allowSyntheticDefaultImports (
各変換結果
1. Babel import ... from '...';
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 '...';
import * as foo from 'foo';
foo(1 === 1);
'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 '...';
import foo from 'foo';
foo(1 === 1);
"use strict";
var foo_1 = require('foo');
foo_1["default"](1 === 1);
4. TypeScript import * as ... from '...';
import * as foo from 'foo';
foo(1 === 1);
"use strict";
var foo = require('foo');
foo(1 === 1);
実行結果
npm package の実装ごとに場合わけする。
1. ES6 module の考慮なし
function foo() { /* ... */ };
module.exports = foo;
// module.exports.default = foo;
// module.exports.__esModule = true;
- Babel
import foo from 'foo';
は OK - Babel
import * as foo from 'foo';
は実行時 Error - TypeScript
import foo from 'foo';
は実行時 Error - TypeScript
import * as foo from 'foo';
は OK
いきなり矛盾している。ヤバイ。
2. ES6 module を考慮し、default に export
function foo() { /* ... */ };
module.exports = foo;
module.exports.default = foo;
// module.exports.__esModule = true;
- Babel
import foo from 'foo';
は OK - Babel
import * as foo from 'foo';
は実行時 Error - TypeScript
import foo from 'foo';
は OK - TypeScript
import * as foo from 'foo';
は OK
これで 3. が OK になった。いい感じだ。
3. ES6 module を考慮し、default に export し、 __esModule を truthy に
function foo() { /* ... */ };
module.exports = foo;
module.exports.default = foo;
module.exports.__esModule = true;
- Babel
import foo from 'foo';
は OK - Babel
import * as foo from 'foo';
は OK - TypeScript
import foo from 'foo';
は OK - 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
補足
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