Prerequisites
- Basic TypeScript syntax and types ๐
- Understanding of functions and classes ๐๏ธ
- JavaScript ES6 fundamentals โก
What you'll learn
- Master ES6 import/export syntax in TypeScript ๐ฆ
- Create type-safe module boundaries ๐ก๏ธ
- Organize code with modern module patterns ๐๏ธ
- Handle default and named exports like a pro โจ
๐ฏ Introduction
Welcome to the world of ES6 modules in TypeScript! ๐ In this guide, weโll explore how to organize your code into reusable, maintainable modules with complete type safety.
Youโll discover how TypeScriptโs module system transforms the way you structure applications. Whether youโre building React components ๐, Node.js APIs ๐ฅ๏ธ, or utility libraries ๐, mastering imports and exports is essential for creating scalable, professional codebases.
By the end of this tutorial, youโll be crafting elegant module architectures that make your teammates say โwow!โ ๐คฉ Letโs dive in! ๐โโ๏ธ
๐ Understanding ES Modules
๐ค What are ES Modules?
ES Modules are like building blocks for your application ๐งฑ. Think of them as individual LEGO pieces that you can combine to create amazing structures. Each module has its own scope and can share specific pieces with other modules.
In TypeScript terms, ES modules provide a standardized way to:
- โจ Encapsulate code - keep related functionality together
- ๐ Share functionality - export what others need, hide implementation details
- ๐ก๏ธ Maintain type safety - TypeScript checks types across module boundaries
- ๐ฆ Enable tree shaking - only bundle code thatโs actually used
๐ก Why Use ES Modules?
Hereโs why developers love ES modules:
- Static Analysis ๐: Tools can analyze dependencies at build time
- Tree Shaking ๐ณ: Remove unused code automatically
- Type Safety ๐: TypeScript validates imports and exports
- Code Organization ๐: Logical separation of concerns
- Reusability โป๏ธ: Share code across projects easily
Real-world example: Imagine building a user management system ๐ฅ. With modules, you can separate authentication, user profiles, and permissions into distinct, reusable pieces!
๐ง Basic Import and Export Syntax
๐ Named Exports and Imports
Letโs start with the most common pattern:
// ๐ utils/math.ts - Export utilities
// ๐งฎ Named exports - can have multiple per file
export const PI = 3.14159;
export const E = 2.71828;
export function add(a: number, b: number): number {
return a + b; // โ Simple addition
}
export function multiply(x: number, y: number): number {
return x * y; // โ๏ธ Multiplication magic
}
// ๐ฏ You can also export after declaration
function subtract(a: number, b: number): number {
return a - b;
}
function divide(a: number, b: number): number {
if (b === 0) throw new Error("Division by zero! ๐ซ");
return a / b;
}
// ๐ฆ Export multiple things at once
export { subtract, divide };
// ๐ components/Calculator.ts - Import utilities
// ๐จ Named imports - destructuring syntax
import { add, multiply, PI } from '../utils/math';
export class Calculator {
// โ Using imported functions
addNumbers(a: number, b: number): number {
console.log(`Adding ${a} + ${b} = ${add(a, b)} ๐งฎ`);
return add(a, b);
}
// ๐ Calculate circle area using imported constant
getCircleArea(radius: number): number {
return PI * multiply(radius, radius); // ๐ต Area = ฯrยฒ
}
}
๐ก Explanation: Named exports let you export multiple things from a single file. Import exactly what you need with destructuring syntax!
๐ฏ Default Exports and Imports
For when you have one main thing to export:
// ๐ models/User.ts - Default export
export interface UserProfile {
id: string;
name: string;
email: string;
avatar?: string;
}
// ๐ค Default export - one per file
export default class User {
constructor(
public id: string,
public name: string,
public email: string
) {}
// ๐ Friendly greeting method
greet(): string {
return `Hello, I'm ${this.name}! ๐`;
}
// ๐ง Get user info
getProfile(): UserProfile {
return {
id: this.id,
name: this.name,
email: this.email
};
}
}
// ๐ services/UserService.ts - Import default
// ๐ฏ Default import - no curly braces needed
import User from '../models/User';
import type { UserProfile } from '../models/User';
export class UserService {
private users: User[] = [];
// โ Create new user
createUser(name: string, email: string): User {
const user = new User(
`user_${Date.now()}`, // ๐ Simple ID generation
name,
email
);
this.users.push(user);
console.log(`Created user: ${user.greet()} ๐`);
return user;
}
// ๐ Find user by ID
findUser(id: string): User | undefined {
return this.users.find(user => user.id === id);
}
// ๐ Get all user profiles
getAllProfiles(): UserProfile[] {
return this.users.map(user => user.getProfile());
}
}
๐ Mixed Exports and Re-exports
Combining different export styles:
// ๐ utils/index.ts - Barrel export pattern
// ๐๏ธ Re-export everything from math utilities
export * from './math';
export * from './string-helpers';
export * from './date-helpers';
// ๐ฏ Default export for the main utility
import { add, multiply } from './math';
export default class UtilityKit {
static quickMath = { add, multiply }; // ๐งฎ Quick access to math
static version = "1.0.0"; // ๐ Version info
}
// ๐ string-helpers.ts
// ๐ค String utility functions
export function capitalize(text: string): string {
return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
}
export function slugify(text: string): string {
return text
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-') // ๐ Replace non-alphanumeric with dashes
.replace(/^-|-$/g, ''); // ๐งน Remove leading/trailing dashes
}
export function truncate(text: string, maxLength: number): string {
if (text.length <= maxLength) return text;
return text.slice(0, maxLength - 3) + '...'; // โ๏ธ Add ellipsis
}
๐ก Practical Examples
๐ Example 1: E-commerce Product System
Letโs build a modular product management system:
// ๐ types/Product.ts - Product type definitions
export interface Product {
id: string;
name: string;
price: number;
category: ProductCategory;
inStock: boolean;
description?: string;
}
export type ProductCategory =
| 'electronics'
| 'clothing'
| 'books'
| 'food'
| 'toys';
export interface CartItem {
product: Product;
quantity: number;
addedAt: Date;
}
// ๐ฐ Price calculation utilities
export const TAX_RATE = 0.08; // 8% tax
export const SHIPPING_COST = 5.99;
export function calculateSubtotal(items: CartItem[]): number {
return items.reduce((sum, item) => {
return sum + (item.product.price * item.quantity);
}, 0);
}
export function calculateTax(subtotal: number): number {
return subtotal * TAX_RATE;
}
export function calculateTotal(items: CartItem[]): number {
const subtotal = calculateSubtotal(items);
const tax = calculateTax(subtotal);
return subtotal + tax + SHIPPING_COST;
}
// ๐ services/ProductService.ts - Product management
import type { Product, ProductCategory } from '../types/Product';
export default class ProductService {
private products: Map<string, Product> = new Map();
// โ Add product to catalog
addProduct(productData: Omit<Product, 'id'>): Product {
const product: Product = {
id: `prod_${Date.now()}_${Math.random().toString(36).slice(2)}`,
...productData
};
this.products.set(product.id, product);
console.log(`๐ฆ Added product: ${product.name} (${this.getCategoryEmoji(product.category)})`);
return product;
}
// ๐ Find products by category
getProductsByCategory(category: ProductCategory): Product[] {
return Array.from(this.products.values())
.filter(product => product.category === category);
}
// ๐ Get available products only
getAvailableProducts(): Product[] {
return Array.from(this.products.values())
.filter(product => product.inStock);
}
// ๐ท๏ธ Update product price
updatePrice(productId: string, newPrice: number): boolean {
const product = this.products.get(productId);
if (!product) {
console.log(`โ Product ${productId} not found`);
return false;
}
const oldPrice = product.price;
product.price = newPrice;
console.log(`๐ฐ Updated ${product.name}: $${oldPrice} โ $${newPrice}`);
return true;
}
// ๐จ Get emoji for category
private getCategoryEmoji(category: ProductCategory): string {
const emojis: Record<ProductCategory, string> = {
electronics: '๐ป',
clothing: '๐',
books: '๐',
food: '๐',
toys: '๐งธ'
};
return emojis[category];
}
}
// ๐ services/CartService.ts - Shopping cart logic
import type { Product, CartItem } from '../types/Product';
import { calculateTotal, calculateSubtotal, calculateTax, SHIPPING_COST } from '../types/Product';
export class CartService {
private items: CartItem[] = [];
// ๐ Add item to cart
addToCart(product: Product, quantity: number = 1): void {
// ๐ Check if item already exists
const existingItem = this.items.find(item => item.product.id === product.id);
if (existingItem) {
existingItem.quantity += quantity;
console.log(`๐ Updated cart: ${product.name} (qty: ${existingItem.quantity})`);
} else {
const cartItem: CartItem = {
product,
quantity,
addedAt: new Date()
};
this.items.push(cartItem);
console.log(`โ Added to cart: ${product.name} ร ${quantity}`);
}
}
// โ Remove item from cart
removeFromCart(productId: string): boolean {
const initialLength = this.items.length;
this.items = this.items.filter(item => item.product.id !== productId);
if (this.items.length < initialLength) {
console.log(`๐๏ธ Removed item from cart`);
return true;
}
return false;
}
// ๐ Get cart summary
getCartSummary(): {
items: CartItem[];
subtotal: number;
tax: number;
shipping: number;
total: number;
itemCount: number;
} {
const subtotal = calculateSubtotal(this.items);
return {
items: [...this.items], // ๐ Copy to prevent mutations
subtotal,
tax: calculateTax(subtotal),
shipping: SHIPPING_COST,
total: calculateTotal(this.items),
itemCount: this.items.reduce((sum, item) => sum + item.quantity, 0)
};
}
// ๐งน Clear cart
clearCart(): void {
this.items = [];
console.log('๐งน Cart cleared');
}
}
๐ฏ Try it yourself: Add a discount system with percentage and fixed amount discounts!
๐ฎ Example 2: Game Module System
Letโs create a modular game architecture:
// ๐ game/entities/Player.ts - Player module
export interface PlayerStats {
health: number;
mana: number;
strength: number;
agility: number;
intelligence: number;
}
export interface PlayerPosition {
x: number;
y: number;
z?: number;
}
export default class Player {
private stats: PlayerStats;
private position: PlayerPosition;
private inventory: string[] = [];
constructor(
public readonly name: string,
initialStats: PlayerStats,
startPosition: PlayerPosition = { x: 0, y: 0 }
) {
this.stats = { ...initialStats };
this.position = { ...startPosition };
console.log(`๐ฎ ${name} has entered the game! โ๏ธ`);
}
// ๐ Move player
moveTo(newPosition: Partial<PlayerPosition>): void {
this.position = { ...this.position, ...newPosition };
console.log(`๐ ${this.name} moved to (${this.position.x}, ${this.position.y})`);
}
// ๐ Manage inventory
addItem(item: string): void {
this.inventory.push(item);
console.log(`๐ฆ ${this.name} picked up: ${item}`);
}
// ๐ Get player info
getInfo(): {
name: string;
stats: PlayerStats;
position: PlayerPosition;
inventoryCount: number;
} {
return {
name: this.name,
stats: { ...this.stats },
position: { ...this.position },
inventoryCount: this.inventory.length
};
}
// โ๏ธ Take damage
takeDamage(amount: number): boolean {
this.stats.health = Math.max(0, this.stats.health - amount);
console.log(`๐ฅ ${this.name} took ${amount} damage! Health: ${this.stats.health}`);
return this.stats.health > 0; // Returns true if still alive
}
}
// ๐ game/systems/BattleSystem.ts - Combat logic
import type Player from '../entities/Player';
export interface BattleResult {
winner: Player | null;
rounds: number;
finalHealths: Record<string, number>;
battleLog: string[];
}
export class BattleSystem {
private log: string[] = [];
// โ๏ธ Simulate battle between players
fight(player1: Player, player2: Player): BattleResult {
this.log = [];
let rounds = 0;
const maxRounds = 10; // ๐ก๏ธ Prevent infinite battles
this.addLog(`๐ฅ Battle begins: ${player1.name} vs ${player2.name}!`);
while (rounds < maxRounds) {
rounds++;
this.addLog(`\n--- Round ${rounds} ---`);
// ๐ฒ Calculate damage (simplified)
const p1Info = player1.getInfo();
const p2Info = player2.getInfo();
const p1Damage = this.calculateDamage(p1Info.stats);
const p2Damage = this.calculateDamage(p2Info.stats);
// ๐ฅ Apply damage
const p1Alive = player2.takeDamage(p1Damage);
const p2Alive = player1.takeDamage(p2Damage);
this.addLog(`${player1.name} deals ${p1Damage} damage!`);
this.addLog(`${player2.name} deals ${p2Damage} damage!`);
// ๐ Check for winner
if (!p1Alive && !p2Alive) {
this.addLog(`๐ Both players fell! It's a draw!`);
return this.createResult(null, rounds, player1, player2);
} else if (!p1Alive) {
this.addLog(`๐ ${player2.name} wins!`);
return this.createResult(player2, rounds, player1, player2);
} else if (!p2Alive) {
this.addLog(`๐ ${player1.name} wins!`);
return this.createResult(player1, rounds, player1, player2);
}
}
// ๐ Time limit reached
this.addLog(`โฐ Time limit reached! Battle ends in a draw.`);
return this.createResult(null, rounds, player1, player2);
}
// ๐ฒ Calculate damage based on stats
private calculateDamage(stats: any): number {
const baseDamage = stats.strength * 2;
const critChance = stats.agility / 100;
const isCrit = Math.random() < critChance;
return Math.floor(baseDamage * (isCrit ? 1.5 : 1));
}
// ๐ Add to battle log
private addLog(message: string): void {
this.log.push(message);
console.log(message);
}
// ๐ Create battle result
private createResult(
winner: Player | null,
rounds: number,
player1: Player,
player2: Player
): BattleResult {
const p1Info = player1.getInfo();
const p2Info = player2.getInfo();
return {
winner,
rounds,
finalHealths: {
[player1.name]: p1Info.stats.health,
[player2.name]: p2Info.stats.health
},
battleLog: [...this.log]
};
}
}
// ๐ game/index.ts - Main game module (barrel export)
// ๐๏ธ Re-export all game modules
export { default as Player } from './entities/Player';
export type { PlayerStats, PlayerPosition } from './entities/Player';
export { BattleSystem } from './systems/BattleSystem';
export type { BattleResult } from './systems/BattleSystem';
// ๐ฎ Default export for easy game setup
export default class Game {
static createWarrior(name: string): Player {
return new Player(name, {
health: 100,
mana: 20,
strength: 15,
agility: 10,
intelligence: 5
});
}
static createMage(name: string): Player {
return new Player(name, {
health: 60,
mana: 100,
strength: 5,
agility: 8,
intelligence: 20
});
}
static createRogue(name: string): Player {
return new Player(name, {
health: 80,
mana: 40,
strength: 10,
agility: 18,
intelligence: 12
});
}
}
๐ Advanced Module Patterns
๐งโโ๏ธ Type-Only Imports and Exports
When you only need types, not runtime values:
// ๐ types/api.ts - Type definitions only
export interface ApiResponse<T> {
data: T;
status: number;
message: string;
timestamp: Date;
}
export interface User {
id: string;
name: string;
email: string;
}
export interface ApiError {
code: string;
message: string;
details?: unknown;
}
// ๐ services/api.ts - Using type-only imports
// ๐ฏ Import types only - no runtime impact!
import type { ApiResponse, User, ApiError } from '../types/api';
export class ApiClient {
// ๐ Fetch user data
async getUser(id: string): Promise<ApiResponse<User>> {
try {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
return {
data,
status: response.status,
message: 'Success! ๐',
timestamp: new Date()
};
} catch (error) {
throw {
code: 'FETCH_ERROR',
message: 'Failed to fetch user ๐ฐ',
details: error
} as ApiError;
}
}
}
๐๏ธ Dynamic Imports
For code splitting and lazy loading:
// ๐ utils/lazy-loader.ts - Dynamic import utilities
export async function loadMathUtils(): Promise<typeof import('./math')> {
// ๐ Dynamically import only when needed
console.log('โณ Loading math utilities...');
const mathModule = await import('./math');
console.log('โ
Math utilities loaded!');
return mathModule;
}
export async function loadHeavyLibrary(): Promise<any> {
// ๐ฆ Conditional loading based on environment
if (typeof window !== 'undefined') {
// ๐ Browser environment
return await import('./browser-heavy-lib');
} else {
// ๐ฅ๏ธ Node.js environment
return await import('./node-heavy-lib');
}
}
// ๐ฏ Generic dynamic loader with error handling
export async function dynamicImport<T>(
modulePath: string,
fallback?: T
): Promise<T> {
try {
const module = await import(modulePath);
return module.default || module;
} catch (error) {
console.error(`โ Failed to load module: ${modulePath}`, error);
if (fallback !== undefined) {
console.log('๐ Using fallback...');
return fallback;
}
throw error;
}
}
๐ง Module Augmentation
Extending existing modules with new functionality:
// ๐ extensions/array-extensions.ts - Extending Array
// ๐ฏ Declare module augmentation
declare global {
interface Array<T> {
shuffle(): T[]; // ๐ฒ Shuffle array randomly
unique(): T[]; // ๐ฏ Remove duplicates
groupBy<K extends keyof T>(key: K): Record<string, T[]>; // ๐ Group by property
}
}
// ๐ ๏ธ Implement the extensions
Array.prototype.shuffle = function<T>(this: T[]): T[] {
const shuffled = [...this];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
};
Array.prototype.unique = function<T>(this: T[]): T[] {
return [...new Set(this)];
};
Array.prototype.groupBy = function<T, K extends keyof T>(
this: T[],
key: K
): Record<string, T[]> {
return this.reduce((groups, item) => {
const groupKey = String(item[key]);
if (!groups[groupKey]) {
groups[groupKey] = [];
}
groups[groupKey].push(item);
return groups;
}, {} as Record<string, T[]>);
};
// ๐ Export to make it a module
export {};
// ๐ main.ts - Using augmented arrays
import './extensions/array-extensions'; // ๐ง Import extensions
// ๐ฒ Test the new array methods
const numbers = [1, 2, 3, 4, 5, 2, 3, 1];
console.log('Original:', numbers);
console.log('Shuffled:', numbers.shuffle()); // ๐ฒ [3, 1, 5, 2, 4, 2, 3, 1]
console.log('Unique:', numbers.unique()); // ๐ฏ [1, 2, 3, 4, 5]
const users = [
{ name: 'Alice', role: 'admin' },
{ name: 'Bob', role: 'user' },
{ name: 'Charlie', role: 'admin' }
];
console.log('Grouped by role:', users.groupBy('role'));
// ๐ { admin: [...], user: [...] }
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Circular Dependencies
// โ Wrong way - creates circular dependency!
// ๐ user.ts
import { Post } from './post';
export class User {
posts: Post[] = [];
}
// ๐ post.ts
import { User } from './user'; // ๐ฅ Circular import!
export class Post {
author: User; // This creates a cycle
}
// โ
Correct way - break the cycle with types!
// ๐ types.ts - Shared type definitions
export interface IUser {
id: string;
name: string;
}
export interface IPost {
id: string;
title: string;
authorId: string; // ๐ฏ Reference by ID, not object
}
// ๐ user.ts
import type { IUser, IPost } from './types';
export class User implements IUser {
constructor(
public id: string,
public name: string,
private posts: IPost[] = []
) {}
addPost(post: IPost): void {
this.posts.push(post);
}
}
// ๐ post.ts
import type { IPost } from './types';
export class Post implements IPost {
constructor(
public id: string,
public title: string,
public authorId: string // ๐ฏ No circular dependency!
) {}
}
๐คฏ Pitfall 2: Default Export vs Named Export Confusion
// โ Confusing mixed patterns
// ๐ bad-example.ts
export default function calculate() { /* ... */ }
export const PI = 3.14159;
export { multiply, divide }; // Where are these defined? ๐ฐ
// Multiple import styles needed:
import calculate from './bad-example'; // Default
import { PI, multiply, divide } from './bad-example'; // Named
// โ
Consistent and clear patterns
// ๐ math-utils.ts - All named exports
export const PI = 3.14159;
export const E = 2.71828;
export function calculate(operation: string, a: number, b: number): number {
// Implementation here
return a + b; // Simplified
}
export function multiply(a: number, b: number): number {
return a * b;
}
export function divide(a: number, b: number): number {
return a / b;
}
// ๐ calculator.ts - Single responsibility default export
import { calculate, multiply, divide, PI } from './math-utils';
export default class Calculator {
// Uses imported utilities
compute(op: string, a: number, b: number): number {
return calculate(op, a, b);
}
}
๐ฅ Pitfall 3: Importing Large Modules
// โ Importing everything - affects bundle size!
import * as lodash from 'lodash'; // ๐ฅ Imports entire library!
function processArray(items: number[]): number[] {
return lodash.shuffle(lodash.uniq(items)); // Only using 2 functions
}
// โ
Import only what you need - better performance!
import { shuffle, uniq } from 'lodash'; // ๐ฏ Tree-shakeable
// Or even better - specific imports
import shuffle from 'lodash/shuffle';
import uniq from 'lodash/uniq';
function processArray(items: number[]): number[] {
return shuffle(uniq(items)); // โจ Only bundles what's used
}
๐ ๏ธ Best Practices
- ๐ฏ Use Barrel Exports: Create index.ts files to simplify imports
- ๐ Prefer Named Exports: Theyโre more explicit and tree-shakeable
- ๐ก๏ธ Use Type-Only Imports: When you only need types, not runtime values
- ๐จ Keep Modules Focused: One responsibility per module
- โจ Avoid Circular Dependencies: Use interfaces and separate type files
- ๐ Make Interfaces Explicit: Export types separately from implementations
- ๐ฆ Think About Bundle Size: Import only what you need
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Task Management System
Create a modular task manager with the following modules:
๐ Requirements:
- ๐ Task model with types (Task, Priority, Status)
- ๐๏ธ TaskService for CRUD operations
- ๐ Analytics service for task statistics
- ๐ Filter service for searching/filtering tasks
- ๐ฆ Main barrel export that ties everything together
- ๐จ Use both named and default exports appropriately!
๐ Bonus Points:
- Add dynamic imports for heavy operations
- Implement module augmentation for Date objects
- Create type-only imports where appropriate
๐ก Solution
๐ Click to see solution
// ๐ types/Task.ts - Type definitions
export interface Task {
id: string;
title: string;
description?: string;
priority: Priority;
status: Status;
createdAt: Date;
updatedAt: Date;
dueDate?: Date;
tags: string[];
}
export type Priority = 'low' | 'medium' | 'high' | 'urgent';
export type Status = 'todo' | 'in_progress' | 'review' | 'done';
export interface TaskFilter {
status?: Status;
priority?: Priority;
tags?: string[];
dueDateBefore?: Date;
dueDateAfter?: Date;
}
export interface TaskStats {
total: number;
byStatus: Record<Status, number>;
byPriority: Record<Priority, number>;
overdue: number;
completedThisWeek: number;
}
// ๐ services/TaskService.ts - Main task operations
import type { Task, Priority, Status } from '../types/Task';
export default class TaskService {
private tasks: Map<string, Task> = new Map();
// โ Create new task
createTask(data: {
title: string;
description?: string;
priority?: Priority;
dueDate?: Date;
tags?: string[];
}): Task {
const task: Task = {
id: `task_${Date.now()}_${Math.random().toString(36).slice(2)}`,
title: data.title,
description: data.description,
priority: data.priority || 'medium',
status: 'todo',
createdAt: new Date(),
updatedAt: new Date(),
dueDate: data.dueDate,
tags: data.tags || []
};
this.tasks.set(task.id, task);
console.log(`โ
Created task: ${task.title} (${this.getPriorityEmoji(task.priority)})`);
return task;
}
// ๐ Update task
updateTask(id: string, updates: Partial<Pick<Task, 'title' | 'description' | 'priority' | 'status' | 'dueDate' | 'tags'>>): Task | null {
const task = this.tasks.get(id);
if (!task) {
console.log(`โ Task ${id} not found`);
return null;
}
const updatedTask: Task = {
...task,
...updates,
updatedAt: new Date()
};
this.tasks.set(id, updatedTask);
console.log(`๐ Updated task: ${updatedTask.title}`);
return updatedTask;
}
// ๐๏ธ Delete task
deleteTask(id: string): boolean {
const deleted = this.tasks.delete(id);
if (deleted) {
console.log(`๐๏ธ Deleted task: ${id}`);
}
return deleted;
}
// ๐ Get task by ID
getTask(id: string): Task | undefined {
return this.tasks.get(id);
}
// ๐ Get all tasks
getAllTasks(): Task[] {
return Array.from(this.tasks.values());
}
// ๐จ Get emoji for priority
private getPriorityEmoji(priority: Priority): string {
const emojis: Record<Priority, string> = {
low: '๐ข',
medium: '๐ก',
high: '๐ ',
urgent: '๐ด'
};
return emojis[priority];
}
}
// ๐ services/FilterService.ts - Search and filter
import type { Task, TaskFilter, Status, Priority } from '../types/Task';
export class FilterService {
// ๐ Filter tasks based on criteria
filterTasks(tasks: Task[], filter: TaskFilter): Task[] {
return tasks.filter(task => {
// Status filter
if (filter.status && task.status !== filter.status) {
return false;
}
// Priority filter
if (filter.priority && task.priority !== filter.priority) {
return false;
}
// Tags filter (task must have at least one matching tag)
if (filter.tags && filter.tags.length > 0) {
const hasMatchingTag = filter.tags.some(tag =>
task.tags.includes(tag)
);
if (!hasMatchingTag) return false;
}
// Due date filters
if (filter.dueDateBefore && task.dueDate && task.dueDate > filter.dueDateBefore) {
return false;
}
if (filter.dueDateAfter && task.dueDate && task.dueDate < filter.dueDateAfter) {
return false;
}
return true;
});
}
// ๐ค Search tasks by text
searchTasks(tasks: Task[], query: string): Task[] {
const searchTerms = query.toLowerCase().split(' ');
return tasks.filter(task => {
const searchableText = [
task.title,
task.description || '',
...task.tags
].join(' ').toLowerCase();
return searchTerms.every(term =>
searchableText.includes(term)
);
});
}
// ๐ Sort tasks by various criteria
sortTasks(tasks: Task[], sortBy: 'createdAt' | 'dueDate' | 'priority' | 'title', ascending = true): Task[] {
const sorted = [...tasks].sort((a, b) => {
let comparison = 0;
switch (sortBy) {
case 'createdAt':
comparison = a.createdAt.getTime() - b.createdAt.getTime();
break;
case 'dueDate':
const aDate = a.dueDate?.getTime() || Infinity;
const bDate = b.dueDate?.getTime() || Infinity;
comparison = aDate - bDate;
break;
case 'priority':
const priorityOrder: Record<Priority, number> = {
urgent: 0, high: 1, medium: 2, low: 3
};
comparison = priorityOrder[a.priority] - priorityOrder[b.priority];
break;
case 'title':
comparison = a.title.localeCompare(b.title);
break;
}
return ascending ? comparison : -comparison;
});
console.log(`๐ Sorted ${sorted.length} tasks by ${sortBy} (${ascending ? 'ascending' : 'descending'})`);
return sorted;
}
}
// ๐ services/AnalyticsService.ts - Task analytics
import type { Task, TaskStats, Status, Priority } from '../types/Task';
export class AnalyticsService {
// ๐ Generate comprehensive task statistics
generateStats(tasks: Task[]): TaskStats {
const now = new Date();
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
const byStatus: Record<Status, number> = {
todo: 0,
in_progress: 0,
review: 0,
done: 0
};
const byPriority: Record<Priority, number> = {
low: 0,
medium: 0,
high: 0,
urgent: 0
};
let overdue = 0;
let completedThisWeek = 0;
for (const task of tasks) {
// Count by status
byStatus[task.status]++;
// Count by priority
byPriority[task.priority]++;
// Count overdue tasks
if (task.dueDate && task.dueDate < now && task.status !== 'done') {
overdue++;
}
// Count completed this week
if (task.status === 'done' && task.updatedAt >= weekAgo) {
completedThisWeek++;
}
}
const stats: TaskStats = {
total: tasks.length,
byStatus,
byPriority,
overdue,
completedThisWeek
};
console.log('๐ Generated task statistics:', stats);
return stats;
}
// ๐ฏ Get productivity insights
getProductivityInsights(tasks: Task[]): string[] {
const stats = this.generateStats(tasks);
const insights: string[] = [];
// Completion rate
const completionRate = (stats.byStatus.done / stats.total) * 100;
if (completionRate > 80) {
insights.push('๐ Excellent completion rate! You\'re crushing it!');
} else if (completionRate > 60) {
insights.push('๐ Good progress! Keep up the momentum!');
} else {
insights.push('๐ช Focus on completing more tasks!');
}
// Overdue tasks
if (stats.overdue > 0) {
insights.push(`โ ๏ธ You have ${stats.overdue} overdue tasks - prioritize these!`);
} else {
insights.push('โ
No overdue tasks - great time management!');
}
// Priority distribution
const urgentRatio = (stats.byPriority.urgent / stats.total) * 100;
if (urgentRatio > 30) {
insights.push('๐ฅ Too many urgent tasks - consider better planning!');
}
return insights;
}
}
// ๐ index.ts - Barrel export
// ๐๏ธ Export all types
export type { Task, Priority, Status, TaskFilter, TaskStats } from './types/Task';
// ๐ฆ Export services
export { default as TaskService } from './services/TaskService';
export { FilterService } from './services/FilterService';
export { AnalyticsService } from './services/AnalyticsService';
// ๐ฏ Default export for main task manager
import TaskService from './services/TaskService';
import { FilterService } from './services/FilterService';
import { AnalyticsService } from './services/AnalyticsService';
export default class TaskManager {
constructor(
private taskService = new TaskService(),
private filterService = new FilterService(),
private analyticsService = new AnalyticsService()
) {}
// ๐ฎ Convenient access to all services
get tasks() { return this.taskService; }
get filter() { return this.filterService; }
get analytics() { return this.analyticsService; }
// ๐ Quick dashboard
getDashboard() {
const allTasks = this.taskService.getAllTasks();
const stats = this.analyticsService.generateStats(allTasks);
const insights = this.analyticsService.getProductivityInsights(allTasks);
return {
stats,
insights,
recentTasks: this.filterService.sortTasks(allTasks, 'createdAt', false).slice(0, 5)
};
}
}
// ๐ example-usage.ts - Using the task manager
import TaskManager, { TaskService, FilterService } from './index';
// ๐ฎ Using the main TaskManager class
const manager = new TaskManager();
// โ Create some tasks
const task1 = manager.tasks.createTask({
title: 'Learn TypeScript modules',
priority: 'high',
tags: ['learning', 'typescript'],
dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 days from now
});
const task2 = manager.tasks.createTask({
title: 'Review pull requests',
priority: 'medium',
tags: ['work', 'review']
});
// ๐ Update task status
manager.tasks.updateTask(task1.id, { status: 'in_progress' });
// ๐ Get dashboard
const dashboard = manager.getDashboard();
console.log('Dashboard:', dashboard);
// ๐ Filter high priority tasks
const highPriorityTasks = manager.filter.filterTasks(
manager.tasks.getAllTasks(),
{ priority: 'high' }
);
console.log('High priority tasks:', highPriorityTasks);
๐ Key Takeaways
Youโve mastered ES6 modules in TypeScript! Hereโs what you can now do:
- โ Create clean module boundaries with proper imports and exports ๐ช
- โ Use type-only imports to optimize bundle size ๐ก๏ธ
- โ Organize code with barrel exports for better developer experience ๐ฏ
- โ Handle circular dependencies with smart architecture patterns ๐ง
- โ Build scalable applications with modular, reusable code ๐
Remember: Good module architecture is like a well-organized toolbox - everything has its place and you can find what you need instantly! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered ES6 modules in TypeScript!
Hereโs what to do next:
- ๐ป Practice with the task manager exercise above
- ๐๏ธ Refactor existing projects to use better module organization
- ๐ Move on to our next tutorial: CommonJS Modules: Node.js Compatibility
- ๐ Share your modular architectures with the community!
Remember: Great applications are built from well-designed modules. Keep building amazing things! ๐
Happy module building! ๐๐ฆโจ