Module Systems & Namespaces

TypeScript Intermediate — Module Systems & Namespaces
TypeScript Intermediate
Course 2 · Chapter 5 · Module Systems & Namespaces

📦 Module Systems & Namespaces

As codebases grow, organization matters. This chapter covers ES modules (the modern standard), CommonJS (Node.js legacy), path mapping (clean imports), barrel exports (organized re-exports), and namespaces (TypeScript-specific grouping). You'll learn how to structure code across files without chaos.

🌍 ES Modules: The Standard

ES modules (ESM) are the modern standard. Files are modules; imports/exports are explicit:

// math.ts export function add(a: number, b: number) { return a + b; } export const PI = 3.14159; // app.ts import { add, PI } from "./math"; // .ts extension is optional console.log(add(2, 3)); // 5 console.log(PI); // 3.14159

Named Exports

export function foo() — export by name. Import with { foo }.

Default Export

export default class User — one default per file. Import as import User from.

Re-export

export { foo } from "./module" — pass-through import/export (barrel exports).

Namespace Import

import * as math from "./math" — import all as math.add().

📼 CommonJS: Node.js Legacy

CommonJS (CJS) is Node.js's original module system. Still widely used, but ES modules are preferred:

// math.ts (CommonJS style) function add(a: number, b: number) { return a + b; } module.exports = { add }; // app.ts (CommonJS style) const { add } = require("./math"); console.log(add(2, 3));

ES Modules vs CommonJS

CommonJS (require/module.exports)
const math = require("./math"); module.exports = { myFunc };
  • Dynamic (runtime)
  • Synchronous
  • Node.js built-in
ES Modules (import/export)
import math from "./math"; export { myFunc };
  • Static (compile-time)
  • Asynchronous
  • JavaScript standard
Modern projects: Use ES modules (set "module": "ESNext" in tsconfig.json). CommonJS is legacy but still needed for older Node.js versions.

🗺️ Path Mapping: Cleaner Imports

Deep nested paths are messy. Path mapping lets you create short aliases:

// tsconfig.json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"], "@utils/*": ["src/utils/*"], "@components/*": ["src/components/*"] } } } // Instead of: import { formatDate } from "../../../../utils/format"; // Write: import { formatDate } from "@utils/format";
💡 Path Alias Conventions

@ prefix is common in modern projects (similar to npm scopes). Use @utils, @types, @components for clarity. Keep the mapping shallow so paths stay readable.

📦 Barrel Exports: Organized Re-exports

A barrel export (index.ts) re-exports from submodules for cleaner public APIs:

// src/utils/string.ts export function capitalize(s: string) { return s.charAt(0).toUpperCase() + s.slice(1); } // src/utils/math.ts export function sum(a: number, b: number) { return a + b; } // src/utils/index.ts (barrel export) export * from "./string"; export * from "./math"; // app.ts — clean, single import import { capitalize, sum } from "@utils";

Without barrel: You'd need two imports. With barrel: One import gets everything.

📋 Ambient Declarations: Types for External Code

Sometimes you use libraries without type definitions. Ambient declarations (`.d.ts` files) provide types:

// types/global.d.ts // Declare a global variable declare const DEBUG: boolean; // Declare a module (for libraries without types) declare module "my-untyped-library" { export function doSomething(x: any): string; } // In app.ts console.log(DEBUG); // ✅ TypeScript knows about it import { doSomething } from "my-untyped-library"; const result = doSomething(42); // ✅ Typed

🔷 TypeScript Namespaces (Legacy Grouping)

Namespaces group related types without ES modules. They're pre-module, but still used in legacy codebases:

// math.ts namespace Math { export function add(a: number, b: number) { return a + b; } export const PI = 3.14159; } // app.ts const result = Math.add(2, 3); // Access via namespace
Avoid namespaces in new code. Use ES modules instead. Namespaces are kept for backward compatibility but modules are cleaner and standard.

🏗️ Real-World Pattern: Well-Organized Project

Clean Project Structure with Path Aliases & Barrels

// Directory structure: // src/ // ├── types/ ← TypeScript types // │ ├── index.ts (barrel) // │ └── user.ts // ├── utils/ ← Utility functions // │ ├── index.ts (barrel) // │ ├── format.ts // │ └── validate.ts // ├── services/ ← Business logic // │ ├── index.ts (barrel) // │ └── api.ts // └── app.ts ← Entry point // tsconfig.json { "compilerOptions": { "baseUrl": ".", "paths": { "@types/*": ["src/types/*"], "@utils/*": ["src/utils/*"], "@services/*": ["src/services/*"] } } } // src/types/index.ts export * from "./user"; // src/utils/index.ts export * from "./format"; export * from "./validate"; // src/app.ts — clean imports import { User } from "@types"; import { formatDate, validateEmail } from "@utils"; import { UserService } from "@services"; const user: User = { id: 1, email: "alice@example.com" }; console.log(formatDate(new Date()));

💻 Coding Challenges

Challenge 1: Create a Barrel Export

Create three utility modules (math.ts, string.ts, array.ts) with exported functions. Then create an index.ts barrel that re-exports all. Test importing from the barrel.

Goal: Practice barrel exports and namespace organization.

→ Solution

Challenge 2: Set Up Path Aliases

Create a tsconfig.json with path aliases (@utils, @types, @services). Set up dummy modules under each and import using the aliases.

Goal: Configure and use path mapping.

→ Solution

Challenge 3: Ambient Declaration

Create a .d.ts file that declares a global DEBUG constant and a module type for an external library. Write code that uses both.

Goal: Practice ambient declarations for untyped external code.

→ Solution

⚠️ Gotcha: Circular Dependencies

Be careful with circular imports: A imports B, B imports A. TypeScript won't error, but at runtime it can cause undefined values. Refactor to break the cycle—move shared types to a third file both can import from.

🎯 What's Next

With module systems mastered, we'll explore Advanced OOP — abstract classes, access modifiers, static members, and design patterns in TypeScript.