I. Origin
Namespaces originate from the module pattern in JavaScript:
var MyModule = {};
(function(exports) {
// Private variables
var s = "hello";
// Public functions
function f() {
return s;
}
exports.f = f;
})(MyModule);
MyModule.f();
// Error MyModule.s is not a function
MyModule.s();
Composed of two parts:
- Module closure: Encapsulates module implementation, isolates scope
- Module object: Variables and functions exposed by the module
Later expanded on this basis with module dynamic loading, splitting into multiple files and other support
TypeScript combined module pattern and class pattern to implement a module mechanism, i.e. namespaces:
namespace MyModule {
var s = "hello";
export function f() {
return s;
}
}
MyModule.f();
// Error Property 's' does not exist on type 'typeof MyModule'.
MyModule.s;
Compilation product is the classic module pattern:
var MyModule;
(function (MyModule) {
var s = "hello";
function f() {
return s;
}
MyModule.f = f;
})(MyModule || (MyModule = {}));
MyModule.f();
MyModule.s;
II. Function
Similar to modules, namespaces are also a way of organizing code:
namespace Page {
export interface IPage {
render(data: object): string;
}
export class IndexPage implements IPage {
render(data: object): string {
return '<div>Index page content is here.</div>';
}
}
}
// Compilation result
var Page;
(function(Page) {
var IndexPage = /** @class */ (function() {
function IndexPage() {}
IndexPage.prototype.render = function(data) {
return '<div>Index page content is here.</div>';
};
return IndexPage;
})();
Page.IndexPage = IndexPage;
})(Page || (Page = {}));
Similarly has scope isolation (above example only exposes Page one global variable), also supports splitting modules by files:
// IPage.ts
namespace Page {
export interface IPage {
render(data: object): string;
}
}
// IndexPage.ts
/// <reference path="./IPage.ts" />
namespace Page {
export class IndexPage implements IPage {
render(data: object): string {
return '<div>Index page content is here.</div>';
}
}
}
// App.ts
/// <reference path="./IndexPage.ts" />
/// <reference path="./DetailPage.ts" />
let indexPageContent = new Page.IndexPage().render({value: 'index'})
Compilation result is:
// IPage.js
Empty
// IndexPage.js
/// <reference path="./IPage.ts" />
var Page;
(function (Page) {
var IndexPage = /** @class */ (function () {
function IndexPage() {
}
IndexPage.prototype.render = function (data) {
return '<div>Index page content is here.</div>';
};
return IndexPage;
}());
Page.IndexPage = IndexPage;
})(Page || (Page = {}));
// App.js
/// <reference path="./IndexPage.ts" />
/// <reference path="./DetailPage.ts" />
var indexPageContent = new Page.IndexPage().render({ value: 'index' });
Notice here uses triple-slash directives to introduce split-out "namespace modules" (not like module's import), still using import will get error:
// Error File '/path/to/IndexPage.ts' is not a module.ts(2306)
import IndexPage from "./IndexPage";
P.S. Additionally, can use --outFile option to generate a JS Bundle (default compilation generates corresponding same-name JS scattered files)
III. Triple-Slash Directives
Supports 6 types of directives:
- Describe file dependencies:
/// <reference path="./myFile.ts" />, referencesmyFile.tsunder current directory - Describe (type) declaration dependencies:
/// <reference types="node" />, references@types/node/index.d.tstype declarations, corresponds to--typesoption - Explicitly reference built-in (type) library files:
/// <reference lib="es2015" />or/// <reference lib="es2017.string" />, references built-in (type) librarylib.es2015.d.tsorlib.es2017.string.d.ts, corresponds to--libcompilation option - Disable default library:
/// <reference no-default-lib="true"/>, doesn't load default library during compilation process, corresponds to--noLibcompilation option, simultaneously marks current file as default library (so--skipDefaultLibCheckoption can skip checking this file) - Specify current module's AMD module name:
///<amd-module name="NamedModule"/>, specifies AMD module name asNamedModule - Specify AMD module dependencies (deprecated):
/// <amd-dependency path="legacy/moduleA" name="moduleA"/>, depends onlegacy/moduleA, and specifies imported module name asmoduleA(nameattribute is optional)
P.S. More examples, see Triple-Slash Directives
Formally starts with 3 slashes, therefore called triple-slash directives, where XML tags are used to express compilation directives. Other notes as follows:
- Must appear at file's first line (except comments) to be effective. That is to say, before a triple-slash directive can only appear single-line comments, multi-line comments or other triple-slash directives
/// <amd-dependency />directive is deprecated, useimport "moduleName";instead- When specifying
--noResolveoption, ignores all/// <reference path="..." />directives (doesn't import these modules)
In function, /// <reference path="..." /> is similar to @import in CSS (when specifying --outFile option, module integration order is consistent with path reference directive order)
In implementation, during preprocessing phase will depth-first parse all triple-slash directives, adding specified files to compilation process
P.S. Triple-slash directives appearing in other positions will be treated as ordinary single-line comments, no error, but ineffective (compiler doesn't recognize)
IV. Aliases
Namespaces support nesting, therefore may appear deep nesting situations:
namespace Shapes {
export namespace Polygons {
export class Triangle { }
export class Square { }
}
}
At this time can use aliases to simplify module references:
import P = Shapes.Polygons;
import Triangle = Shapes.Polygons.Triangle;
let sq = new P.Square();
let triangle = new Triangle();
// After compilation
var P = Shapes.Polygons;
var Triangle = Shapes.Polygons.Triangle;
var sq = new P.Square();
var triangle = new Triangle();
Not hard to discover, here import is var's syntax sugar:
This is similar to using var, but also works on the type and namespace meanings of the imported symbol.
Therefore when aliasing a value will create a new reference:
Importantly, for values, import is a distinct reference from the original symbol, so changes to an aliased var will not be reflected in the original variable.
For example:
namespace NS {
export let x = 1;
}
import x = NS.x;
import y = NS.x;
(x as any) = 2;
y === 1; // true
// After compilation
var NS;
(function (NS) {
NS.x = 1;
})(NS || (NS = {}));
var x = NS.x;
var y = NS.x;
x = 2;
y === 1; // true
P.S. import q = x.y.z alias syntax only applies to namespaces, requires right side must be namespace access
V. namespace and module
Before TypeScript 1.5 there was only
modulekeyword, didn't distinguish internal modules (internal modules) and external modules (external modules), both were defined throughmodulekeyword. Later for clarity, addednamespacekeyword to represent internal modules
(Excerpted from namespace keyword)
In short, keywords module and namespace are completely equivalent in syntax, for example:
namespace Shape.Rectangle {
export let a;
export let b;
export function getArea() { return a * b; }
}
And
module Shape.Rectangle {
export let a;
export let b;
export function getArea() { return a * b; }
}
Compilation results are both:
var Shape;
(function (Shape) {
var Rectangle;
(function (Rectangle) {
function getArea() { return Rectangle.a * Rectangle.b; }
Rectangle.getArea = getArea;
})(Rectangle = Shape.Rectangle || (Shape.Rectangle = {}));
})(Shape || (Shape = {}));
Using namespace to replace old module is just to avoid confusion, or say to make way for ES Module, AMD, UMD and other Module concepts (so-called external modules). Because if occupying module keyword, but actually defining not Module but Namespace, it's a very confusing thing
VI. Modules and Namespaces
Internal Modules and External Modules
That is to say:
- Internal modules: i.e. namespaces, declared through
namespaceormodulekeywords - External modules: i.e. modules (such as ES Module, CommonJS, AMD, UMD, etc.), no need to explicitly declare, (containing
importorexport) files are modules
External modules can simply understand as modules in external files, because can define multiple different namespace or module (i.e. internal modules) in same file, but cannot define multiple ES Modules
P.S. After all namespaces are essentially IIFE, unrelated to module loaders, doesn't exist file-is-module loading mechanism constraints
Concept Differences
Conceptually, TypeScript follows ES Module specification (file is module), outputs CommonJS, AMD, UMD and other module forms through compilation
While namespaces originate from module pattern in JavaScript, considered a product of old era, not recommended to use (except for declaring module types)
Loading Mechanism Differences
In module import mechanism, namespaces need to be imported through triple-slash directives, equivalent to source code embedding (similar to @import in CSS), will introduce extra variables into current scope
P.S. If not bundling into single file Bundle, need to introduce these dependencies imported through triple-slash directives at runtime (for example through <script> tags)
While modules are imported through import/require etc. methods, decided by caller whether to reference it through variables, won't actively affect current scope
P.S. import "module-name"; syntax only imports module (its side effects), doesn't reference and access module, specifically see import
Best Practices
In module and namespace usage, there are some practical experiences:
- Reduced namespace nesting levels, for example classes containing only static methods are usually unnecessary, module names are enough to express semantics
- When module only exposes one API, using export default is more suitable, importing is more convenient, and caller doesn't need to care about API name
- When wanting to expose multiple APIs, directly
exportall (if too many, import module object, such asimport * as largeModule from 'SoLargeModule') - Extend existing modules through re-export, for example
export as - Don't use namespaces in modules, because modules already have logical structure (file directory structure) and module scope, namespaces don't provide more benefits
No comments yet. Be the first to share your thoughts.