TypeScript Namespaces & Modules Cheat Sheet
Compares ES module import/export syntax with legacy TypeScript namespaces, covering declaration merging and when each approach fits.
ES Modules (the Modern Default)
Standard import/export syntax for organizing TypeScript code today.
// math.tsexport function add(a: number, b: number): number { return a + b; }export const PI = 3.14159;export default class Calculator { /* ... */ } // default export// app.tsimport Calculator, { add, PI } from './math';import * as MathUtils from './math'; // namespace import// Re-exportingexport { add as sum } from './math';export * from './math';
Namespaces (Legacy, Single-File Grouping)
Group related code under one name without ES module tooling.
namespace Geometry { export interface Point { x: number; y: number; } export function distance(a: Point, b: Point): number { return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2); } // Nested namespaces export namespace Shapes { export class Circle { constructor(public radius: number) {} } }}const p1: Geometry.Point = { x: 0, y: 0 };const c = new Geometry.Shapes.Circle(5);
Declaration Merging with Namespaces
Attach static members to a class using a same-named namespace.
// Attach static members/types to a class or function via a same-named namespaceclass Album { constructor(public title: string) {}}namespace Album { export function create(title: string): Album { return new Album(title); }}const a = Album.create('Abbey Road');
Module Concepts
Key vocabulary for how TypeScript exposes and resolves code.
- export- Marks a declaration as visible outside its module
- export default- One per module; imported without curly braces
- import type- Imports only type information, guaranteed to be erased at compile time
- module vs namespace keyword- 'module X {}' is a deprecated alias for 'namespace X {}'
- triple-slash reference- /// <reference path="./file.ts" /> links namespace files together (pre-module era)
- ambient declarations- declare namespace X {} describes shapes for existing global/JS code without emitting runtime code
- moduleResolution setting- tsconfig option controlling how import paths are resolved (node16, bundler, etc.)
Augmenting a Third-Party Module's Types
Extend an existing library's declared types without forking the package.
// express-augment.d.tsimport 'express';declare module 'express' { interface Request { user?: { id: string; role: string }; }}// now anywhere in the app:import { Request } from 'express';function handler(req: Request) { req.user?.id; // typed, no 'any'}
Global Augmentation from Inside a Module
Add ambient global declarations (e.g. window properties) from a file that is itself a module.
// analytics.ts - having any import/export makes this a module, not a global scriptexport {};declare global { interface Window { analytics: { track(event: string, props?: Record<string, unknown>): void }; }}window.analytics.track('page_view', { path: '/pricing' }); // typed globally
'export =' / 'import ... = require()' Interop
The CommonJS-style export/import pair for libraries that export a single callable or class value.
// legacy-lib.d.tsdeclare function legacyLib(config: { debug: boolean }): void;namespace legacyLib { export const version: string;}export = legacyLib;// consumer.tsimport legacyLib = require('legacy-lib');legacyLib({ debug: true });console.log(legacyLib.version);// Only valid with module: commonjs/node16/nodenext; not usable alongside 'export default'
Dynamic import() for Code-Splitting
Lazily load a module at runtime while keeping full static types.
async function loadChart() { const { renderChart } = await import('./chart'); // returns Promise<typeof import('./chart')> renderChart(document.getElementById('root')!);}// Type-only dynamic import for referencing a type without a runtime module loadtype ChartModule = Awaited<ReturnType<typeof import>> extends never ? never : typeof import('./chart');
Module Resolution Strategies
tsconfig settings that change how import specifiers are resolved and emitted.
- moduleResolution: node16 / nodenext- Mirrors Node's ESM resolution exactly, including mandatory file extensions in relative imports
- moduleResolution: bundler- Matches how bundlers (Vite, esbuild, webpack) resolve paths; allows extensionless imports
- verbatimModuleSyntax- Forces explicit 'import type'/'export type' and preserves import/export statements exactly as written in the emit
- paths + baseUrl- Configure alias imports (e.g. '@/lib/x') for the compiler; bundlers need a matching alias config separately
- isolatedModules- Requires every file to be safely transpilable alone (no const enum, no ambiguous re-exports of types)
- resolveJsonModule- Allows 'import data from "./data.json"' with an inferred type from the JSON shape
Avoid namespaces in new code that targets modern bundlers - they predate ES modules and mainly still show up for organizing global .d.ts ambient type declarations or in legacy codebases; use ES module import/export for everything else.