+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 96 of 355

๐Ÿ“ฆ ES Modules in TypeScript: Import and Export Mastery

Master ES6 modules in TypeScript with type-safe imports, exports, and modern module patterns for scalable applications ๐Ÿš€

๐Ÿš€Intermediate
18 min read

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:

  1. Static Analysis ๐Ÿ”: Tools can analyze dependencies at build time
  2. Tree Shaking ๐ŸŒณ: Remove unused code automatically
  3. Type Safety ๐Ÿ”’: TypeScript validates imports and exports
  4. Code Organization ๐Ÿ“: Logical separation of concerns
  5. 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

  1. ๐ŸŽฏ Use Barrel Exports: Create index.ts files to simplify imports
  2. ๐Ÿ“ Prefer Named Exports: Theyโ€™re more explicit and tree-shakeable
  3. ๐Ÿ›ก๏ธ Use Type-Only Imports: When you only need types, not runtime values
  4. ๐ŸŽจ Keep Modules Focused: One responsibility per module
  5. โœจ Avoid Circular Dependencies: Use interfaces and separate type files
  6. ๐Ÿ”’ Make Interfaces Explicit: Export types separately from implementations
  7. ๐Ÿ“ฆ 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:

  1. ๐Ÿ’ป Practice with the task manager exercise above
  2. ๐Ÿ—๏ธ Refactor existing projects to use better module organization
  3. ๐Ÿ“š Move on to our next tutorial: CommonJS Modules: Node.js Compatibility
  4. ๐ŸŒŸ Share your modular architectures with the community!

Remember: Great applications are built from well-designed modules. Keep building amazing things! ๐Ÿš€


Happy module building! ๐ŸŽ‰๐Ÿ“ฆโœจ