Docs
README
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
| Pattern | Use Case | Key Benefit |
|---|---|---|
| Singleton | Shared state, config | Single instance across app |
| Factory | Object creation | Encapsulate creation logic |
| Facade | Complex subsystems | Simplified API |
| Adapter | Different interfaces | Interface compatibility |
| DI | Testing, flexibility | Loose coupling |
| Barrel | Module organization | Clean 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.