概要
TypeScript(というかES6)のimport文を、いまいち書き方を理解しないまま下記の記事を参考に書いていました。
なので、とりあえず * as foo の形式でfooにモジュールを束縛して使うのが今までの使い方とあまり差がなく、わかりやすいでしょう。
という訳で基本的にはimport * as foo from 'foo'
の形式でimport文を書いていたのですが、これがtarget --es5
のケースと、target --es6
+ babelのケースで若干違いがありハマったのでメモ。ちなみに上の記事が問題というわけではなく、よく理解せずに使っていた私が悪いという話です。
そもそも何故target --es6
+ babelと2度変換をかけるのか
target --es6
で変換したコードを、さらにバベってES5にするというのは一見すると手間が増えただけのように思えます。手間が増えるのを承知のうえでこの構成にしている理由は、今後のメンテナンス性にあります。
TypeScriptは広く普及しているといえどAltJSの1つであるという事実に変わりはありません。一方ES6は、今後スタンダードになることが確実です。なので今後コードを引き継ぐことを考えると、ES6に寄せていきたい訳です。ただ静的型付けであるTypeScriptは個人的にはどうしても使いたいので、一度target --es6
での出力を挟むことで いつでもTypeScriptを切り捨てられる環境にしつつ、TypeScriptを使っていく と良いのかなと思います。
例
実行環境
node v0.12.7
tcs 1.6.2
babel 5.8.23
サンプル
元のTypeScriptコードです。importでlodashを読み込み、シンプルにそれを使っているだけですね。
/// <reference path="./typings/bundle.d.ts" />
import * as _ from 'lodash';
const array = [1, 2, 3];
_.forEach(array, (n) => { console.log(n); });
target --es5のケース
tscでtarget --es5
に指定した場合、次のように変換されました。import文がcommonJSのrequireにシンプルに変換されています。今までrequire
で書いていたときと全く同じ形に変換され、違和感なく使うことが出来ます。
/// <reference path="./typings/bundle.d.ts" />
var _ = require('lodash');
var array = [1, 2, 3];
_.forEach(array, function (n) { console.log(n); });
target --es6 => babelのケース
ハマったのがこのケース。TypeScriptを一旦target --es6
の形式で出力し、それをBabelにかけて最終的なES5コードにしています。
ES6への変換後のコードがコチラ。今回は型を特に使ってなかったので、何も変わらないです。
/// <reference path="./typings/bundle.d.ts" />
import * as _ from 'lodash';
const array = [1, 2, 3];
_.forEach(array, (n) => { console.log(n); });
そして以下がes6のコードをbabelにかけた例。require
に対して、_interopRequireWildcard
という内部関数が1枚挟まっています。
/// <reference path="./typings/bundle.d.ts" />
'use strict';
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; } }
var _lodash = require('lodash');
var _ = _interopRequireWildcard(_lodash);
var array = [1, 2, 3];
_.forEach(array, function (n) {
console.log(n);
});
分かりづらいので展開すると以下のような形に。
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;
}
}
ここに見事に引っかかりました。target --es5
の変換と、target --es5
=> babelの変換は等価ではなく、後者の場合のみhasOwnProperty
でないプロパティは削除されて読み込まれるようです。
どんな問題が起きるのか?
target --es5
のコードを、target --es6
+ babel に置き換えてもコードは書きかえなくていいだろう、と思っていると痛い目見ます。
私がハマったのはnode.jsで広く使われる認証モジュールpassport.js
を使っていたときで、ざっくり書くと以下のようなコードです。これをtarget --es6
+ babelの構成にした途端、use
プロパティが無いと怒られてしまいました...
passport.js
のコードを見てもらえるとわかりますが、use
及び他のメソッドはprorotypeとして定義されており、require()
したときbabelが吐いたコードによって除外されてしまっています。
/// <reference path="./typings/bundle.d.ts" />
import * as passport from 'passport';
import {Strategy as FacebookStrategy} from 'passport-local';
passport.use(new FacebookStrategy(() => {
//
}
解決方法としては、以下のようにワイルドカード指定をやめることが考えられます。
/// <reference path="./typings/bundle.d.ts" />
import Passport from 'passport';
import {Strategy as FacebookStrategy} from 'passport-local';
passport.use(new FacebookStrategy(() => {
//
}
すると以下のように変換されるはずです。こちらのケースならhasOwnProperty()
のチェックが走らず、今まで通りの感覚でrequire()
することが出来ます。
/// <reference path="./typings/bundle.d.ts" />
'use strict';
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _passport = require('passport');
var _passport2 = _interopRequireDefault(_passport);
ただし、問題があり2015/10/28時点で DefinitelyTypedのpassport.js型定義ファイルがこの形式のimportに対応していないません。コンパイルで怒られてしまう。
DefinitelyTypedにプルリクを送ればいいのかもしれませんが、今後も他のモジュールで同じ事を繰り返すんだろうなと思ってやる気がでない..
まとめ
target --es5
とtarget --es6
+ babelは等価ではありません。注意しましょう。
自分で書いてて「本当かコレ」感があったので、詳しい人のご意見お待ちしております。
他にもPromise(bluebird)使いたい時に、import * Promise as 'bluebird'
とするとtarget --es6
だと「ES6にはPromise既に定義されていますよ」と怒られる問題もどう解決したらいいんだろうか。node 0.10系でバベったES6コードを動かしたいときに困る。
参考
Babelで理解するEcmaScript6の import / export
http://qiita.com/inuscript/items/41168a50904242005271
TypeScript 1.5.3 変更点
http://qiita.com/vvakame/items/9b9fde6c71aae6a824c0