Capítulo 25 de 36

Chapter 25: Namespaces

Core Idea

A namespace groups related types/values under one qualified name to avoid polluting the global scope — historically called "internal modules" (renamed in TS 1.5 to align with ECMAScript terminology, where module X {} is now written namespace X {}), usable across multiple files without a module loader, but superseded by ES Modules for most modern application code.

Key Concepts

  • Basic namespacing: wrapping related interfaces/classes in namespace Validation { export interface StringValidator {...}; export class LettersOnlyValidator implements StringValidator {...} } groups them under Validation.*. Only exported members are visible outside the namespace — a const/let left unexported (e.g. an internal regex used by validators) stays a private implementation detail.
  • Multi-file namespaces: the same namespace name can be reopened across several files, each contributing more members (this is namespace declaration merging — see the Declaration Merging chapter). Cross-file dependencies within a namespace are wired up with /// <reference path="..." /> directives, since there's no import between the files — they all become part of one namespace regardless of file boundaries.
  • Loading multi-file namespace output: either concatenate everything into one JS file via tsc --outFile bundle.js entry.ts (the compiler orders files by the reference-tag dependency graph), or emit one JS file per source file and load them via ordered <script> tags matching the same dependency order — namespaces don't have module loader semantics, so the load order has to be arranged manually one way or the other.
  • Namespace aliases (import q = x.y.z): shortens a long qualified path to a local name — not the same as import x = require("name") for loading modules, this is a pure local alias for an already-in-scope namespace/type/value. Works for types and namespace meanings too, not just runtime values; for a value alias specifically, it's a distinct binding (mutating the aliased local doesn't reach back to change the original).
  • Ambient namespaces: for describing the shape of a non-TypeScript library that exposes a global object (loaded via <script> tag rather than a module loader) — declare namespace D3 { export interface Selectors {...} } followed by declare var d3: D3.Base; tells the compiler about a global's shape without providing any implementation. These "ambient" (implementation-less) declarations conventionally live in .d.ts files, analogous to C/C++ header files.

Code Examples

// Validation.ts
namespace Validation {
  export interface StringValidator { isAcceptable(s: string): boolean; }
}
// ZipCodeValidator.ts
/// <reference path="Validation.ts" />
namespace Validation {
  export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) { return /^[0-9]{5}$/.test(s); }
  }
}
  • What it demonstrates: the same Validation namespace reopened across two files via declaration merging, wired together with a reference tag rather than an import.
namespace Shapes { export namespace Polygons { export class Square {} } }
import polygons = Shapes.Polygons;
const sq = new polygons.Square(); // same as new Shapes.Polygons.Square()
  • What it demonstrates: a namespace alias shortening a deeply-nested qualified name.

Reference Tables

ConceptPurpose
namespace X { export ... }group related declarations under one qualified name
/// <reference path="...">wire dependencies between files sharing a namespace (no import involved)
import q = x.y.zlocal alias for a qualified name (types, namespaces, or values)
declare namespace X {...}ambient (implementation-less) description of a global-scope library's shape

Key Takeaways

  1. Prefer ES Modules for new application code — namespaces remain relevant mainly for ambient .d.ts declarations of script-tag-loaded globals, and legacy multi-file non-module codebases.
  2. Namespace aliases (import q = x.y) are a local shorthand, distinct from import x = require(...) module loading — don't confuse the two similar-looking syntaxes.
  3. Multi-file namespaces require manual load-order management (outFile concatenation or ordered <script> tags) since there's no loader resolving dependencies at runtime the way ES Modules have.

Connects To

  • Namespaces and Modules: the migration/pitfalls guide recommending modules over namespaces for new code.
  • Declaration Merging: the mechanism that lets the same namespace be reopened across files.
  • Type Declarations: .d.ts ambient declarations, the primary remaining use case for namespaces today.