Skip to content

TypeScript - Modules

Modules are a fundamental concept in modern JavaScript and TypeScript for organizing code into reusable, independent pieces. They help manage dependencies, prevent global namespace pollution, and improve maintainability by encapsulating code.

TypeScript fully embraces the standard ECMAScript (ES) Module system, which uses the import and export keywords. This is the recommended approach for structuring TypeScript applications for both browsers and Node.js environments.

Any file containing a top-level import or export statement is considered a module. Variables, functions, classes, etc., declared in a module are local to that module unless explicitly exported.

You can export declarations by adding the export keyword.

// --- mathUtils.ts ---
export const PI = 3.14159;
export function add(x: number, y: number): number {
return x + y;
}
export interface Point {
x: number;
y: number;
}
// Default export (only one per module)
export default class Calculator {
add(x: number, y: number): number {
return x + y;
}
// ... other methods
}

You can also export members separately:

// --- stringUtils.ts ---
function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
function reverse(str: string): string {
return str.split('').reverse().join('');
}
export { capitalize, reverse }; // Exporting named members
export { capitalize as cap }; // Exporting with a different name

Use the import keyword to access exported members from other modules. The path specified is usually relative or can be resolved by the module loader/bundler (e.g., from node_modules).

// --- main.ts ---
// Importing named exports
import { add, PI, Point } from './mathUtils';
// Importing a default export (can use any name)
import MyCalculator from './mathUtils';
// Importing named exports with alias
import { capitalize as makeFirstLetterUpper, reverse } from './stringUtils';
// Importing the entire module as an object
import * as math from './mathUtils';
console.log(`Adding 5 and 3: ${add(5, 3)}`); // Output: 8
console.log(`PI is approx: ${PI}`); // Output: 3.14159
const origin: Point = { x: 0, y: 0 };
console.log('Origin:', origin);
const calc = new MyCalculator();
console.log(`Calculator adds 10 and 20: ${calc.add(10, 20)}`); // Output: 30
console.log(makeFirstLetterUpper('hello')); // Output: Hello
console.log(reverse('world')); // Output: dlrow
console.log(`Accessing via module object: ${math.add(1, 1)}`); // Output: 2
console.log(`Default export via module object: ${new math.default().add(2,2)}`); // Output: 4

How modules are found (resolution) and executed depends on the target environment:

  • Browsers: Modern browsers support ES Modules directly using <script type="module" src="...">. However, for compatibility, dependency management, and optimization, developers typically use bundlers like Webpack, Rollup, Parcel, or Vite. Bundlers process import/export statements, resolve dependencies, and create optimized JavaScript files for the browser.
  • Node.js: Node.js has built-in support for both ES Modules (using .mjs files or setting "type": "module" in package.json) and the older CommonJS module system (require/module.exports). TypeScript can compile to either format using the module option in tsconfig.json (e.g., "module": "NodeNext" or "module": "CommonJS").

Key options in tsconfig.json related to modules include:

  • "module": Specifies the module system for the compiled JavaScript output (e.g., "ES2020", "CommonJS", "NodeNext").
  • "target": Specifies the ECMAScript version for the output code (influences features available).
  • "moduleResolution": How the compiler finds modules (usually "Node" or "NodeNext").
  • "outDir": The directory where compiled JavaScript files are placed.
  • "rootDir": Specifies the root directory of source files (helps maintain structure in outDir).

TypeScript also has a concept called namespace. Before ES Modules became standard, namespaces were the primary way in TypeScript to group related code and avoid polluting the global scope. They compile to nested JavaScript objects or Immediately Invoked Function Expressions (IIFEs).

While ES Modules are now the standard for organizing code and managing dependencies, namespaces can still be useful for:

  • Organizing internal code within a large project where modules might feel too granular.
  • Structuring declaration files (.d.ts) for libraries that expose global objects.
  • Working with older codebases or specific build systems that rely on --outFile compilation (though this is less common now).
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}
// Usage:
let validator: Validation.StringValidator = new Validation.LettersOnlyValidator();
console.log(validator.isAcceptable("HelloWorld")); // true
console.log(validator.isAcceptable("12345")); // false

If namespaces span multiple files, you might need /// <reference path="..." /> directives or use the --outFile compiler option, but again, ES Modules handle multi-file organization more naturally.

For modern TypeScript development, strongly prefer ES Modules (import/export) over namespaces for code organization and dependency management. Understand namespaces primarily for compatibility with older code or specific use cases like global declaration merging.