2
votes

In our pre-existing codebase, we use the "classes are Functions and Functions are Objects" aspect of JavaScript to do namespacing.

E.g., suppose we have a class named Foo, that contains a class named Bar.

Foo is a globally exported object, (i.e, it exists at window.Foo), and window.Foo.Bar is the constructor for Bar. (I think it's fair to say that this is a common JavaScript pattern).

It's true that this pattern creates a directional dependency. Foo must be loaded before Bar. We accomplish this using stealjs, an asynchronous module loader that is not conformant with AMD protocol. For this reason, we cannot use TypeScript's built-in import/export AMD code generation. At least, not until we do several weeks worth of drudgery to port our entire code base to requirejs.

To match this pattern in TypeScript, we speak of a class named Foo, and also a module named Foo.

class Foo {
    /* Foo's properties */
}

module Foo {
    export class Bar {
    /* Bar's properties */
    }
}

This is legal TypeScript and it compiles to the JavaScript situation described above: the Foo class object will contain the Bar class object as its "Bar" property. The Foo module is said to merge with the Foo class, and Bar is said to be an inner class.

However, if I simply separate these two statements into separate files:

// Foo.ts
class Foo {
    /* Foo's properties */
}

// Foo/Bar.ts
module Foo {
    export class Bar {
    /* Bar's properties */
    }
}

This is no longer legal TypeScript, and it doesn't compile. [1] (This despite the fact that we could and would arrange for the second file to be loaded after the first file loads via our own module loader.)

This is a big problem for us, because we're not about to smoosh all of our inner class declarations into one file. As far as I can tell, there is actually no solution to this. We simply have to pollute the global namespace and cannot merge inner classes with our namespaces.

If we were using TS's built in import/export semantics this problem wouldn't come up, but we aren't, for the reason described above. Porting everything to requirejs is not on the table right now. Given our situation, what do you think is the best strategy for working around the class-module merging restriction?

[1]TypeScript: Module x cannot merge with previous declaration of x in a different file

1
Interestingly enough, this works in my IDE (IntelliJ) because it compiles each file separately (there is no interdependency between module Foo and class Foo). Could you come up with a similar strategy: independent compilation using a script then merging ?Bruno Grieder
You're right that typescript would "do the right thing" if only it would just compile. And, I think that's interesting. But, I think it's probably more work to hack the build than it is to hack around the namespace limitation. For now, we're going with the latter approach. Thanks for the suggestion, however. I do appreciate it!masonk

1 Answers

0
votes

You can switch to require.js AMD piece at a time rather than rewrite everything all at once. The easiest way to do that, in fact, is to rely on TypeScript's built-in AMD support, since it automates so much that you need to do and the export and import syntax in TypeScript is very easy to work with. Files that you don't add export or import statements to them in TypeScript get built as they currently are and files that use export/import become AMD modules. There is a bit of a learning curve to this as you will need to add a require config and start to use the require(['mytsamd'], function (mytsamd) { /* do stuff with mytsamd */ }) pattern (or convert that to a promise with something like Q.js) in non-TypeScript files that need to refer to AMD objects.