TypeScript

10      Internal Modules

An internal module is a named container of statements and declarations. An internal module represents both a namespace and a singleton module instance. The namespace contains named types and other namespaces, and the singleton module instance contains properties for the module’s exported members. The body of an internal module corresponds to a function that is executed once, thereby providing a mechanism for maintaining local state with assured isolation.

10.1 Module Declarations

An internal module declaration declares a namespace name and, in the case of an instantiated module, a member name in the containing module.

ModuleDeclaration:
module   IdentifierPath   {   ModuleBody   }

IdentifierPath:
Identifier
IdentifierPath   
.   Identifier

Internal modules are either instantiated or non-instantiated. A non-instantiated module is an internal module containing only interface types and other non-instantiated modules. An instantiated module is an internal module that doesn’t meet this definition. In intuitive terms, an instantiated module is one for which a module object instance is created, whereas a non-instantiated module is one for which no code is generated.

When a module identifier is referenced as a ModuleName (section 3.6.2) it denotes a container of module and type names, and when a module identifier is referenced as a PrimaryExpression (section 4.3) it denotes the singleton module instance. For example:

module M {
    export interface P { x: number; y: number; }
    export var a = 1;
}

var p: M.P;             // M used as ModuleName
var m = M;              // M used as PrimaryExpression
var x1 = M.a;           // M used as PrimaryExpression
var x2 = m.a;           // Same as M.a
var q: m.P;             // Error

Above, when ‘M’ is used as a PrimaryExpression it denotes an object instance with a single member ‘a’ and when ‘M’ is used as a ModuleName it denotes a container with a single type member ‘P’. The final line in the example is an error because ‘m’ is a variable which cannot be referenced in a type name.

If the declaration of ‘M’ above had excluded the exported variable ‘a’, ‘M’ would be a non-instantiated module and it would be an error to reference ‘M’ as a PrimaryExpression.

An internal module declaration that specifies an IdentifierPath with more than one identifier is equivalent to a series of nested single-identifier internal module declarations where all but the outermost are automatically exported. For example:

module A.B.C {
    export var x = 1;
}

corresponds to

module A {
    export module B {
        export module C {
            export var x = 1;
        }
    }
}

10.2 Module Body

The body of an internal module corresponds to a function that is executed once to initialize the module instance.

ModuleBody:
ModuleElementsopt

ModuleElements:
ModuleElement
ModuleElements   ModuleElement

ModuleElement:
Statement
exportopt   VariableDeclaration
exportopt   FunctionDeclaration
exportopt   ClassDeclaration
exportopt   InterfaceDeclaration
exportopt   EnumDeclaration
exportopt   ModuleDeclaration
exportopt   ImportDeclaration

Each module body has a declaration space for local variables (including functions, modules, class constructor functions, and enum objects), a declaration space for local named types (classes, interfaces, and enums), and a declaration space for local namespaces (containers of named types). Every declaration (whether local or exported) in a module contributes to one or more of these declaration spaces.

10.3 Import Declarations

Import declarations are used to create local aliases for entities in other modules.

ImportDeclaration:
import   Identifier   =   EntityName   ;

EntityName:
ModuleName
ModuleName   
.   Identifier

An EntityName consisting of a single identifier is resolved as a ModuleName and is thus required to reference an internal module. The resulting local alias references the given internal module and is itself classified as an internal module.

An EntityName consisting of more than one identifier is resolved as a ModuleName followed by an identifier that names one or more exported entities in the given module. The resulting local alias has all the meanings and classifications of the referenced entity or entities. (As many as three distinct meanings are possible for an entity name—namespace, type, and member.) In effect, it is as if the imported entity or entities were declared locally with the local alias name.

In the example

module A {
    
export interface X { s: string }
    
export var X: X;
}

module B {
    
interface A { n: number }
    import Y = A;    // Alias only for module A
    
import Z = A.X;  // Alias for both type and member A.X
    
var v: Z = Z;
}

within ‘B’, ‘Y’ is an alias only for module ‘A’ and not the local interface ‘A’, whereas ‘Z’ is an alias for all exported meanings of ‘A.X’, thus denoting both an interface type and a variable.

If the ModuleName portion of an EntityName references an instantiated module, the ModuleName is required to reference the module instance when evaluated as an expression. In the example

module A {
    export interface X { s: string }
}

module B {
    var A = 1;
    import Y = A;
}

‘Y’ is a local alias for the non-instantiated module ‘A’. If the declaration of ‘A’ is changed such that ‘A’ becomes an instantiated module, for example by including a variable declaration in ‘A’, the import statement in ‘B’ above would be an error because the expression ‘A’ doesn’t reference the module instance of module ‘A’.

When an import statement includes an export modifier, all meanings of the local alias are exported.

TODO: Specify the exact restrictions on import declarations referencing other import declarations. We minimally want to disallow circular references.

10.4 Export Declarations

An export declaration declares an externally accessible module member. An export declaration is simply a regular declaration prefixed with the keyword export.

Exported class, interface, and enum types can be accessed as a TypeName (section 3.6.2) of the form M.T, where M is a reference to the containing module and T is the exported type name. Likewise, as part of a TypeName, exported modules can be accessed as a ModuleName of the form M.N, where M is a reference to the containing module and N is the exported module.

Exported variable, function, class, enum, module, and import alias declarations become properties on the module instance and together establish the module’s instance type. This unnamed type has the following members:

·         A property for each exported variable declaration.

·         A property of a function type for each exported function declaration.

·         A property of a constructor type for each exported class declaration.

·         A property of an object type for each exported enum declaration.

·         A property of an object type for each exported instantiated module declaration.

·         A property for each exported import alias that references a variable, function, class, enum, or instantiated module.

An exported member depends on a (possibly empty) set of named types (section 3.5). Those named types must be at least as accessible as the exported member, or otherwise an error occurs.

The named types upon which a member depends are the named types occurring in the transitive closure of the directly depends on relationship defined as follows:

·         A variable directly depends on the Type specified in its type annotation.

·         A function directly depends on each Type specified in a parameter or return type annotation.

·         A class directly depends on each TypeReference specified as a base class or implemented interface, and each Type specified in a constructor parameter type annotation, member variable type annotation, member function parameter or return type annotation, member accessor parameter or return type annotation, or index signature type annotation.

·         An interface directly depends on each TypeReference specified as a base interface and on the ObjectType specified as its body.

·         A module directly depends on its exported members.

·         A Type or ObjectType directly depends on every TypeReference that occurs within the type at any level of nesting.

·         A TypeReference directly depends on the type it references and on each Type specified as a type argument.

A named type T having a root module R (section 2.3) is said to be at least as accessible as a member M if

·         R is the global module or an external module, or

·         R is an internal module in the parent module chain of M.

In the example

interface A { x: string; }

module M {
    export interface B { x: A; }
    export interface C { x: B; }
    export function foo(c: C) { … }
}

the ‘foo’ function depends upon the named types ‘A’, ‘B’, and ‘C’. In order to export ‘foo’ it is necessary to also export ‘B’ and ‘C’ as they otherwise would not be at least as accessible as ‘foo’. The ‘A’ interface is already at least as accessible as ‘foo’ because it is declared in a parent module of foo’s module.

10.5 Declaration Merging

Internal modules are “open-ended” and internal module declarations with the same qualified name relative to a common root (as defined in section 2.3) contribute to a single module. For example, the following two declarations of a module outer might be located in separate source files.

File a.ts:

module outer {
    var local = 1;           // Non-exported local variable
    export var a = local;    // outer.a
    export module inner {
        export var x = 10;   // outer.inner.x
    }
}

File b.ts:

module outer {
    var local = 2;           // Non-exported local variable
    export var b = local;    // outer.b
    export module inner {
        export var y = 20;   // outer.inner.y
    }
}

Assuming the two source files are part of the same program, the two declarations will have the global module as their common root and will therefore contribute to the same module instance, the instance type of which will be:

{
    a: number;
    b: number;
    inner: {
        x: number;
        y: number;
    };
}

Declaration merging does not apply to local aliases created by import declarations. In other words, it is not possible have an import declaration and a module declaration for the same name within the same module body.

Declaration merging also extends to internal module declarations with the same qualified name relative to a common root as a function, class, or enum declaration:

·         When merging a function and an internal module, the type of the function object is merged with the instance type of the module. In effect, the overloads or implementation of the function provide the call signatures and the exported members of the module provide the properties of the combined type.

·         When merging a class and an internal module, the type of the constructor function object is merged with the instance type of the module. In effect, the overloads or implementation of the class constructor provide the construct signatures, and the static members of the class and exported members of the module provide the properties of the combined type. It is an error to have static class members and exported module members with the same name.

·         When merging an enum and an internal module, the type of the enum object is merged with the instance type of the module. In effect, the members of the enum and the exported members of the module provide the properties of the combined type. It is an error to have enum members and exported module members with the same name.

When merging a non-ambient function or class declaration and a non-ambient internal module declaration, the function or class declaration must be located prior to the internal module declaration in the same source file. This ensures that the shared object instance is created as a function object. (While it is possible to add properties to an object after its creation, it is not possible to make an object “callable” after the fact.)

The example

interface Point {
    x:
number;
    y:
number;
}

function point(x: number, y: number): Point {
    
return { x: x, y: y };
}

module point {
    
export var origin = point(0, 0);
    
export function equals(p1: Point, p2: Point) {
        
return p1.x == p2.x && p1.y == p2.y;
    }
}

var p1 = point(0, 0);
var p2 = point.origin;
var b = point.equals(p1, p2);

declares ‘point’ as a function object with two properties, ‘origin’ and ‘equals’. Note that the module declaration for ‘point’ is located after the function declaration.

10.6 Code Generation

An internal module generates JavaScript code that is equivalent to the following:

var <ModuleName>;
(function(
<ModuleName>) {
    
<ModuleStatements>
})(
<ModuleName>||(<ModuleName>={}));

where ModuleName is the name of the module and ModuleStatements is the code generated for the statements in the module body. The ModuleName function parameter may be prefixed with one or more underscore characters to ensure the name is unique within the function body. Note that the entire module is emitted as an anonymous function that is immediately executed. This ensures that local variables are in their own lexical environment isolated from the surrounding context. Also note that the generated function doesn’t create and return a module instance, but rather it extends the existing instance (which may have just been created in the function call). This ensures that internal modules can extend each other.

An import statement generates code of the form

var <Alias> = <EntityName>;

This code is emitted only if the imported entity is referenced as a PrimaryExpression somewhere in the body of the importing module. If an imported entity is referenced only as a TypeName or ModuleName, nothing is emitted. This ensures that types declared in one internal module can be referenced through an import alias in another internal module with no run-time overhead.

When a variable is exported, all references to the variable in the body of the module are replaced with

<ModuleName>.<VariableName>

This effectively promotes the variable to be a property on the module instance and ensures that all references to the variable become references to the property.

When a function, class, enum, or module is exported, the code generated for the entity is followed by an assignment statement of the form

<ModuleName>.<EntityName> = <EntityName>;

This copies a reference to the entity into a property on the module instance.

 


TypeScript Language Specification (converted to HTML pages by @Bartvds)

Version 0.9.1

August 2013

Microsoft is making this Specification available under the Open Web Foundation Final Specification Agreement  Version 1.0 ('OWF 1.0') as of October 1, 2012. The OWF 1.0 is available at  http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0.

TypeScript is a trademark of Microsoft Corporation.