Modules and Namespaces in TypeScript

A Guide to TypeScript's Modules and Namespaces

Modules and Namespaces in TypeScript

Welcome back to our TypeScript series!

Today, we’ll explore modules and namespaces, two features that help you organize your code. While both provide ways to structure your code, they serve different purposes and have different use cases. We will cover the basics, usage, and best practices for both modules and namespaces.

1. Modules

Modules are a way to organize code into reusable blocks. They provide a way to split your code into separate files and export and import functionality between them. Modules can be thought of as files or external modules.

1.1. Exporting and Importing

Exporting from a Module

To make code available outside a module, you use the export keyword.

// file: mathUtils.ts
export function add(a: number, b: number): number {
  return a + b;
}

export const PI = 3.14;

Importing from a Module

To use exported code from another module, you use the import keyword.

// file: main.ts
import { add, PI } from './mathUtils';

console.log(add(5, 3)); // Output: 8
console.log(PI); // Output: 3.14

1.2. Default Exports

A module can have a default export, which simplifies importing.

Default Export

// file: mathUtils.ts
export default function subtract(a: number, b: number): number {
  return a - b;
}

Default Import

// file: main.ts
import subtract from './mathUtils';

console.log(subtract(10, 4)); // Output: 6

1.3. Re-exporting

Modules can re-export entities from other modules, allowing for easier aggregation.

1.3.1 How to Re-exporting

You can re-export items using the export keyword along with from to specify the module you're re-exporting from.

// file: basicMath.ts
export * from './mathUtils';

1.3.2 Example: Re-exporting

Consider three modules, each exporting some functionality:

// file: mathUtils.ts
export function add(a: number, b: number): number {
  return a + b;
}

export function subtract(a: number, b: number): number {
  return a - b;
}

// file: constants.ts
export const PI = 3.14;
export const E = 2.71;

// file: strings.ts
export function greet(name: string): string {
  return `Hello, ${name}!`;
}

You can create a central module that re-exports items from these modules:

// file: index.ts
export { add, subtract } from './mathUtils';
export { PI, E } from './constants';
export { greet } from './strings';

Now, you can import everything from the index.ts module:

// file: main.ts
import { add, subtract, PI, E, greet } from './index';

console.log(add(5, 3)); // Output: 8
console.log(subtract(10, 4)); // Output: 6
console.log(PI); // Output: 3.14
console.log(E); // Output: 2.71
console.log(greet("Alice")); // Output: Hello, Alice!

1.3.3 Re-exporting with ` export *`

You can also re-export everything from a module using export *.

// file: index.ts
export * from './mathUtils';
export * from './constants';
export * from './strings';

This re-exports all named exports from mathUtils, constants, and strings.

2. Namespaces

Namespaces are a way to organize code within a single file or across multiple files in the global scope. They are useful for organizing large applications and avoiding name collisions.

2.1. Defining a Namespace

You define a namespace using the namespace keyword.

namespace MathUtils {
  export function add(a: number, b: number): number {
    return a + b;
  }

  export const PI = 3.14;
}

Using a Namespace

To use the functions or variables defined in a namespace, you need to reference them with the namespace name.

console.log(MathUtils.add(5, 3)); // Output: 8
console.log(MathUtils.PI); // Output: 3.14

2.2. Nested Namespaces

Namespaces can be nested to create a hierarchical structure.

namespace Geometry {
  export namespace Circle {
    export function circumference(radius: number): number {
      return 2 * MathUtils.PI * radius;
    }
  }
}

console.log(Geometry.Circle.circumference(10)); // Output: 62.8

3. Modules vs. Namespaces

While both modules and namespaces can be used to organize code, they have different use cases:

  • Modules: Use modules to organize code into reusable files and manage dependencies. Modules are the preferred way to organize code in modern TypeScript and JavaScript projects, especially for large-scale applications.

  • Namespaces: Use namespaces to organize code within a file or across multiple files in the global scope. They are useful for organizing libraries or frameworks where global scope management is necessary.

4. Best Practices

  • Prefer Modules: Use modules over namespaces for better maintainability and scalability.

  • Consistent Imports and Exports: Use named exports for consistency and avoid default exports unless necessary.

  • Organize Code: Break down large modules into smaller, more manageable files and use re-exports to aggregate them.

5. Example Code

Here’s an example that combines various concepts of modules and namespaces:

// file: mathUtils.ts
export function add(a: number, b: number): number {
  return a + b;
}

export const PI = 3.14;

// file: geometry.ts
import { PI } from './mathUtils';

export namespace Geometry {
  export namespace Circle {
    export function circumference(radius: number): number {
      return 2 * PI * radius;
    }
  }

  export namespace Rectangle {
    export function area(length: number, width: number): number {
      return length * width;
    }
  }
}

// file: main.ts
import { add } from './mathUtils';
import { Geometry } from './geometry';

console.log(add(5, 3)); // Output: 8
console.log(Geometry.Circle.circumference(10)); // Output: 62.8
console.log(Geometry.Rectangle.area(5, 10)); // Output: 50

Summary

Today, we explored modules and namespaces in TypeScript, covering the basics, usage, and best practices for both. Modules are the preferred way to organize code in modern TypeScript, while namespaces are useful for managing code in the global scope. Understanding these concepts will help you structure your code effectively and maintainably.

Next time, we’ll dive into decorators in TypeScript. Stay tuned!