I’m building a web application with Node.js and typescript for the first time and have discovered some “fun” quirks with how Node.js modules and typescript modules interact with each other. I use the term interact loosely as they don’t actually talk to each other, and in fact each do their own separate thing. In this post, I’ll offer some tips on how I untangled this.
Node.js modules are based on the CommonJS modules architecture, using require and exports. Typescript has language level support for modules, which are basically glorified namespaces. Typescript also has support for AMD, which is yet another module loading specification. Each of these patterns all create a very confusing Javascript ecosystem.
Tying together Typescript’s module system and Node.js requires a bit of glue code. First up you need to ensure that you’ve told typescript to allow the exports and require keywords. This can be easily done by going to this helpful library of typescript module definitions and download node.d.ts. Next up import the definition into your ts file by putting this line at the top. We put the node.d.ts in a sub folder called typescript-node-definitions.
///<reference path='typescript-node-definitions/node.d.ts'/>
After that exports and require should now be recognised by the Typescript compiler. You should be able to use nodejs modules by calling require inside your ts files. And you can export with ease by doing at the end of your ts file:
exports.HomeController = MyWebSite.HomeController;
You can then reference the compiled js file normally in your main file:
var MyWebSite = require('./HomeController.js');
Most likely, you’ll have several files/classes that need to be under the same namespace, so a helper method can be used to corral them under the same object namespace.
[js]
function requireall() {
var cns = { };
for (var i = 0; i < arguments.length; i++) {
var ns = require(arguments[i]);
for (var o in ns) {
cns[o] = ns[o];
}
}
return cns;
}
[/js]
And replace require with
var MyWebSite = requireall('./HomeController.js', './AboutController.js');
Trouble occurs when you try to make one class per file. You have your module distributed off multiple ts files and one of them is a sub class which references a base class. You can add a reference to the base class:
///<reference path="BaseController.ts"/>
This satisfies tsc, but when you run the node you’ll get an error:
__.prototype = b.prototype; ^ TypeError: Cannot read property 'prototype' of undefined
That’s because node.js has no concept of typescript references. We need to use require to import the other file. Unfortunately, the module namespace we need to import into is recognised by the typescript as the module and any attempt to assign to it will result in a compiler error. This is where things need to get a bit hacky. We need to inject the base class into the namespace without triggering the compiler alarm. Underhandedly, we can use eval to achieve this. Put this under the module blabla { line and modify the __importClassName and __importModuleName variables:
var __importClassName = "HomeController"; var __importModuleName = "MyWebSite"; eval(__importModuleName + "." + __importClassName + " = require(\"./" + __importClassName + ".js\")." + __importClassName + ";");
This will allow node.js to resolve the base class and the whole thing to run!
Has this been corrected?
I don’t think it’s been corrected, although the latest versions have support for CommonJS modules. I think the expectation is that you have one compiled js file per module/namespace, so if you configure the typescript compiler to compile all your separate ts files of the same namespace into one js file, it will work fine, but not if they output to separate js files.