- TypeScript Tutorial
- TypeScript Home
- TypeScript Introduction
- TypeScript Setup
- TypeScript First Program
- TypeScript vs JavaScript
- TypeScript Data Types
- TypeScript Type Inference
- TypeScript Type Annotations
- TypeScript Interfaces
- TypeScript Enums
- TypeScript Type Aliases
- TypeScript Type Assertions
- TypeScript Variables
- TypeScript Functions
- TypeScript Functions
- TypeScript Optional Parameters
- TypeScript Default Parameters
- TypeScript Rest Parameters
- TypeScript Arrow Functions
- Classes and Objects
- Introduction to Classes
- Properties and Methods
- Access Modifiers
- Static Members
- Inheritance
- Abstract Classes
- Interfaces vs Classes
- Advanced Types
- TypeScript Union Types
- TypeScript Intersection Types
- TypeScript Literal Types
- TypeScript Nullable Types
- TypeScript Type Guards
- TypeScript Discriminated Unions
- TypeScript Index Signatures
- TypeScript Generics
- Introduction to Generics
- TypeScript Generic Functions
- TypeScript Generic Classes
- TypeScript Generic Constraints
- TypeScript Modules
- Introduction to Modules
- TypeScript Import and Export
- TypeScript Default Exports
- TypeScript Namespace
- Decorators
- Introduction to Decorators
- TypeScript Class Decorators
- TypeScript Method Decorators
- TypeScript Property Decorators
- TypeScript Parameter Decorators
- Configuration
- TypeScript tsconfig.json File
- TypeScript Compiler Options
- TypeScript Strict Mode
- TypeScript Watch Mode
TypeScript Introduction to Modules
In TypeScript, modules allow you to structure your code into smaller, reusable, and maintainable components. Without modules, variables and functions declared in different files could easily clash, leading to "spaghetti code" that is difficult to debug. A module is essentially a self-contained environment; anything defined within it—such as a class, function, or variable—stays private to that file unless you explicitly share it using the export keyword.
What are Modules?
Modules are a way to organize TypeScript code into separate, self-contained units that can be imported and exported between different files. In the world of modern web development, a module is typically a single file. By isolating functionality, you prevent the global scope from being cluttered with variables that don't need to be there.
TypeScript follows the ECMAScript 2015 (ES6) module standard. This means that any file containing a top-level import or export is considered a module. If a file has neither, its contents are treated as being in the global scope (available everywhere), which is generally avoided in professional projects.
How Modules Work in TypeScript
The relationship between modules is built on two primary actions:
Exporting: This is how a module makes certain parts of its code available to the outside world. You use the export keyword to "publish" functions, variables, or classes.
Importing: This is how one module "consumes" or uses code that has been exported by another module using the import keyword.
Syntax of Modules
1. Exporting from a Module
You can export variables, functions, classes, or interfaces by prefixing them with the export keyword. This tells TypeScript, "This specific piece of code is public."
Example: Exporting Variables and Functions
// file: mathUtils.ts
export const PI = 3.14159;
export function calculateCircumference(radius: number): number {
return 2 * PI * radius;
}
export class Calculator {
add(x: number, y: number): number {
return x + y;
}
}
- In this example,
PI,calculateCircumference, and theCalculatorclass are all available for other files to use.
export keyword. If you define a function in fileA.ts and try to import it in fileB.ts without exporting it first, TypeScript will throw a "Module not found" or "Member not exported" error.
2. Importing from a Module
To use those exported members, use the import statement followed by the names of the components inside curly braces { }.
Example: Importing Variables and Functions
// file: main.ts
import { PI, calculateCircumference, Calculator } from './mathUtils';
const myCalc = new Calculator();
console.log(`Circumference: ${calculateCircumference(10)}`);
console.log(`Addition: ${myCalc.add(10, 5)}`);
- The relative path
./mathUtilstells TypeScript to look for a file namedmathUtils.ts(or.js) in the same directory. - Note that we typically omit the file extension (
.ts) in the import statement.
.js extension in your import paths, even though the source file is .ts.
Default Exports
Each module can have exactly one default export. This is often used when a file contains a single major piece of functionality, like a main class or a configuration object.
Example: Default Export
// file: Logger.ts
export default class Logger {
log(message: string) {
console.log(`[LOG]: ${message}`);
}
}
Importing a Default Export
// file: app.ts
import Logger from './Logger';
const logger = new Logger();
logger.log("App started successfully!");
- Key Difference: Notice that we do not use curly braces
{ }when importing a default export. You can also name the default import whatever you like in the receiving file.
Renaming Imports
Sometimes you might import two different modules that have functions with the same name. To avoid a conflict, you can use the as keyword to rename them during the import process.
Example: Renaming Imports
// file: app.ts
import { add as addNumbers } from './mathOperations';
import { add as addUIElement } from './domUtils';
console.log(addNumbers(5, 10)); // Uses the math function
fetch function from a module to fetchUserData makes your code more self-documenting.
Re-exporting from a Module
Re-exporting allows you to gather exports from multiple files and expose them from a single "entry point" file. This pattern is commonly known as a Barrel File.
Example: Re-exporting (index.ts)
// file: services/index.ts
export * from './userService';
export * from './authService';
export { databaseConfig } from './config';
- Using
export *re-exports everything from the target file. - This allows other developers to import everything they need from the
servicesfolder using a single line.
Namespaces vs Modules
In the early days of TypeScript, Namespaces (formerly "Internal Modules") were the primary way to organize code. However, the industry has shifted toward ES Modules.
- Modules: The modern standard. They handle dependencies better, support tree-shaking (removing unused code), and are the standard for Node.js and modern browsers.
- Namespaces: Use the
namespacekeyword. They are generally only used today for legacy codebases or for writing Type Definition files (.d.ts).
TypeScript Module Resolution
Module resolution is the process the compiler uses to figure out what a "module specifier" (the string path in the import) refers to. TypeScript supports two main strategies:
- Classic: Mostly for backward compatibility; you likely won't use this.
- Node: This mimics how Node.js works. It looks in
node_modulesand checks forpackage.jsonfiles. This is the most common setting for modern apps.
You can control this in your tsconfig.json:
{
"compilerOptions": {
"moduleResolution": "node",
"module": "ESNext"
}
}
Summary
Modules are the backbone of clean, professional TypeScript development. By mastering them, you can build scalable applications that remain organized as they grow. To recap:
- Exporting: Use
exportto share code. - Importing: Use
import { ... }to consume code. - Default Exports: Best for files that export a single primary object or class.
- Barrel Files: Use
export *in anindex.tsto clean up your project's import paths. - Modules over Namespaces: Stick to modules for modern development.
By moving your logic into specialized modules, you ensure that your code is reusable, testable, and significantly easier for your team to navigate.