TypeScript implements external modules that are closely aligned with those proposed for ECMAScript 6 and supports code generation targeting CommonJS and AMD module systems.
NOTE: TypeScript currently doesn't support the full proposed capabilities of the ECMAScript 6 import and export syntax. We expect to align more closely on the syntax as the ECMAScript 6 specification evolves.
A TypeScript program consists of one or more source files that are either implementation source files or
declaration source files. Source files with extension .ts
are ImplementationSourceFiles containing
statements and declarations. Source files with extension .d.ts
are DeclarationSourceFiles containing
declarations only. Declaration source files are a strict subset of implementation source files.
SourceFile:
ImplementationSourceFile
DeclarationSourceFile
ImplementationSourceFile:
ImplementationElements(opt)
ImplementationElements:
ImplementationElement
ImplementationElements ImplementationElement
ImplementationElement:
ModuleElement
ExportAssignment
export(opt) ExternalImportDeclaration
export(opt) AmbientDeclaration
DeclarationSourceFile:
DeclarationElements(opt)
DeclarationElements:
DeclarationElement
DeclarationElements DeclarationElement
DeclarationElement:
export(opt) InterfaceDeclaration
export(opt) ImportDeclaration
ExportAssignment
export(opt) ExternalImportDeclaration
export(opt) AmbientDeclaration
When a TypeScript program is compiled, all of the program's source files are processed together. Statements and declarations in different source files can depend on each other, possibly in a circular fashion. By default, a JavaScript output file is generated for each implementation source file in a compilation, but no output is generated from declaration source files.
The source elements permitted in a TypeScript implementation source file are a superset of those supported by JavaScript. Specifically, TypeScript extends the JavaScript grammar's existing VariableDeclaration (section 5.1) and FunctionDeclaration (section 6.1) productions, and adds InterfaceDeclaration (section 7.1), ClassDeclaration (section 8.1), EnumDeclaration (section 9.1), ModuleDeclaration (section 10.1), ImportDeclaration (section 10.3), ExternalImportDeclaration (section 11.2.2), ExportAssignment (section 11.2.4), and AmbientDeclaration (section 12.1) productions.
Declaration source files are restricted to contain declarations only. Declaration source files can be used to declare the static type information associated with existing JavaScript code in an adjunct manner. They are entirely optional but enable the TypeScript compiler and tools to provide better verification and assistance when integrating existing JavaScript code and libraries in a TypeScript application.
Implementation and declaration source files that contain no import or export declarations form the single
global module. Entities declared in the global module are in scope everywhere in a program. Initialization
order of the source files that make up the global module ultimately depends on the order in which the
generated JavaScript files are loaded at run-time (which, for example, may be controlled by <script/>
tags
that reference the generated JavaScript files).
Implementation and declaration source files that contain at least one external import declaration, export assignment, or top-level exported declaration are considered separate external modules. Entities declared in an external module are in scope only in that module, but exported entities can be imported into other modules using import declarations. Initialization order of external modules is determined by the module loader being and is not specified by the TypeScript language. However, it is generally the case that non-circularly dependent modules are automatically loaded and initialized in the correct order.
External modules can additionally be declared using AmbientModuleDeclarations in the global module that directly specify the external module names as string literals. This is described further in section 12.1.6.
The TypeScript compiler automatically determines a source file's dependencies and includes those dependencies in the program being compiled. The determination is made from "reference comments" and external import declarations as follows:
- A comment of the form
/// <reference path="..."/>
adds a dependency on the source file specified in the path argument. The path is resolved relative to the directory of the containing source file. - An external import declaration that specifies a relative external module name (section 11.2.1)
resolves the name relative to the directory of the containing source file. If a source file with the
resulting path and file extension
.ts
exists, that file is added as a dependency. Otherwise, if a source file with the resulting path and file extension.d.ts
exists, that file is added as a dependency. - An external import declaration that specifies a top-level external module name (section 11.2.1)
resolves the name in a host dependent manner (typically by resolving the name relative to a
module name space root or searching for the name in a series of directories). If a source file with
extension
.ts
or.d.ts
corresponding to the reference is located, that file is added as a dependency.
Any files included as dependencies in turn have their references analyzed in a transitive manner until all dependencies have been determined.
External modules are separately loaded bodies of code referenced using external module names. External modules can be likened to functions that are loaded and executed once to initialize their associated module instance. Entities declared in an external module are private and inaccessible elsewhere unless they are exported.
External modules are written as separate source files that contain at least one external import declaration, export assignment, or top-level exported declaration. Specifically, if a source file contains at least one
- ExternalImportDeclaration,
- ExportAssignment,
- top-level exported VariableDeclaration,
- top-level exported FunctionDeclaration,
- top-level exported ClassDeclaration,
- top-level exported InterfaceDeclaration,
- top-level exported EnumDeclaration,
- top-level exported ModuleDeclaration,
- top-level exported ImportDeclaration, or
- top-level exported AmbientDeclaration,
that source file is considered an external module; otherwise, the source file is considered part of the global module.
Below is an example of two external modules written in separate source files.
File main.ts:
import log = require("./log");
log.message("hello");
File log.ts:
export function message(s: string) {
console.log(s);
}
The import declaration in the main
module references the log
module and compiling the main.ts
file
causes the log.ts
file to also be compiled as part of the program. At run-time, the import declaration
loads the log
module and produces a reference to its module instance through which it is possible to
reference the exported function.
TypeScript supports two patterns of JavaScript code generation for external modules: The CommonJS Modules pattern (section 11.2.5), typically used by server frameworks such as node.js, and the Asynchronous Module Definition (AMD) pattern (section 11.2.6), an extension to CommonJS Modules that permits asynchronous module loading, as is typical in browsers. The desired module code generation pattern is selected through a compiler option and does not affect the TypeScript source code. Indeed, it is possible to author external modules that can be compiled for use both on the server side (e.g. using node.js) and on the client side (using an AMD compliant loader) with no changes to the TypeScript source code.
External modules are identified and referenced using external module names. The following definition is aligned with that provided in the CommonJS Modules 1.0 specification.
- An external module name is a string of "terms" delimited by forward slashes.
- External module names may not have file-name extensions like ".js".
- External module names may be "relative" or "top-level". An external module name is "relative" if the first term is "." or "..".
- Top-level names are resolved off the conceptual module name space root.
- Relative names are resolved relative to the name of the module in which they occur.
For purposes of resolving external module references, TypeScript associates a file path with every external
module. The file path is simply the path of the module's source file without the file extension. For
example, an external module contained in the source file C:\src\lib\io.ts
has the file path C:/src/lib/io
and an external module contained in the source file C:\src\ui\editor.d.ts
has the file path C:/src/ui/editor
.
An external module name in an import declaration is resolved as follows:
- If the import declaration specifies a relative external module name, the name is resolved relative
to the directory of the referencing module's file path. The program must contain a module with
the resulting file path or otherwise an error occurs. For example, in a module with the file path
C:/src/ui/main
, the external module names./editor
and../lib/io
reference modules with the file pathsC:/src/ui/editor
andC:/src/lib/io
. - If the import declaration specifies a top-level external module name and the program contains an AmbientExternalModuleDeclaration (section 12.1.6) with a string literal that specifies that exact name, then the import declaration references that ambient external module.
- If the import declaration specifies a top-level external module name and the program contains no AmbientExternalModuleDeclaration (section 12.1.6) with a string literal that specifies that exact name, the name is resolved in a host dependent manner (for example by considering the name relative to a module name space root). If a matching module cannot be found an error occurs.
External import declarations are used to import external modules and create local aliases by which they may be referenced.
ExternalImportDeclaration:
import Identifier = ExternalModuleReference ;
ExternalModuleReference:
require ( StringLiteral )
The string literal specified in an ExternalModuleReference is interpreted as an external module name (section 11.2.1).
An external import declaration introduces a local identifier that references a given external module. The local identifier becomes an alias for, and is classified exactly like, the entity or entities exported from the referenced external module. Specifically, if the referenced external module contains no export assignment the identifier is classified as a module, and if the referenced external module contains an export assignment the identifier is classified exactly like the entity or entities named in the export assignment.
An external module that contains no export assignment (section 11.2.4) exports an entity classified as a module. Similarly to an internal module, export declarations (section 10.4) in the external module are used to declare the members of this entity.
Unlike a non-instantiated internal module (section 10.1), an external module containing only interface types and non-instantiated internal modules still has a module instance associated with it, albeit one with no members.
If an external module contains an export assignment it is an error for the external module to also contain export declarations. The two types of exports are mutually exclusive.
An export assignment designates a module member as the entity to be exported in place of the external module itself.
ExportAssignment:
export = Identifier ;
When an external module containing an export assignment is imported, the local alias introduced by the external import declaration takes on all meanings of the identifier named in the export assignment.
It is an error for an external module to contain more than one export assignment.
Assume the following example resides in the file point.ts
:
export = Point;
class Point {
constructor(public x: number, public y: number) { }
static origin = new Point(0, 0);
}
When point.ts
is imported in another external module, the import alias references the exported class and
can be used both as a type and as a constructor function:
import Pt = require("./point");
var p1 = new Pt(10, 20);
var p2 = Pt.origin;
Note that there is no requirement that the import alias use the same name as the exported entity.
The CommonJS Modules definition specifies a methodology for writing JavaScript modules with implied
privacy, the ability to import other modules, and the ability to explicitly export members. A CommonJS
compliant system provides a require
function that can be used to synchronously load other external
modules to obtain their singleton module instance, as well as an exports
variable to which a module can
add properties to define its external API.
The main
and log
example from section 11.2 above generates the following JavaScript code when
compiled for the CommonJS Modules pattern:
File main.js:
var log = require("./log");
log.message("hello");
File log.js:
exports.message = function(s) {
console.log(s);
}
An external import declaration is represented in the generated JavaScript as a variable initialized by a call
to the require
function provided by the module system host. A variable declaration and require
call is
emitted for a particular imported module only if the imported module, or a local alias (section 10.3) that
references the imported module, is referenced as a PrimaryExpression somewhere in the body of the
importing module. If an imported module is referenced only as a ModuleName or TypeQueryExpression,
nothing is emitted.
An example:
File geometry.ts:
export interface Point { x: number; y: number };
export function point(x: number, y: number): Point {
return { x: x, y: y };
}
File game.ts:
import g = require("./geometry");
var p = g.point(10, 20);
The game
module references the imported geometry
module in an expression (through its alias g
) and
a require
call is therefore included in the emitted JavaScript:
var g = require("./geometry");
var p = g.point(10, 20);
Had the game
module instead been written to only reference geometry
in a type position
import g = require("./geometry");
var p: g.Point = { x: 10, y: 20 };
the emitted JavaScript would have no dependency on the geometry
module and would simply be
var p = { x: 10, y: 20 };
The Asynchronous Module Definition (AMD) specification extends the CommonJS Modules specification
with a pattern for authoring asynchronously loadable modules with associated dependencies. Using the
AMD pattern, modules are emitted as calls to a global define
function taking an array of dependencies,
specified as external module names, and a callback function containing the module body. The global
define
function is provided by including an AMD compliant loader in the application. The loader arranges
to asynchronously load the module's dependencies and, upon completion, calls the callback function
passing resolved module instances as arguments in the order they were listed in the dependency array.
The "main" and "log" example from above generates the following JavaScript code when compiled for the AMD pattern.
File main.js:
define(["require", "exports", "./log"], function(require, exports, log) {
log.message("hello");
}
File log.js:
define(["require", "exports"], function(require, exports) {
exports.message = function(s) {
console.log(s);
}
}
The special require
and exports
dependencies are always present. Additional entries are added to the
dependencies array and the parameter list as required to represent imported external modules. Similar to
the code generation for CommonJS Modules, a dependency entry is generated for a particular imported
module only if the imported module is referenced as a PrimaryExpression somewhere in the body of the
importing module. If an imported module is referenced only as a ModuleName, no dependency is
generated for that module.