Meteor.jsでTypeScriptを使う際の複数ファイル間モジュール連携


## はじめに

現状、こうやっているよ。という備忘録です。
tsファイル間でモジュール連携するのに、ひどいやり方をしています。

もっとマトモなやり方があるだろ、という方はぜひ教えてください。

## TypeScript導入

まずは、TypeScriptを導入しましょう。

```bash
npm install -g typescript
```

次に、meteorで以下のパッケージを導入します。これでMeteorでTypeScriptが使えるようになります。

```bash
meteor add meteortypescript:compiler
```

なんですが、さらに以下も導入しておくと、メジャーなライブラリのd.tsファイルをごっそりインストールしてくれて便利です。

```bash
meteor add meteortypescript:typescript-libs

```

## 書き方

フツーに、拡張子を.jsではなく.tsにして記述するだけです。
そうすると、tsファイルを編集して保存するだけで、jsへのコンパイルまで自動でmeteorがやってくれます。

## さて、問題のファイル間のモジュール連携だ。

さてさて、meteorでは、実行時に各jsファイルは結合されてminifiedされます（デバッグモード時除く）。
で、プロダクションモードでもデバッグモードでもそうなんですが、各jsファイルのJavaScriptコードはそれぞれ無名関数に包まれてしまうんですね。

なので、ファイル間で変数のやりとりをしたい場合は、

```javascript
Posts = new Mongo.Collection('posts');
```

のように、varを付けずにグローバルに変数を定義する必要があるわけです。

この、各ファイルのコードが無名関数に包まれてしまうというのは、tsファイルも例外ではありません。

すると、困ったことになります。

例えば、以下の２つのtsファイルがあるとするじゃないですか。

```map.ts
module WrtGame {
  export class Map {
    protected map:any;
    constructor(isCalledFromChild:boolean) {
      if(!isCalledFromChild) {
        throw new Error("This class is a abstract class.");
      }
    }

    public setMap(map:any)
    {
      this.map = map;
      console.log("map set!")
    }
  }
}
```

```flatmap.ts
module WrtGame {
  export class FlatMap extends Map {
    constructor()
    {
      super(true);
    }
  }
}
```

meteorで実行してみてください。JavaScriptコンソールでエラーが起きるはずです。以下はJSにコンパイルされたflatmap.jsの実行結果。

![スクリーンショット 2015-03-26 8.18.31.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/85c7a848-93e0-d8ab-991c-cd157f1ee2b8.jpeg "スクリーンショット 2015-03-26 8.18.31.jpg")

内部モジュールである`WrtGame`は実際はvar WrtGameというオブジェクトなんですが、これがやはり無名関数で包まれています。map.jsも同様です。
つまり、flatmap.jsとmap.jsの内部モジュールがそれぞれ **別物** になっているため、クラス間の継承動作がうまく動作しないのです。
困りましたね。

## 今私がとっている暫定回避策

さて、困り果てた私が暫定的にやっている回避策がこれです。

```__wrtgame.ts
// WrtGameモジュールの作成
module WrtGame {
  export class foooooooo{} // ダミークラス
}
interface Window {
  WrtGame: any;
}
window.WrtGame = WrtGame;
```

```_map.ts
module WrtGame {
  eval('WrtGame = _.isUndefined(window.WrtGame) ? WrtGame : window.WrtGame;'); // 内部モジュールを複数ファイルで共有するためのハック
  export class Map {
    protected map:any;
    constructor(isCalledFromChild:boolean) {
      if(!isCalledFromChild) {
        throw new Error("This class is a abstract class.");
      }
    }

    public setMap(map:any)
    {
      this.map = map;
      console.log("map set!")
    }
  }
}
```

```flatmap.ts
module WrtGame {
  eval('WrtGame = _.isUndefined(window.WrtGame) ? WrtGame : window.WrtGame;'); // 内部モジュールを複数ファイルで共有するためのハック
  export class FlatMap extends Map {
    constructor()
    {
      super(true);
    }
  }
}
```

先ほどとの違いは、まず、__wrtgame.tsというファイルが追加されています。そこでは内部モジュールWrtGameをwindowオブジェクトに代入しています。次に、各tsファイルのモジュール内先頭に

```javascript
eval('WrtGame = _.isUndefined(window.WrtGame) ? WrtGame : window.WrtGame;'); // 内部モジュールを複数ファイ
```

（変数window.WrtGameのundefinedチェックにUnderscore.jsライブラリを使っています）

というハックコードがあることです。コンパイル後のJavaScriptコードを見てみましょう。

```flatmap.js
(function(){var __extends = this.__extends || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
var WrtGame;
(function (WrtGame) {
    eval('WrtGame = _.isUndefined(window.WrtGame) ? WrtGame : window.WrtGame;');
    var FlatMap = (function (_super) {
        __extends(FlatMap, _super);
        function FlatMap() {
            _super.call(this, true);
        }
        return FlatMap;
    })(WrtGame.Map);
    WrtGame.FlatMap = FlatMap;
})(WrtGame || (WrtGame = {}));
//# sourceMappingURL=game_flat_map.js.map

})();
```

関数`function (WrtGame)`の開始直後に、引数WrtGameにevalで無理やりwindow.WrtGameを代入しています。
こうすることで、すべてのコンパイル後のjsファイルで、内部モジュールの共有が行われ、正常動作することになります。

あ、ちなみに注意点として、読み込まれるファイルの順番が重要です。
`__wrtgame.ts`、`_map.ts`、`flatmap.ts`の順番で読み込まれる必要があるので、ファイル名先頭にアンダースコアを入れています（meteorは基本的にはアルファベット順でファイルをロードするため）。

## 最後に

いかがだったでしょうか。すっごい香ばしいですよね。イケてないですよね（汗）
私だって他に良い方法あったら知りたいよ！ evalなんて使いたくないよ！（涙）

ということで、おそらくもっと良いマトモなやり方絶対あるはずです。誰か教えてください(*´σｰ｀)