はじめに
現状、こうやっているよ。という備忘録です。
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ファイルがあるとするじゃないですか。
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!")
}
}
}
module WrtGame {
export class FlatMap extends Map {
constructor()
{
super(true);
}
}
}
meteorで実行してみてください。JavaScriptコンソールでエラーが起きるはずです。以下はJSにコンパイルされたflatmap.jsの実行結果。
内部モジュールであるWrtGame
は実際はvar WrtGameというオブジェクトなんですが、これがやはり無名関数で包まれています。map.jsも同様です。
つまり、flatmap.jsとmap.jsの内部モジュールがそれぞれ 別物 になっているため、クラス間の継承動作がうまく動作しないのです。
困りましたね。
今私がとっている暫定回避策
さて、困り果てた私が暫定的にやっている回避策がこれです。
// WrtGameモジュールの作成
module WrtGame {
export class foooooooo{} // ダミークラス
}
interface Window {
WrtGame: any;
}
window.WrtGame = WrtGame;
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!")
}
}
}
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コードを見てみましょう。
(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なんて使いたくないよ!(涙)
ということで、おそらくもっと良いマトモなやり方絶対あるはずです。誰か教えてください(*´σー`)