Project Configuration

Modules

A note about terminology:
It’s important to note that in TypeScript 1.5, the nomenclature has changed.
“Internal modules” are now “namespaces”.
“External modules” are now simply “modules”, as to align with ECMAScript 2015’s terminology, (namely that module X { is equivalent to the now-preferred namespace X {).

Introduction

Starting with ECMAScript 2015, JavaScript has a concept of modules. TypeScript shares this concept.

Modules are executed within their own scope, not in the global scope; this means that variables, functions, classes, etc. declared in a module are not visible outside the module unless they are explicitly exported using one of the export forms.
Conversely, to consume a variable, function, class, interface, etc. exported from a different module, it has to be imported using one of the import forms.

Modules are declarative; the relationships between modules are specified in terms of imports and exports at the file level.

Modules import one another using a module loader.
At runtime the module loader is responsible for locating and executing all dependencies of a module before executing it.
Well-known modules loaders used in JavaScript are the CommonJS module loader for Node.js and require.js for Web applications.

In TypeScript, just as in ECMAScript 2015, any file containing a top-level import or export is considered a module.
Conversely, a file without any top-level import or export declarations is treated as a script whose contents are available in the global scope (and therefore to modules as well).

Export

Exporting a declaration

Any declaration (such as a variable, function, class, type alias, or interface) can be exported by adding the export keyword.

Import the entire module into a single variable, and use it to access the module exports

Import a module for side-effects only

Though not recommended practice, some modules set up some global state that can be used by other modules.
These modules may not have any exports, or the consumer is not interested in any of their exports.
To import these modules, use:

import "./my-module.js";

Default exports

Each module can optionally export a default export.
Default exports are marked with the keyword default; and there can only be one default export per module.
default exports are imported using a different import form.

default exports are really handy.
For instance, a library like JQuery might have a default export of jQuery or $, which we’d probably also import under the name $ or jQuery.

JQuery.d.ts

declare let $: JQuery;
export default $;

App.ts

import $ from "JQuery";
$("button.continue").html( "Next Step..." );

Classes and function declarations can be authored directly as default exports.
Default export class and function declaration names are optional.

OneTwoThree.ts

Log.ts

export = and import = require()

Both CommonJS and AMD generally have the concept of an exports object which contains all exports from a module.

They also support replacing the exports object with a custom single object.
Default exports are meant to act as a replacement for this behavior; however, the two are incompatible.
TypeScript supports export = to model the traditional CommonJS and AMD workflow.

The export = syntax specifies a single object that is exported from the module.
This can be a class, interface, namespace, function, or enum.

When exporting a module using export =, TypeScript-specific import module = require("module") must be used to import the module.

Code Generation for Modules

Depending on the module target specified during compilation, the compiler will generate appropriate code for Node.js (CommonJS), require.js (AMD), UMD, SystemJS, or ECMAScript 2015 native modules (ES6) module-loading systems.
For more information on what the define, require and register calls in the generated code do, consult the documentation for each module loader.

This simple example shows how the names used during importing and exporting get translated into the module loading code.

Optional Module Loading and Other Advanced Loading Scenarios

In some cases, you may want to only load a module under some conditions.
In TypeScript, we can use the pattern shown below to implement this and other advanced loading scenarios to directly invoke the module loaders without losing type safety.

The compiler detects whether each module is used in the emitted JavaScript.
If a module identifier is only ever used as part of a type annotations and never as an expression, then no require call is emitted for that module.
This elision of unused references is a good performance optimization, and also allows for optional loading of those modules.

The core idea of the pattern is that the import id = require("...") statement gives us access to the types exposed by the module.
The module loader is invoked (through require) dynamically, as shown in the if blocks below.
This leverages the reference-elision optimization so that the module is only loaded when needed.
For this pattern to work, it’s important that the symbol defined via an import is only used in type positions (i.e. never in a position that would be emitted into the JavaScript).

To maintain type safety, we can use the typeof keyword.
The typeof keyword, when used in a type position, produces the type of a value, in this case the type of the module.

Working with Other JavaScript Libraries

To describe the shape of libraries not written in TypeScript, we need to declare the API that the library exposes.

We call declarations that don’t define an implementation “ambient”.
Typically, these are defined in .d.ts files.
If you’re familiar with C/C++, you can think of these as .h files.
Let’s look at a few examples.

Ambient Modules

In Node.js, most tasks are accomplished by loading one or more modules.
We could define each module in its own .d.ts file with top-level export declarations, but it’s more convenient to write them as one larger .d.ts file.
To do so, we use a construct similar to ambient namespaces, but we use the module keyword and the quoted name of the module which will be available to a later import.
For example:

Shorthand ambient modules

If you don’t want to take the time to write out declarations before using a new module, you can use a shorthand declaration to get started quickly.

declarations.d.ts

declare module "hot-new-module";

All imports from a shorthand module will have the any type.

import x, {y} from "hot-new-module";
x(y);

Wildcard module declarations

Some module loaders such as SystemJS
and AMD allow non-JavaScript content to be imported.
These typically use a prefix or suffix to indicate the special loading semantics.
Wildcard module declarations can be used to cover these cases.

UMD modules

Some libraries are designed to be used in many module loaders, or with no module loading (global variables).
These are known as UMD modules.
These libraries can be accessed through either an import or a global variable.
For example:

It can also be used as a global variable, but only inside of a script.
(A script is a file with no imports or exports.)

mathLib.isPrime(2);

Guidance for structuring modules

Export as close to top-level as possible

Consumers of your module should have as little friction as possible when using things that you export.
Adding too many levels of nesting tends to be cumbersome, so think carefully about how you want to structure things.

Exporting a namespace from your module is an example of adding too many layers of nesting.
While namespaces sometime have their uses, they add an extra level of indirection when using modules.
This can quickly become a pain point for users, and is usually unnecessary.

Static methods on an exported class have a similar problem - the class itself adds a layer of nesting.
Unless it increases expressivity or intent in a clearly useful way, consider simply exporting a helper function.

If you’re only exporting a single class or function, use export default

Just as “exporting near the top-level” reduces friction on your module’s consumers, so does introducing a default export.
If a module’s primary purpose is to house one specific export, then you should consider exporting it as a default export.
This makes both importing and actually using the import a little easier.
For example:

MyLargeModule.ts

Consumer.ts

Re-export to extend

Often you will need to extend functionality on a module.
A common JS pattern is to augment the original object with extensions, similar to how JQuery extensions work.
As we’ve mentioned before, modules do not merge like global namespace objects would.
The recommended solution is to not mutate the original object, but rather export a new entity that provides the new functionality.

Consider a simple calculator implementation defined in module Calculator.ts.
The module also exports a helper function to test the calculator functionality by passing a list of input strings and writing the result at the end.

The new module ProgrammerCalculator exports an API shape similar to that of the original Calculator module, but does not augment any objects in the original module.
Here is a test for our ProgrammerCalculator class:

TestProgrammerCalculator.ts

Do not use namespaces in modules

When first moving to a module-based organization, a common tendency is to wrap exports in an additional layer of namespaces.
Modules have their own scope, and only exported declarations are visible from outside the module.
With this in mind, namespace provide very little, if any, value when working with modules.

On the organization front, namespaces are handy for grouping together logically-related objects and types in the global scope.
For example, in C#, you’re going to find all the collection types in System.Collections.
By organizing our types into hierarchical namespaces, we provide a good “discovery” experience for users of those types.
Modules, on the other hand, are already present in a file system, necessarily.
We have to resolve them by path and filename, so there’s a logical organization scheme for us to use.
We can have a /collections/generic/ folder with a list module in it.

Namespaces are important to avoid naming collisions in the global scope.
For example, you might have My.Application.Customer.AddForm and My.Application.Order.AddForm – two types with the same name, but a different namespace.
This, however, is not an issue with modules.
Within a module, there’s no plausible reason to have two objects with the same name.
From the consumption side, the consumer of any given module gets to pick the name that they will use to refer to the module, so accidental naming conflicts are impossible.