TypeScript: Internal vs External Modules
Typescript feels as a language that can offers quite a few things. There is however one thing that I had a hard time with: Modules.
It looks simple at first, but when you look carefully you will see that it is easy to get wrong.
In this post I will show you what I mean and give a few tips on how to use Typescript modules correctly in your own applications. <!-- more -->
Typescript supports two modes in which you can apply modules. The first one is internal modules. Typescript allows you to define modules within your typescript files. The syntax is very similar to namespaces in Java and .NET and looks like this:
Things that are exported from a module, like the class you see in the sample above, are exposed as public properties of the module object when compiled. It's a bit like the public classes in a namespace in Java or .NET, except that in Typescript you cannot import classes from a module into the local scope like you would in C# or Java. Instead you define what's called a shortcut to types in module.
You can define internal modules in a single file or multiple files. When you write file1.ts and add a module A, you can define it again in file2.ts When you compile both files they will try to merge the exported objects into the existing namespace or define the namespace when it doesn't exist yet.
Actually, when you are running Typescript 1.6 or later you can replace the module syntax with the namespace syntax that is supported by the new Ecmascript 6 standard. I personally found this both good and bad. I understand that the team had to maintain backwards compatibility for internal modules. But I also wanted the team to remove the internal module support to make things more clear. I guess this is the life of a young programming language.
Internal modules are useful when you like to work with namespaces. And they have their uses as you will see later. External modules can also be used when you build a program using typescript. The use for external modules is however quite different from internal modules.
While internal modules can be seen as namespaces for your code, external modules must be viewed as ... well modules.
NodeJS uses modules to keep groups of classes together and while it is tempting to view them as namespaces they are in comparison to C# and Java a very different beast.
The way you define external modules is also very different from the internal module syntax. You don't use the module syntax. Instead you define things in your file that you want to export.
This does not do a lot, you need to use
tsc --module commonjs myfile.ts to compile the file in such a way that it is compatible with the NodeJS module syntax.
If you want to use RequireJS you have to use yet another syntax to compile the file
tsc --module amd myfile.ts so that a different syntax is generated that is
compatible with RequireJS.
External modules can be imported using the
var mymodule = require('./mymodule') syntax. This is very similar to what NodeJS uses natively.
When you use RequireJS you will see that the require statement gets translated to the correct syntax for that type of module loader. The same goes for the System module loader.
Two flavors, two different goals
So Typescript provides two different kinds of modules and the naming is kind of confusing when you haven't used Typescript before. This begs the question, what should you use when? Well, it really depends on what you want. And you can even combine the two together if you like.
I think you should keep things simple for yourself, otherwise you will run into big trouble at some point in the future.
Internal modules are really namespaces, which is model that is most often applied in the browser. There's a big need for namespaces in a web application since everything naturally gets loaded into the window scope. The window scope can turn into a massive pile of variables if you don't manage your scripts carefully. This is where internal modules or namespaces really shine.
On the other hand if you're building a NodeJS application you have to work with modules in the traditional sense. Every file is its own module and there is no global scope to worry about. When you import a module you automatically assign it to a well known variable. It's logical to use external modules for this scenario.
You can use internal modules in NodeJS applications, but it complicates things greatly. This kind of unnecessary complexity could kill productivity and maintainability in no time. Same goes for using external modules in the browser.
RequireJS is invented to be used from the browser, but quite hard to get right. First you have to tell requireJS where to find the main script in a script tag that looks like this:
What this does is download the app.js file separately from the server. This incurs an extra HTTP request you really don't need to get everything going. The best and probably the best way to get your application scripts fast and easy is by using Grunt or Gulp to concatenate and minify the application scripts. That way the download time is shorter and you perform just one HTTP request to get all the scripts you need. The website is more suitable for mobile when you do this and you have far fewer problems debugging the thing (That is if you use a sourcemap to get to the original sources for debugging).
Which way to go from here?
Where to from here? I think that if you use Typescript you should learn to use the namespace syntax instead of the module syntax to separate clearly between internal and external modules. Because internal modules are namespaces and external modules are just modules.
Don't mix and match module types, it will make your code very hard to read for beginner developers and may lead to questions with more experienced developers as well.
As they like to say these days: "This is Bill, he uses modules in NodeJS apps and namespaces in web apps. Bill is Smart. Be like Bill."