9. Modules & Namespaces

Modules are a crucial part of structuring and organizing code in TypeScript. They allow you to create reusable components and manage dependencies effectively. In this article, we’ll explore how to use modules to organize your TypeScript code, export and import modules, and create a modular codebase.

Modules in TypeScript

Modules in TypeScript are used to group related code, encapsulate functionality, and provide a way to separate code into smaller, manageable parts. You can think of modules as self-contained units that encapsulate code and data, preventing naming conflicts and improving code maintainability.

Creating a Module

To create a module in TypeScript, use the export keyword to make variables, functions, classes, or types accessible from outside the module.

// mathUtils.ts

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

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

In this example, we have a module named mathUtils that exports two functions, add and subtract.

Importing a Module

To use a module in another part of your code, use the import statement to bring the exported members into scope.

// app.ts

import { add, subtract } from './mathUtils';

const result1 = add(5, 3);        // 8
const result2 = subtract(10, 2);  // 8

In the app.ts file, we import the add and subtract functions from the mathUtils module and use them in our code.

Exporting and Importing Modules

Named Exports

You can use named exports to specify which functions, variables, or classes you want to make available outside the module. Use the { } syntax to import specific members by name.

// mathUtils.ts

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

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

import { add, subtract } from './mathUtils';

const result1 = add(5, 3);        // 8
const result2 = subtract(10, 2);  // 8

Re-Exporting Modules

You can also re-export modules by importing and exporting them again in another module. This can be useful for creating a single entry point for a set of related modules.

// utils.ts

export function log(message: string) {
    console.log(message);
}
// index.ts

export * from './utils';
// app.ts

import { log } from './index';

log('Hello, TypeScript!'); // Outputs: "Hello, TypeScript!"

In this example, we re-export the log function from the utils module through the index module for easier access.

Default Exports

Default exports allow you to export a single value or functionality from a module, which can be imported without specifying a name for it. This is especially useful when you want to export the primary functionality of a module.

Exporting a Default

// mathUtils.ts

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

Importing a Default

// app.ts

import add from './mathUtils';

const result = add(5, 3); // 8

Default exports simplify the import statement and can make your code more concise when you have a single main export.

Ambient Declarations

Ambient declarations are a way to tell TypeScript about types or variables that are defined outside the TypeScript environment, such as in JavaScript libraries or other external code. They provide type information for external code and enable TypeScript to perform type checking when you use those external resources.

Example: Working with a JavaScript Library

Suppose you are using the popular library jQuery in your TypeScript project. You can create an ambient declaration to let TypeScript know about jQuery’s types.

// jquery.d.ts

declare var jQuery: {
    (selector: string): any;
    fn: {
        [key: string]: any;
    };
};

Now, TypeScript will understand the structure of the jQuery library when you use it in your code.

// app.ts

const element = jQuery('.my-element'); // Type-checked usage of jQuery

Ambient declarations are particularly useful when integrating TypeScript with existing JavaScript libraries or when working with external resources that don’t have TypeScript type definitions.

Namespaces vs. Modules

Both namespaces and modules are used for organizing code in TypeScript, but they serve slightly different purposes.

Namespaces

Namespaces provide a way to group related code and avoid naming collisions by creating a hierarchical structure. They are used for organizing code within a single global scope.

// myNamespace.ts

namespace MyNamespace {
    export function greet() {
        console.log('Hello from MyNamespace!');
    }
}
// app.ts

MyNamespace.greet(); // "Hello from MyNamespace!"

Namespaces can be useful for encapsulating related functionality, but they can lead to global scope pollution if not managed properly.

Modules

Modules, on the other hand, allow you to encapsulate code within its own scope, preventing global scope pollution. They provide a cleaner and more modular approach to organizing code and are the preferred way to structure TypeScript projects.

// myModule.ts

export function greet() {
    console.log('Hello from MyModule!');
}
// app.ts

import { greet } from './myModule';

greet(); // "Hello from MyModule!"

Modules provide better encapsulation and help you manage dependencies more efficiently. They are suitable for structuring your entire TypeScript project, from small libraries to large applications.

Conclusion

Modules in TypeScript are a powerful tool for organizing and structuring your codebase. They enable you to create reusable components, manage dependencies, and prevent naming conflicts. By using named and default exports, you can make your code more modular and maintainable. Understanding how to export and import modules is essential for building maintainable TypeScript applications. Ambient declarations enable TypeScript to work with external code. When it comes to structuring your codebase, modules are the preferred choice due to their encapsulation and dependency management capabilities.

Previous
Next