READMEJavaScript

README

Module 14 ES Modules / .5 Module Patterns

Concept Lesson
Advanced
4 min

Learning Objective

Understand Module 14 ES Modules well enough to explain it, recognize it in JavaScript, and apply it in a small task.

Why It Matters

This concept is part of the foundation that later lessons and projects assume you already understand.

Learning ObjectivesSingleton PatternEs Modules Are Natural SingletonsStateful SingletonFactory Pattern
Private notes
0/8000

Notes stay private to your browser until account sync is configured.

README
1 min read18 headings

14.5 Module Patterns

📖 Introduction

Module patterns are architectural approaches for organizing code using JavaScript modules. This section covers common patterns that leverage ES Modules to create maintainable, scalable applications.

┌─────────────────────────────────────────────────────────────────────────────┐
│                       MODULE PATTERNS OVERVIEW                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐        │
│   │   SINGLETON     │    │    FACTORY      │    │    FACADE       │        │
│   │                 │    │                 │    │                 │        │
│   │  One instance   │    │  Create objects │    │  Simplify API   │        │
│   │  shared across  │    │  with hidden    │    │  for complex    │        │
│   │  entire app     │    │  implementation │    │  subsystems     │        │
│   └─────────────────┘    └─────────────────┘    └─────────────────┘        │
│                                                                             │
│   ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐        │
│   │    ADAPTER      │    │   DEPENDENCY    │    │   BARREL/       │        │
│   │                 │    │   INJECTION     │    │   RE-EXPORT     │        │
│   │  Convert one    │    │                 │    │                 │        │
│   │  interface to   │    │  Provide deps   │    │  Aggregate &    │        │
│   │  another        │    │  from outside   │    │  expose modules │        │
│   └─────────────────┘    └─────────────────┘    └─────────────────┘        │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

🎯 Learning Objectives

By the end of this section, you will:

  • ✅ Implement the Singleton pattern with modules
  • ✅ Create factory modules
  • ✅ Build facade patterns for complex systems
  • ✅ Use adapter patterns for API compatibility
  • ✅ Implement dependency injection
  • ✅ Organize code with barrel files
  • ✅ Apply module best practices

1️⃣ Singleton Pattern

ES Modules Are Natural Singletons

// ═══════════════════════════════════════════════════════════════
// config.js - Module-level Singleton
// ═══════════════════════════════════════════════════════════════

// This object is created ONCE when module is first imported
// All subsequent imports get the SAME instance

const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3,
  debug: process.env.NODE_ENV === 'development',
};

// Freeze to prevent modifications (true singleton)
Object.freeze(config);

export default config;

// ═══════════════════════════════════════════════════════════════
// Usage in multiple files - same instance
// ═══════════════════════════════════════════════════════════════

// fileA.js
import config from './config.js';
console.log(config.apiUrl); // https://api.example.com

// fileB.js
import config from './config.js';
console.log(config === configFromFileA); // true (same object!)
┌─────────────────────────────────────────────────────────────────────┐
│                    SINGLETON WITH MODULES                           │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   WHY MODULES ARE SINGLETONS:                                       │
│                                                                     │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │                                                              │  │
│   │   1. First import of config.js                               │  │
│   │      ↓                                                       │  │
│   │   2. Module code executes ONCE                               │  │
│   │      ↓                                                       │  │
│   │   3. Exports are cached                                      │  │
│   │      ↓                                                       │  │
│   │   4. Second import of config.js                              │  │
│   │      ↓                                                       │  │
│   │   5. Returns cached exports (NO re-execution!)               │  │
│   │                                                              │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│   COMMON SINGLETON USE CASES:                                       │
│   • Configuration                                                   │
│   • Database connections                                            │
│   • Logging service                                                 │
│   • State management (stores)                                       │
│   • Event bus                                                       │
│   • Cache                                                           │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Stateful Singleton

// ═══════════════════════════════════════════════════════════════
// store.js - Singleton state store
// ═══════════════════════════════════════════════════════════════

// Private state (not exported)
let state = {
  user: null,
  isAuthenticated: false,
  theme: 'light',
};

// Subscribers for state changes
const subscribers = new Set();

// Public API
export const store = {
  // Get current state (return copy to prevent direct mutation)
  getState() {
    return { ...state };
  },

  // Update state
  setState(updates) {
    state = { ...state, ...updates };
    this.notify();
  },

  // Subscribe to changes
  subscribe(callback) {
    subscribers.add(callback);
    // Return unsubscribe function
    return () => subscribers.delete(callback);
  },

  // Notify all subscribers
  notify() {
    subscribers.forEach((callback) => callback(this.getState()));
  },
};

// Freeze the API (not the state)
Object.freeze(store);

// ═══════════════════════════════════════════════════════════════
// Usage
// ═══════════════════════════════════════════════════════════════

// component.js
import { store } from './store.js';

// Subscribe to changes
const unsubscribe = store.subscribe((newState) => {
  console.log('State changed:', newState);
});

// Update state
store.setState({ user: { name: 'Alice' }, isAuthenticated: true });

// Later: unsubscribe
unsubscribe();

2️⃣ Factory Pattern

Creating Objects with Hidden Implementation

// ═══════════════════════════════════════════════════════════════
// userFactory.js - Factory module
// ═══════════════════════════════════════════════════════════════

// Private counter (not exported)
let nextId = 1;

// Private validation (not exported)
function validateEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

// Private default values
const defaults = {
  role: 'user',
  active: true,
  createdAt: () => new Date(),
};

// Factory function (exported)
export function createUser({ name, email, role = defaults.role }) {
  // Validation
  if (!name || typeof name !== 'string') {
    throw new Error('Name is required');
  }
  if (!validateEmail(email)) {
    throw new Error('Invalid email format');
  }

  // Create user object with private id
  const id = nextId++;

  return {
    id,
    name,
    email,
    role,
    active: defaults.active,
    createdAt: defaults.createdAt(),

    // Methods
    deactivate() {
      this.active = false;
    },
    promote() {
      this.role = 'admin';
    },
    toJSON() {
      return { id, name, email, role, active: this.active };
    },
  };
}

// Factory for admins
export function createAdmin(userData) {
  const user = createUser({ ...userData, role: 'admin' });

  return {
    ...user,
    permissions: ['read', 'write', 'delete', 'admin'],

    grantPermission(permission) {
      if (!this.permissions.includes(permission)) {
        this.permissions.push(permission);
      }
    },
  };
}

// ═══════════════════════════════════════════════════════════════
// Usage
// ═══════════════════════════════════════════════════════════════

import { createUser, createAdmin } from './userFactory.js';

const user1 = createUser({ name: 'Alice', email: 'alice@example.com' });
const user2 = createUser({ name: 'Bob', email: 'bob@example.com' });
const admin = createAdmin({ name: 'Charlie', email: 'charlie@example.com' });

console.log(user1.id); // 1
console.log(user2.id); // 2
console.log(admin.id); // 3
┌─────────────────────────────────────────────────────────────────────┐
│                      FACTORY PATTERN                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌──────────────────────────────────────────────────────────────┐ │
│   │                      FACTORY MODULE                           │ │
│   │  ┌────────────────────────────────────────────────────────┐  │ │
│   │  │  PRIVATE (not exported)                                 │  │ │
│   │  │  • let counter = 0;                                     │  │ │
│   │  │  • function validate() {...}                            │  │ │
│   │  │  • const defaults = {...}                               │  │ │
│   │  └────────────────────────────────────────────────────────┘  │ │
│   │                           │                                   │ │
│   │                           ▼                                   │ │
│   │  ┌────────────────────────────────────────────────────────┐  │ │
│   │  │  PUBLIC (exported)                                      │  │ │
│   │  │  • export function createThing() {...}                  │  │ │
│   │  │  • export function createSpecialThing() {...}           │  │ │
│   │  └────────────────────────────────────────────────────────┘  │ │
│   └──────────────────────────────────────────────────────────────┘ │
│                                                                     │
│   BENEFITS:                                                         │
│   • Hide implementation details                                     │
│   • Consistent object creation                                      │
│   • Validation in one place                                         │
│   • Easy to extend/modify                                           │
│   • Can track instances (counter)                                   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Async Factory

// ═══════════════════════════════════════════════════════════════
// connectionFactory.js - Async factory
// ═══════════════════════════════════════════════════════════════

// Private connection pool
const pool = new Map();

// Private config
const defaultConfig = {
  host: 'localhost',
  port: 5432,
  maxConnections: 10,
};

// Async factory function
export async function createConnection(options = {}) {
  const config = { ...defaultConfig, ...options };
  const key = `${config.host}:${config.port}`;

  // Return existing connection if available
  if (pool.has(key) && pool.get(key).isAlive()) {
    return pool.get(key);
  }

  // Create new connection
  console.log(`Connecting to ${key}...`);

  // Simulate async connection
  await new Promise((resolve) => setTimeout(resolve, 100));

  const connection = {
    config,
    connected: true,

    isAlive() {
      return this.connected;
    },

    async query(sql) {
      if (!this.connected) throw new Error('Not connected');
      console.log(`Executing: ${sql}`);
      return { rows: [], sql };
    },

    async close() {
      this.connected = false;
      pool.delete(key);
      console.log(`Connection to ${key} closed`);
    },
  };

  pool.set(key, connection);
  return connection;
}

// Cleanup helper
export async function closeAllConnections() {
  const connections = Array.from(pool.values());
  await Promise.all(connections.map((conn) => conn.close()));
}

3️⃣ Facade Pattern

Simplifying Complex Subsystems

// ═══════════════════════════════════════════════════════════════
// Complex subsystem modules
// ═══════════════════════════════════════════════════════════════

// auth/token.js
export function generateToken(user) {
  return `token_${user.id}_${Date.now()}`;
}

export function verifyToken(token) {
  return token && token.startsWith('token_');
}

// auth/session.js
const sessions = new Map();

export function createSession(userId, token) {
  const session = { userId, token, createdAt: new Date() };
  sessions.set(userId, session);
  return session;
}

export function getSession(userId) {
  return sessions.get(userId);
}

export function destroySession(userId) {
  sessions.delete(userId);
}

// auth/password.js
export async function hashPassword(password) {
  // Simulated hashing
  return `hashed_${password}`;
}

export async function verifyPassword(password, hash) {
  return hash === `hashed_${password}`;
}

// users/repository.js
const users = new Map();

export function findByEmail(email) {
  return Array.from(users.values()).find((u) => u.email === email);
}

export function findById(id) {
  return users.get(id);
}

export function save(user) {
  users.set(user.id, user);
  return user;
}

// ═══════════════════════════════════════════════════════════════
// auth.js - FACADE that simplifies all the above
// ═══════════════════════════════════════════════════════════════

import { generateToken, verifyToken } from './auth/token.js';
import { createSession, getSession, destroySession } from './auth/session.js';
import { hashPassword, verifyPassword } from './auth/password.js';
import { findByEmail, findById, save } from './users/repository.js';

// Simple, unified API
export const auth = {
  async register(email, password, name) {
    // Check if user exists
    if (findByEmail(email)) {
      throw new Error('Email already registered');
    }

    // Hash password and create user
    const hashedPassword = await hashPassword(password);
    const user = save({
      id: Date.now(),
      email,
      name,
      password: hashedPassword,
    });

    // Auto-login after registration
    return this.login(email, password);
  },

  async login(email, password) {
    // Find user
    const user = findByEmail(email);
    if (!user) {
      throw new Error('Invalid credentials');
    }

    // Verify password
    const valid = await verifyPassword(password, user.password);
    if (!valid) {
      throw new Error('Invalid credentials');
    }

    // Create session
    const token = generateToken(user);
    createSession(user.id, token);

    return { user: { id: user.id, email, name: user.name }, token };
  },

  logout(userId) {
    destroySession(userId);
  },

  isAuthenticated(userId) {
    const session = getSession(userId);
    return session && verifyToken(session.token);
  },

  getCurrentUser(userId) {
    if (!this.isAuthenticated(userId)) return null;
    const user = findById(userId);
    return user ? { id: user.id, email: user.email, name: user.name } : null;
  },
};

// ═══════════════════════════════════════════════════════════════
// Usage - Simple API hides complexity
// ═══════════════════════════════════════════════════════════════

import { auth } from './auth.js';

// Simple usage - don't need to know about tokens, sessions, hashing
const result = await auth.register('alice@example.com', 'password123', 'Alice');
console.log(result.token);

const loginResult = await auth.login('alice@example.com', 'password123');
console.log(auth.isAuthenticated(loginResult.user.id)); // true

auth.logout(loginResult.user.id);
console.log(auth.isAuthenticated(loginResult.user.id)); // false
┌─────────────────────────────────────────────────────────────────────┐
│                       FACADE PATTERN                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   WITHOUT FACADE:                   WITH FACADE:                    │
│                                                                     │
│   // Client must know all          // Client uses simple API        │
│   // subsystem details             import { auth } from './auth'    │
│                                                                     │
│   import { token }                  auth.login(email, pass);        │
│   import { session }                auth.logout(userId);            │
│   import { password }               auth.isAuthenticated(id);       │
│   import { user }                                                   │
│                                                                     │
│   const hash = password.hash()                                      │
│   const user = user.find()                                          │
│   const tok = token.generate()                                      │
│   session.create(...)                                               │
│                                                                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌────────────────────────────────────────────────────────────┐   │
│   │                         CLIENT                              │   │
│   │                           │                                 │   │
│   │                           ▼                                 │   │
│   │              ┌──────────────────────┐                       │   │
│   │              │       FACADE         │ ◀── Simple API       │   │
│   │              └──────────────────────┘                       │   │
│   │                    │    │    │                              │   │
│   │          ┌─────────┘    │    └─────────┐                    │   │
│   │          ▼              ▼              ▼                    │   │
│   │   ┌──────────┐   ┌──────────┐   ┌──────────┐               │   │
│   │   │ Subsys A │   │ Subsys B │   │ Subsys C │               │   │
│   │   └──────────┘   └──────────┘   └──────────┘               │   │
│   │                                                             │   │
│   └────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

4️⃣ Adapter Pattern

Converting Interfaces

// ═══════════════════════════════════════════════════════════════
// Different analytics libraries with different APIs
// ═══════════════════════════════════════════════════════════════

// googleAnalytics.js - Google Analytics API
export const ga = {
  send(hitType, eventCategory, eventAction, eventLabel) {
    console.log(
      `GA: ${hitType} - ${eventCategory}/${eventAction}/${eventLabel}`
    );
  },
  pageview(path) {
    console.log(`GA Pageview: ${path}`);
  },
};

// mixpanel.js - Mixpanel API (different structure!)
export const mixpanel = {
  track(eventName, properties) {
    console.log(`Mixpanel: ${eventName}`, properties);
  },
  page(pageName, properties) {
    console.log(`Mixpanel Page: ${pageName}`, properties);
  },
};

// ═══════════════════════════════════════════════════════════════
// analyticsAdapter.js - Unified interface
// ═══════════════════════════════════════════════════════════════

import { ga } from './googleAnalytics.js';
import { mixpanel } from './mixpanel.js';

// Adapter for Google Analytics
function createGAAdapter() {
  return {
    trackEvent(name, properties = {}) {
      ga.send(
        'event',
        properties.category || 'general',
        name,
        properties.label
      );
    },
    trackPageView(path, title) {
      ga.pageview(path);
    },
  };
}

// Adapter for Mixpanel
function createMixpanelAdapter() {
  return {
    trackEvent(name, properties = {}) {
      mixpanel.track(name, properties);
    },
    trackPageView(path, title) {
      mixpanel.page(title || path, { path });
    },
  };
}

// Factory to get the right adapter
export function createAnalyticsAdapter(provider = 'ga') {
  switch (provider) {
    case 'ga':
    case 'google':
      return createGAAdapter();
    case 'mixpanel':
      return createMixpanelAdapter();
    default:
      throw new Error(`Unknown provider: ${provider}`);
  }
}

// Default export with unified interface
const defaultAdapter = createGAAdapter();

export const analytics = {
  trackEvent: (name, props) => defaultAdapter.trackEvent(name, props),
  trackPageView: (path, title) => defaultAdapter.trackPageView(path, title),
};

// ═══════════════════════════════════════════════════════════════
// Usage - Same code works with any analytics provider
// ═══════════════════════════════════════════════════════════════

import { analytics } from './analyticsAdapter.js';

// Same interface regardless of underlying provider
analytics.trackEvent('button_click', { category: 'ui', label: 'submit' });
analytics.trackPageView('/dashboard', 'Dashboard');

5️⃣ Dependency Injection

Providing Dependencies from Outside

// ═══════════════════════════════════════════════════════════════
// Without DI - Hard to test, tightly coupled
// ═══════════════════════════════════════════════════════════════

// userService.js (BAD - imports dependencies directly)
import { db } from './database.js';
import { logger } from './logger.js';
import { mailer } from './mailer.js';

export async function createUser(userData) {
  logger.info('Creating user...');
  const user = await db.users.create(userData);
  await mailer.send(user.email, 'Welcome!');
  return user;
}

// ═══════════════════════════════════════════════════════════════
// With DI - Dependencies passed in
// ═══════════════════════════════════════════════════════════════

// userService.js (GOOD - receives dependencies)
export function createUserService({ db, logger, mailer }) {
  return {
    async createUser(userData) {
      logger.info('Creating user...');
      const user = await db.users.create(userData);
      await mailer.send(user.email, 'Welcome!');
      return user;
    },

    async findUser(id) {
      return db.users.findById(id);
    },

    async deleteUser(id) {
      logger.info(`Deleting user ${id}...`);
      return db.users.delete(id);
    },
  };
}

// ═══════════════════════════════════════════════════════════════
// Composition root - Wire everything together
// ═══════════════════════════════════════════════════════════════

// container.js
import { createDatabase } from './database.js';
import { createLogger } from './logger.js';
import { createMailer } from './mailer.js';
import { createUserService } from './userService.js';
import { createOrderService } from './orderService.js';

// Create instances
const logger = createLogger({ level: 'info' });
const db = createDatabase({ connectionString: process.env.DB_URL });
const mailer = createMailer({ apiKey: process.env.MAIL_API_KEY });

// Inject dependencies
export const userService = createUserService({ db, logger, mailer });
export const orderService = createOrderService({ db, logger, userService });

// ═══════════════════════════════════════════════════════════════
// Testing with mock dependencies
// ═══════════════════════════════════════════════════════════════

// userService.test.js
import { createUserService } from './userService.js';

describe('UserService', () => {
  it('should create a user', async () => {
    // Create mock dependencies
    const mockDb = {
      users: {
        create: jest.fn().mockResolvedValue({ id: 1, email: 'test@test.com' }),
      },
    };
    const mockLogger = { info: jest.fn() };
    const mockMailer = { send: jest.fn().mockResolvedValue(true) };

    // Inject mocks
    const userService = createUserService({
      db: mockDb,
      logger: mockLogger,
      mailer: mockMailer,
    });

    // Test
    const user = await userService.createUser({ email: 'test@test.com' });

    expect(mockDb.users.create).toHaveBeenCalled();
    expect(mockMailer.send).toHaveBeenCalledWith('test@test.com', 'Welcome!');
    expect(user.id).toBe(1);
  });
});
┌─────────────────────────────────────────────────────────────────────┐
│                    DEPENDENCY INJECTION                             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   WITHOUT DI:                       WITH DI:                        │
│                                                                     │
│   Module imports its               Dependencies passed in           │
│   own dependencies                  from outside                    │
│                                                                     │
│   ┌──────────────┐                 ┌──────────────┐                │
│   │   Service    │                 │   Service    │                │
│   │    │         │                 │      ▲       │                │
│   │    ├── DB ◀──┤ Hard-coded      │      │       │ Injected       │
│   │    ├── Log ◀─┤                 │ { db, log }  │                │
│   │    └── Mail◀─┤                 │              │                │
│   └──────────────┘                 └──────────────┘                │
│                                                                     │
│   Problems:                         Benefits:                       │
│   • Hard to test                    • Easy to test (mock deps)     │
│   • Tightly coupled                 • Loosely coupled              │
│   • Hard to change deps             • Swap implementations         │
│                                                                     │
│   COMPOSITION ROOT:                                                 │
│   ┌────────────────────────────────────────────────────────────┐   │
│   │  // container.js - Wire everything once                    │   │
│   │  const db = createDB(config);                               │   │
│   │  const logger = createLogger();                             │   │
│   │  const userService = createUserService({ db, logger });     │   │
│   │  export { userService };                                    │   │
│   └────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

6️⃣ Barrel Pattern (Re-exports)

Organizing Public API

// ═══════════════════════════════════════════════════════════════
// Project structure with barrels
// ═══════════════════════════════════════════════════════════════

// src/
// ├── components/
// │   ├── Button.js
// │   ├── Input.js
// │   ├── Modal.js
// │   └── index.js       ← Barrel
// ├── hooks/
// │   ├── useAuth.js
// │   ├── useFetch.js
// │   └── index.js       ← Barrel
// ├── utils/
// │   ├── math.js
// │   ├── string.js
// │   └── index.js       ← Barrel
// └── index.js           ← Main barrel

// ═══════════════════════════════════════════════════════════════
// components/index.js - Component barrel
// ═══════════════════════════════════════════════════════════════

export { default as Button } from './Button.js';
export { default as Input } from './Input.js';
export { default as Modal } from './Modal.js';

// Re-export types (if using TypeScript)
export type { ButtonProps } from './Button.js';
export type { InputProps } from './Input.js';

// ═══════════════════════════════════════════════════════════════
// hooks/index.js - Hooks barrel
// ═══════════════════════════════════════════════════════════════

export { useAuth } from './useAuth.js';
export { useFetch } from './useFetch.js';
export { useLocalStorage } from './useLocalStorage.js';

// ═══════════════════════════════════════════════════════════════
// utils/index.js - Utils barrel with selective exports
// ═══════════════════════════════════════════════════════════════

// Only export public utilities
export { add, subtract, multiply } from './math.js';
export { capitalize, truncate } from './string.js';

// Don't export internal helpers
// import { internalHelper } from './internal.js';  // Keep private

// ═══════════════════════════════════════════════════════════════
// src/index.js - Main entry point
// ═══════════════════════════════════════════════════════════════

// Re-export everything from sub-barrels
export * from './components/index.js';
export * from './hooks/index.js';
export * from './utils/index.js';

// ═══════════════════════════════════════════════════════════════
// Usage - Clean imports
// ═══════════════════════════════════════════════════════════════

// Instead of:
import { Button } from './components/Button.js';
import { Input } from './components/Input.js';
import { useAuth } from './hooks/useAuth.js';

// Use:
import { Button, Input, useAuth } from './src';
// or
import { Button, Input } from './src/components';
import { useAuth, useFetch } from './src/hooks';
┌─────────────────────────────────────────────────────────────────────┐
│                      BARREL PATTERN                                 │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   STRUCTURE:                                                        │
│                                                                     │
│   src/                                                              │
│   ├── components/                                                   │
│   │   ├── Button.js    ──┐                                         │
│   │   ├── Input.js      ├──▶ index.js (barrel)                     │
│   │   └── Modal.js     ──┘       │                                 │
│   │                              ▼                                  │
│   └── index.js   ◀───────────────┘  (main barrel)                  │
│                                                                     │
│   BENEFITS:                                                         │
│   • Clean import paths                                              │
│   • Control public API                                              │
│   • Hide implementation details                                     │
│   • Easy refactoring                                                │
│                                                                     │
│   ⚠️  CAUTIONS:                                                     │
│   • Can hurt tree-shaking if importing from main barrel             │
│   • Deep barrels can slow build times                               │
│   • Circular dependency risk                                        │
│                                                                     │
│   BEST PRACTICES:                                                   │
│   • Use specific barrels: import from './components'               │
│   • Avoid re-exporting everything from main barrel                 │
│   • Keep barrels shallow (one level)                               │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

7️⃣ Module Best Practices

┌─────────────────────────────────────────────────────────────────────┐
│                    MODULE BEST PRACTICES                            │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   1. SINGLE RESPONSIBILITY                                          │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │  ❌ BAD: god-module.js (does everything)                    │  │
│   │  ✅ GOOD: auth.js, users.js, orders.js (focused modules)    │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│   2. EXPLICIT EXPORTS                                               │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │  ❌ BAD: export default { lots, of, things }                │  │
│   │  ✅ GOOD: export { specific, named, exports }               │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│   3. AVOID SIDE EFFECTS                                             │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │  ❌ BAD: console.log('loaded'); at top level               │  │
│   │  ❌ BAD: fetch('/api/init'); at top level                  │  │
│   │  ✅ GOOD: export function init() { fetch(...) }            │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│   4. CONSISTENT STRUCTURE                                           │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │  // Every module follows same pattern:                      │  │
│   │  // 1. Imports                                              │  │
│   │  // 2. Constants                                            │  │
│   │  // 3. Private helpers                                      │  │
│   │  // 4. Public exports                                       │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│   5. AVOID CIRCULAR DEPENDENCIES                                    │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │  ❌ A imports B, B imports A                                 │  │
│   │  ✅ Extract shared code to C, both import C                 │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│   6. USE DEPENDENCY INJECTION                                       │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │  ❌ import { db } from './db'  // Hard dependency          │  │
│   │  ✅ export function create({ db }) // Injected             │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│   7. DOCUMENT PUBLIC API                                            │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │  /**                                                        │  │
│   │   * Creates a new user                                      │  │
│   │   * @param {Object} data - User data                        │  │
│   │   * @returns {Promise<User>}                                │  │
│   │   */                                                        │  │
│   │  export async function createUser(data) { ... }             │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

📚 Summary

PatternUse CaseKey Benefit
SingletonShared state, configSingle instance across app
FactoryObject creationEncapsulate creation logic
FacadeComplex subsystemsSimplified API
AdapterDifferent interfacesInterface compatibility
DITesting, flexibilityLoose coupling
BarrelModule organizationClean imports

📚 Module 14 Complete!

You've learned:

  • ✅ Module system evolution (IIFE → CommonJS → AMD → ESM)
  • ✅ All ES Module syntax patterns
  • ✅ CommonJS and AMD for legacy code
  • ✅ Bundlers (Webpack, Vite, Rollup, esbuild)
  • ✅ Module architectural patterns

Next: Continue to Module 15: Asynchronous JavaScript to learn about Promises, async/await, and handling asynchronous operations.

Skill Check

Test this lesson

Answer 4 quick questions to lock in the lesson and feed your adaptive practice queue.

--
Score
0/4
Answered
Not attempted
Status
1

Which module does this lesson belong to?

2

Which section is covered in this lesson content?

3

Which term is most central to this lesson?

4

What is the best way to use this lesson for real learning?

Your answers save locally first, then sync when account storage is available.
Practice queue