Building heterogeneous TypeScript libraries

Updated: 2014-09-01
Published: 2012-09-21

A technique for compiling one or more TypeScript source files to a single JavaScript library file that can be used in both the browser and by Node.js applications.


By way of example

Our library source consists of a single TypeScript module called Lib spread across multiple source files (lib1.ts and lib2.ts) that exports a public API:

lib1.ts

module Lib {
  export function f() {}
}

declare var exports: any;
if (typeof exports != 'undefined') {
  exports.f = Lib.f;
}

lib2.ts

module Lib {
  export var v = {foo: 42};
}

declare var exports: any;
if (typeof exports != 'undefined') {
  exports.v = Lib.v;
}

The source file are compiled to a single JavaScript library lib.js using the TypeScript compiler:

tsc --out lib.js lib1.ts lib2.ts

lib.js

var Lib;
(function (Lib) {
    function f() {
    }
    Lib.f = f;
})(Lib || (Lib = {}));
if(typeof exports != 'undefined') {
    exports.f = Lib.f;
}
var Lib;
(function (Lib) {
    Lib.v = {
        foo: 42
    };
})(Lib || (Lib = {}));
if(typeof exports != 'undefined') {
    exports.v = Lib.v;
}

lib.js file now can be included on an HTML page with:

<script type="text/javascript" src="lib.js"></script>

Or in a Node.js application with:

var Lib = require('./lib.js');

The APIs are accessed via the module name e.g. Lib.f(), Lib.v.

Explanatory notes

The key to being able to import the code into Node.js with require('./lib.js') is conditionally assigning public API objects to properties of the global exports object e.g.

declare var exports: any;
if (typeof exports != 'undefined') {
  exports.f = Lib.f;
}
  • exports is a global object defined by CommonJS compatible loaders such as Node’s.
  • This code must be placed at the end of the source file outside the module declaration.
  • This code will not be executed in the browser (or any non-CommonJS environment) because exports is not defined.
  • To minimize browser global namespace pollution all source is enveloped in a single open module (Lib) — this is not necessary in a module loader environment (e.g. Node.js).
  • /// <reference path="..."/> directives are unnecessary because all files are included in the single compile command.
  • Multi-file “open” modules are not truly open — you must export any module objects that need to be accessed across file boundaries.
  • TypeScript can emit CommonJS modules directly by prefixing module with the export keyword and using the compiler --module commonjs option, but there are two problems with this approach:
    1. The generated code is not browser compatible.
    2. Exported (external) modules must reside in a single source file (they are not open).

References

About these ads

One Response to “Building heterogeneous TypeScript libraries”

  1. Robbie McDiarmid Says:

    Thanks a lot. I’ve been looking all over for a solution like this.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: