Module Systems & Namespaces
π¦ Module Systems & Namespaces
π 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
"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";
@ 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
ποΈ 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.
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.
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.
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.