Skip to content
This repository has been archived by the owner on May 25, 2023. It is now read-only.

Latest commit

 

History

History
378 lines (287 loc) · 16.8 KB

ch11.md

File metadata and controls

378 lines (287 loc) · 16.8 KB

11 Source Files and External Modules

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.

11.1 Source Files

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.

11.1.1 Source Files Dependencies

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.

11.2 External Modules

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.

11.2.1 External Module Names

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 paths C:/src/ui/editor and C:/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.

11.2.2 External Import Declarations

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.

11.2.3 Export Declarations

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.

11.2.4 Export Assignments

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.

11.2.5 CommonJS Modules

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 };

11.2.6 AMD Modules

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.