Meteor Typescript – Classes and Modules

Typescript is slowly making its name, when only recently Google announced joning forces with Microsoft in creating Typescript 1.5 as the main scripting language for Angular 2.0. Also, Webstorm 10 now supports typescript 1.4 and make Typescript programming a breeze.

Meteor is making it particulary difficult to use typescript as the scripting language due to the following reasons:

  1. The load order of javascript files
  2. Isolation of the code within defined files with var

Imagine following scenario with two classes belonging into the same module, split across two files:

//file:foo.ts
module Hugo {
  export class Foo {
    fooMe():string {
      return 'foo'
    }
  }
}

and

/// <reference path="foo.ts"/>
module Hugo {
 export class Bar extends Foo {
    bar () : string {
      return 'bar1';
    }
  }
}

Load Order

While previous example compiles typescript just fine, you will end up with the following error in the console:

TypeError: Cannot read property 'prototype' of undefined
at __extends (app/models/bar.js:5:21)

I would love to tell you that there exists an automatic method of solving these issues, but till Meteor releases a more sophisticated build mechanism, we are either depending on some dirty hacks, or as I prefer to do this, is to check the current load order and adjust my application accordingly. If you do not mind to do this, you are down to three options.

Option 1: Flat Directory Structure

With the flat directory structure, you depend on alphabetical ordering of loaded files, and therefore you rename foo.ts to _foo.ts so that it will load before bar.ts.

Option 2: Hierarchical Directories

Meteor (for some reason) loads first the child directories, so you can place the foo.ts into a sub-directory, e.g. /dependencies/foo.ts.

Option 3: Libs

Since meteor loads lib directories first, you can place foo.ts into the lib directory and your problem is solved.

All three options suffer from major problem, when in complex projects it becomes very difficult to maintain.

Modules

I have spent significant amount of time trying to figure out the best way of dealing with modules and classes across multiple files. The problem lies with Typescript compiler automatically emitting the var ModuleName making the module private to the given script file. Following is the compiled code for the Foo class:

var Hugo;
(function (Hugo) {
    var Foo = (function () {
        function Foo() {
        }
        Foo.prototype.fooMe = function () {
            return 'fooMe1';
        };
        return Foo;
    })();
    Hugo.Foo = Foo;
})(Hugo || (Hugo = {}));

Finding inspiration on StackOverflow we can easily deal with exporting the module to the global namespace by using this.Hugo = Hugo leading to the following source:

//file:foo.ts
module Hugo {
  export class Foo {
    fooMe():string {
      return 'foo'
    }
  }
}
this.Hugo = Hugo;

Yet, we still keep receiving the Cannot read property 'prototype' of undefined error. The reason for this is, that var Hugo is also emitted into the compiled Bar.js file, ignoring the value in this.Hugo and using empty object {} in the constructor.

var Hugo; // ignores this.Hugo
(function (Hugo) {
    var Bar = (function (_super) {
        __extends(Bar, _super);
        function Bar() {
            _super.apply(this, arguments);
        }
        Bar.prototype.bar = function () {
            return 'bar1';
        };
        return Bar;
    })(Hugo.Foo);
    Hugo.Bar = Bar; // Hugo here is {}
})(Hugo || (Hugo = {}));

Solution

I have checked several options of how to deal with this problem, among which was creating external modules with commonjs and importing them with mrt:exports package, but that solution was not producing any good results. Therefore, I decided to use the evil eval command and redefine the module variable before it gets redefined again. Here is the code:

eval('var Hugo = (this.Hugo || (this.Hugo = {})'); // this will override the automatically emitted var Hugo and assigns it with globally defined Hugo module 

module Hugo {
  export class Foo {
    fooMe():string {
      return 'foo'
    }
  }
}
/// <reference path="lib/foo.ts"/>
eval('var Hugo = (this.Hugo || (this.Hugo = {})'); // this will override the automatically emitted var Hugo and assigns it with globally defined Hugo module 

module Hugo {
 export class Bar extends Foo {
    bar () : string {
      return 'bar1';
    }
  }
}

While neither of these solutions are particularly pretty, they allow to confortably use the Typescript within Meteor, and once Meteor releases more sophisticated support for load ordering anf modules, it can be easily replaced.

If you have any better solution please share it with me.

Advertisements