LoginSignup
9
10

More than 5 years have passed since last update.

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

Posted at

はじめに

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

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

TypeScript導入

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

npm install -g typescript

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

meteor add meteortypescript:compiler

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

meteor add meteortypescript:typescript-libs

書き方

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

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

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

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

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

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

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

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

例えば、以下の2つの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

内部モジュールである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ファイルのモジュール内先頭に

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.tsflatmap.tsの順番で読み込まれる必要があるので、ファイル名先頭にアンダースコアを入れています(meteorは基本的にはアルファベット順でファイルをロードするため)。

最後に

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

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

9
10
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
10