Making a RequireJS managed library dependency explicit on a TypeScript file

Topics: General, Language Specification
Apr 5, 2013 at 10:10 PM
The problem is using an external library (ie. jquery) on a TypeScript file while making the dependency explicit for RequireJS.

That is, that the generated code includes something like:
define(['require', 'jquery'], function() {...})
This has been asked multiple times, and there are various sort of answers. The cleanest way I found to do it for now is by writing something like:
/// <reference path="../definitions/jquery.d.ts"/>
/// <amd-dependency path="jquery"/>
Using the undocumented amd-dependency thing (which is quite useful for other scenarios, BTW, like importing css! & text! modules).

The problem with that approach is that:
a) You're repeating yourself.
b) The reference tag is 'inherited' while importing a module that already contains it. That is, if you do import utils = module('utils') and utils.ts had a <reference> to jquery, you don't need to add another <reference> at the module. That's a bad thing IMO because you might forget to add the explicit amd-reference. I want to have to explicitly require it and then be included in the require dependencies.

Are there any plans to tackle this issue? Or is there a right way to do it with the current specification?

Maybe having something like <amd-reference> solves the issue by making the reference available only on the current module and also working as the current <amd-dependency>....
Apr 8, 2013 at 9:10 PM
I think the key is picking a consistent approach and sticking to it. I prefer the syntax of internal modules, but I want the flexibility of loading using AMD, so I am using amd-dependency in the following manner:

Consider the following project layout:

/BusinessDomain
---/BusinessDomain.d.ts
--/Order.ts
--/Customer.ts
/UserModel
--/UserDomain.d.ts
--/User.ts
--/Account.ts
/Namespaces
--/BusinessDomain.ts
--/UserModel.ts
App.ts

Inside the library implementation folders (BusinessDomain and UserModel) I create 1 file per class/interface. The d.ts file for the library contains a reference to each file in the library, and each file is defined as an internal module like this:

Order.ts
/// <reference path="BusinessDomain.d.ts" />

module BusinessDomain {

    export class Order {
    ....
    }
    
}
Customer.ts
/// <reference path="BusinessDomain.d.ts" />

module BusinessDomain {

    export class Customer{
    ....
    }
    
}
BusinessDomain.d.ts
/// <reference path="Customer.ts" />
/// <reference path="Order.ts" />
Namespaces/BusinessDomain.ts
/// <amd-dependency path="../BusinessDomain/Order"/>
/// <amd-dependency path="../BusinessDomain/Customer"/>

export module BusinessObjects {

}
App.ts
/// <reference path="BusinessObjects/BusinessObjects.d.ts"/>
/// <amd-dependency path="Namespaces/BusinessObjects"/>

export module App {
    var order = new BusinessObjects.Order();
}
Apr 9, 2013 at 3:25 AM
I like your approach and I have some further questions:
  • Which of the files do you really have to write your self and witch are generated or compiled? (for example the d.ts file)
  • How the file Namespaces/BusinessDomain.ts looks like?
  • Could you provide a little sample project which is describing your approach more deeply?
Many thanks in advance.
Apr 9, 2013 at 5:04 PM
Interesting approach! I think that's a good way to support 1 class per file modules, while still being able to use AMD to load the whole module. Though it adds all the definitions as globals, it's a good way to encourage using the same module name everywhere instead of having import foo = module('order') in a place and import bar = module('order') in another.

However, it also highlights what I was complaining about (though in the context of TS libraries instead of external libraries as in my original post).

Suppose that on Order.ts you add a reference to an external library (libA) that you want to use there. So you'll probably write something like:
/// <reference path="BusinessDomain.d.ts" />
/// <reference path="libA.d.ts" />
/// <amd-reference path="libA" />

module BusinessDomain {
    export class Order {
    ....
    }    
}
Here you:
a) Are repeating yourself. Nothing too bad, but probably the language could help you here, to make these uses more straightforward and to encourage one way of doing it. We don't want every single team defining their way to handle & structure modules, that's what we had with JS. A clear guideline encouraged by the language design would be a plus, IMO.

b) libA is now also available on App, though App didn't include it explicitly (you could go to App and do a new libA.MyClass()). I don't like that. The fact that Order used libA is an implementation detail, if I happen to use libA on App and later no longer use it from Order, my code will break (at compile time, thankfully). I would prefer it to be hidden and be forced to include libA on App if I need to use that library on App. The way to do that while hiding it is by using an import instead, but then we go back to my original post, about how to handle it for non RequireJS third-party libraries like jQuery.

By the way, your approach also highlights than having a class per file, where all the classes belong to the same module, and using only the import syntax, doesn't really work, without manual work, which is a shame. The only way I can think of doing it is by:
moduleA.ts:
import classA = module('classA')
export var ClassA = classA.ClassA;
.... (same thing for every other class)
That would let you have clases internal to the module though ;).
Apr 9, 2013 at 6:11 PM
Edited Apr 9, 2013 at 6:14 PM
Re: @idev71

Which of the files do you really have to write your self and witch are generated or compiled? (for example the d.ts file)

I am developing a visual studio extension that generates TypeScript classes and interfaces from C# using the Roslyn CTP, so in my example BusinessDomain and UserModel would be projects inside a solution that contain classes or interfaces marked with a custom attribute. The tool then generates the TypeScript and as well as reference files.

How the file Namespaces/BusinessDomain.ts looks like?

The file Namespaces/BusinessDomain.ts is an empty module, the sole purpose of the external module BusinessObjects is to load the components of the internal module into the global namespace.

Could you provide a little sample project which is describing your approach more deeply?

I do have a sample solution inside the source tree for my visual studio extension. I am in the process of getting the project approved by my employer to be released as open source under the Apache 2.0 license. I expect to be able to push the current source code and a preview build sometime in the next week or so.

There is a project page for the Visual Studio extension at http://typesharp.codeplex.com, I would suggest following this project if you are interested and I can update this thread when the current source code is available.
Apr 9, 2013 at 6:36 PM
Edited Apr 9, 2013 at 6:36 PM
RE: @saguiar

Suppose that on Order.ts you add a reference to an external library (libA) that you want to use there. So you'll probably write something like ...

As I mentioned in my reply to @idev71 my example is based on what I am developing for my code generator tool.

I am currently handling references between libraries using the following methodology:
  1. Each class/interface implementation file within BusinessDomain only contains a reference to BusinessDomain.d.ts
  2. BusinessDomain.d.ts contains a reference for each file within BusinessDomain
  3. If there is a reference to a type in another library then the reference for that library is included in BusinessDomain.d.ts, so for example if we reference User from UserModel inside Order in BusinessDomain then BusinessDomain.d.ts would look like this:
/// <reference path="Customer.ts" />
/// <reference path="Order.ts" />
/// <reference path="../UserModel/UserModel.d.ts" />
  1. If a library contains external references, then when the external module reference is generated it will look like this:
/// <amd-dependency path="UserModel" />
/// <amd-dependency path="../BusinessDomain/Order"/>
/// <amd-dependency path="../BusinessDomain/Customer"/>

export module BusinessObjects {

}
By the way, your approach also highlights than having a class per file, where all the classes belong to the same module, and using only the import syntax, doesn't really work, without manual work, which is a shame. The only way I can think of doing it is by...

My issue with the current external module implementation is the clutter when importing modules, I don't want to have to do:
import foo = module("ModuleName")
var foobar = new foo.ModuleName.MyType(); // clutter here, I would rather type var foobar = new ModuleName.MyType()
My understanding is that TypeScript 0.9 is going to change how this works by allowing something like:
import ModuleName = module("ModuleName");
var foobar = new ModuleName.MyType()
However I'm unsure how this will work with multiple files per module since my understanding of the spec is that it will allow you to export a single class as the top level export.

So for now I agree that there is some duplication with references and amd-dependencies, however vs the other alternatives I find it to be the best approach for now.