Modules
ES6 Modules (ESM)
Basic Export/Import
// math.js - Named exports
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// Alternative named export syntax
const multiply = (a, b) => a * b;
const divide = (a, b) => a / b;
export { multiply, divide };
// Default export
export default function calculate(operation, a, b) {
switch (operation) {
case 'add':
return add(a, b);
case 'subtract':
return subtract(a, b);
case 'multiply':
return multiply(a, b);
case 'divide':
return divide(a, b);
default:
throw new Error('Unknown operation');
}
}
// main.js - Import examples
import calculate, { PI, add, multiply } from './math.js';
import * as math from './math.js';
import { add as sum, subtract as diff } from './math.js'; // Rename
// Usage
console.log(PI); // 3.14159
console.log(add(2, 3)); // 5
console.log(calculate('add', 2, 3)); // 5
console.log(math.multiply(2, 3)); // 6
console.log(sum(5, 2)); // 7 (renamed import)
Advanced Export Patterns
// config.js - Mixed exports
const API_URL = 'https://api.example.com';
const TIMEOUT = 5000;
class ApiClient {
constructor(baseURL = API_URL) {
this.baseURL = baseURL;
}
async get(endpoint) {
const response = await fetch(`${this.baseURL}${endpoint}`);
return response.json();
}
}
// Export constants
export { API_URL, TIMEOUT };
// Export class as default
export default ApiClient;
// Export instance
export const apiClient = new ApiClient();
Re-exports
// utils/index.js - Barrel exports
export { default as ApiClient } from './api-client.js';
export { Logger } from './logger.js';
export { formatDate, formatCurrency } from './formatters.js';
export * from './validators.js';
// Re-export with rename
export { debounce as delay } from './timing.js';
// Re-export default as named
export { default as HttpClient } from './http-client.js';
// main.js - Import from barrel
import { ApiClient, Logger, formatDate, isEmail } from './utils/index.js';
Dynamic Imports
// Dynamic import returns a Promise
async function loadModule() {
const module = await import('./math.js');
console.log(module.add(2, 3)); // 5
console.log(module.default('add', 2, 3)); // 5
}
// Conditional loading
async function loadFeature(featureName) {
let module;
switch (featureName) {
case 'charts':
module = await import('./features/charts.js');
break;
case 'analytics':
module = await import('./features/analytics.js');
break;
default:
throw new Error('Unknown feature');
}
return module.default;
}
// Lazy loading with error handling
async function lazyLoadComponent(componentName) {
try {
const { default: Component } = await import(
`./components/${componentName}.js`
);
return Component;
} catch (error) {
console.error(`Failed to load component ${componentName}:`, error);
// Return fallback component
const { default: FallbackComponent } = await import(
'./components/Fallback.js'
);
return FallbackComponent;
}
}
Module Loading Patterns
// Preload modules
const modulePromises = {
charts: import('./features/charts.js'),
analytics: import('./features/analytics.js'),
};
// Use preloaded modules
async function useCharts() {
const chartsModule = await modulePromises.charts;
return chartsModule.createChart();
}
// Parallel loading
async function loadMultipleModules() {
const [mathModule, utilsModule, apiModule] = await Promise.all([
import('./math.js'),
import('./utils.js'),
import('./api.js'),
]);
return {
math: mathModule,
utils: utilsModule,
api: apiModule,
};
}
CommonJS (Node.js)
Basic Export/Require
// math.js - CommonJS exports
const PI = 3.14159;
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
// Export object
module.exports = {
PI,
add,
subtract,
};
// Or export individual items
exports.PI = PI;
exports.add = add;
exports.subtract = subtract;
// Export function as default
module.exports = function calculate(operation, a, b) {
// implementation
};
// Export with additional properties
module.exports = calculate;
module.exports.PI = PI;
module.exports.add = add;
// main.js - CommonJS require
const math = require('./math');
const { add, subtract, PI } = require('./math');
// Usage
console.log(math.add(2, 3)); // 5
console.log(PI); // 3.14159
CommonJS Patterns
// logger.js - Module with internal state
const logLevels = {
ERROR: 0,
WARN: 1,
INFO: 2,
DEBUG: 3,
};
let currentLevel = logLevels.INFO;
function setLevel(level) {
currentLevel = level;
}
function log(level, message) {
if (level <= currentLevel) {
console.log(`[${Object.keys(logLevels)[level]}] ${message}`);
}
}
module.exports = {
setLevel,
error: msg => log(logLevels.ERROR, msg),
warn: msg => log(logLevels.WARN, msg),
info: msg => log(logLevels.INFO, msg),
debug: msg => log(logLevels.DEBUG, msg),
};
Conditional Requires
// Feature detection
let crypto;
try {
crypto = require('crypto');
} catch (error) {
// Fallback for browsers
crypto = require('./crypto-polyfill');
}
// Environment-specific modules
const config =
process.env.NODE_ENV === 'production'
? require('./config/production')
: require('./config/development');
// Dynamic requires
function loadPlugin(pluginName) {
try {
return require(`./plugins/${pluginName}`);
} catch (error) {
console.warn(`Plugin ${pluginName} not found`);
return null;
}
}
Module Patterns
Singleton Pattern
// database.js - Singleton module
class Database {
constructor() {
if (Database.instance) {
return Database.instance;
}
this.connection = null;
this.connected = false;
Database.instance = this;
}
async connect() {
if (!this.connected) {
// Simulate connection
this.connection = await createConnection();
this.connected = true;
}
return this.connection;
}
}
// Export singleton instance
export default new Database();
Factory Pattern
// logger-factory.js
class Logger {
constructor(name, level = 'info') {
this.name = name;
this.level = level;
}
log(message) {
console.log(`[${this.name}] ${message}`);
}
}
class LoggerFactory {
static loggers = new Map();
static getLogger(name, level) {
if (!this.loggers.has(name)) {
this.loggers.set(name, new Logger(name, level));
}
return this.loggers.get(name);
}
}
export default LoggerFactory;
Module with Initialization
// analytics.js
class Analytics {
constructor() {
this.initialized = false;
this.events = [];
}
async init(config) {
if (this.initialized) return;
// Initialize analytics service
await this.setupService(config);
this.initialized = true;
// Process queued events
this.events.forEach(event => this.track(event));
this.events = [];
}
track(event) {
if (!this.initialized) {
this.events.push(event);
return;
}
// Send event to analytics service
this.sendEvent(event);
}
async setupService(config) {
// Implementation
}
sendEvent(event) {
// Implementation
}
}
export default new Analytics();
Namespace Pattern
// app-namespace.js
const App = {
version: '1.0.0',
// Utils namespace
Utils: {
formatDate(date) {
return date.toISOString().split('T')[0];
},
generateId() {
return Math.random().toString(36).substr(2, 9);
},
},
// API namespace
API: {
baseURL: 'https://api.example.com',
async get(endpoint) {
const response = await fetch(`${this.baseURL}${endpoint}`);
return response.json();
},
async post(endpoint, data) {
const response = await fetch(`${this.baseURL}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
return response.json();
},
},
// Events namespace
Events: {
listeners: new Map(),
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
},
emit(event, data) {
const callbacks = this.listeners.get(event) || [];
callbacks.forEach(callback => callback(data));
},
},
};
export default App;
Module Best Practices
Circular Dependencies
// Avoid circular dependencies
// user.js
import { getPostsByUser } from './posts.js'; // ❌ Circular dependency
export class User {
constructor(id, name) {
this.id = id;
this.name = name;
}
async getPosts() {
return getPostsByUser(this.id);
}
}
// posts.js
import { User } from './user.js'; // ❌ Circular dependency
// Better approach - use dependency injection
// user.js
export class User {
constructor(id, name, postService) {
this.id = id;
this.name = name;
this.postService = postService;
}
async getPosts() {
return this.postService.getPostsByUser(this.id);
}
}
// posts.js
export class PostService {
async getPostsByUser(userId) {
// Implementation
}
}
Module Documentation
/**
* User management utilities
* @module UserUtils
* @version 1.2.0
* @author John Doe
* @since 1.0.0
*/
/**
* Creates a new user object
* @param {string} name - User's name
* @param {string} email - User's email
* @param {Object} options - Additional options
* @param {string} [options.role='user'] - User's role
* @returns {Object} User object
* @example
* const user = createUser('John', 'john@example.com', { role: 'admin' });
*/
export function createUser(name, email, options = {}) {
return {
id: generateId(),
name,
email,
role: options.role || 'user',
createdAt: new Date(),
};
}
/**
* Validates user data
* @param {Object} user - User object to validate
* @throws {ValidationError} When user data is invalid
* @returns {boolean} True if valid
*/
export function validateUser(user) {
if (!user.name || !user.email) {
throw new Error('Name and email are required');
}
return true;
}
Testing Modules
// math.js
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// math.test.js
import { add, multiply } from './math.js';
describe('Math utilities', () => {
test('add function', () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
});
test('multiply function', () => {
expect(multiply(2, 3)).toBe(6);
expect(multiply(0, 5)).toBe(0);
});
});