Module System and CommonJS vs ES Modules
Understanding Node.js module system and differences between CommonJS and ES modules
Challenge
Section titled “Challenge”Your project has mixed CommonJS and ES module code. Some modules import fine, others throw errors. How do you organize and migrate between module systems?
Explanation
Section titled “Explanation”Node.js supports two module systems: CommonJS (default) and ES Modules (modern). Understanding how each works and how to interoperate them is essential for Node.js development.
CommonJS (Traditional)
Section titled “CommonJS (Traditional)”// math.js - Define exportsmodule.exports = { add: (a, b) => a + b, subtract: (a, b) => a - b};
// app.js - Require modulesconst math = require('./math');console.log(math.add(5, 3)); // 8
// Partial exportsmodule.exports.multiply = (a, b) => a * b;
// Or assign to exports objectexports.divide = (a, b) => a / b;
// ⚠️ Note: Don't mix exports and module.exports!module.exports = { /* ... */ };exports.something = null; // Won't work as expectedES Modules (Modern)
Section titled “ES Modules (Modern)”// math.mjs or with "type": "module" in package.json
// Named exportsexport const add = (a, b) => a + b;export const subtract = (a, b) => a - b;
// Default exportexport default { multiply: (a, b) => a * b, divide: (a, b) => a / b};
// app.mjs - Import modulesimport math, { add, subtract } from './math.mjs';
console.log(add(5, 3)); // 8console.log(math.multiply(5, 3)); // 15
// Import all as namespaceimport * as operations from './math.mjs';console.log(operations.add(5, 3)); // 8
// Conditional importsif (someCondition) { import('./expensive-module.mjs').then(mod => { // Use module });}Solution: Module Organization
Section titled “Solution: Module Organization”// package.json{ "name": "my-app", "version": "1.0.0", "type": "module", // Use ES modules "exports": { ".": "./src/index.js", "./utils": "./src/utils/index.js", "./cli": "./src/cli.js" }}
// src/index.js - Main entry pointexport { default as Calculator } from './calculator.js';export * from './utils/index.js';
// src/calculator.jsexport default class Calculator { add(a, b) { return a + b; }
subtract(a, b) { return a - b; }}
// src/utils/index.jsexport { formatNumber } from './format.js';export { validateInput } from './validation.js';
// src/utils/format.jsexport const formatNumber = (num, decimals = 2) => { return num.toFixed(decimals);};
// src/utils/validation.jsexport const validateInput = (input) => { return typeof input === 'number' && !isNaN(input);};
// Usageimport Calculator, { formatNumber, validateInput } from './index.js';Interoperability: CommonJS in ES Modules
Section titled “Interoperability: CommonJS in ES Modules”// ✅ Import CommonJS from ES Module (Node.js 12+)import math from './math.cjs'; // CommonJS file
// ⚠️ Named imports from CommonJS require namespaceimport * as math from './math.cjs';console.log(math.add(5, 3)); // 8
// ✅ Use createRequire for full compatibilityimport { createRequire } from 'module';const require = createRequire(import.meta.url);
const config = require('./config.json');const helper = require('./helper.cjs');Interoperability: ES Modules in CommonJS
Section titled “Interoperability: ES Modules in CommonJS”// ❌ import/export syntax not available in CommonJS// import Calculator from './calculator.mjs'; // Won't work
// ✅ Use dynamic import (available in CommonJS)async function loadModule() { const { Calculator } = await import('./calculator.mjs'); return new Calculator();}
loadModule().then(calc => { console.log(calc.add(5, 3)); // 8});
// Use in top-level(async () => { const lib = await import('./lib.mjs'); console.log(lib.doSomething());})();Module Caching
Section titled “Module Caching”// CommonJS caches modules in require.cacheconst math1 = require('./math');const math2 = require('./math');
console.log(math1 === math2); // true - same instance
// Clear cache if neededdelete require.cache[require.resolve('./math')];const math3 = require('./math'); // Fresh instance
// ES Modules also cache, but no direct access to cacheimport mod1 from './module.mjs';import mod2 from './module.mjs';// mod1 === mod2 - same instanceBest Practices for Module Organization
Section titled “Best Practices for Module Organization”// ✅ Group related functionality// auth/// index.js - Main auth export// jwt.js - JWT handling// password.js - Password hashing// database/// index.js// connection.js// queries.js
// src/auth/index.jsexport { default as JWTManager } from './jwt.js';export { default as PasswordManager } from './password.js';
// src/index.js - Single entry pointexport * from './auth/index.js';export * from './database/index.js';
// ✅ Use barrel exports for cleaner imports// Instead of: import { add } from './utils/math.js'// Use: import { add } from './utils'// (if utils has index.js exporting math.js)
// ✅ Avoid circular dependencies// auth/index.jsexport { default as AuthService } from './service.js';
// auth/service.jsimport UserRepository from '../database/user.js'; // Different module// Good: No circular dependency
// ❌ Bad: Circular dependency// user.js imports from auth/service.js// auth/service.js imports from user.jsPerformance Considerations
Section titled “Performance Considerations”// ✅ Lazy load heavy modulesclass DataProcessor { process() { // Import only when needed const heavyLib = require('./heavy-library'); return heavyLib.process(this.data); }}
// ✅ Use import.meta for module-specific infoconsole.log(import.meta.url); // file URL of current moduleconsole.log(import.meta.main); // true if main module
// ✅ Minimize module scope// Bad: Everything at module scopeapp.use('/api', routes);app.use(middleware);
// Good: Code executes only when importedexport function setupRoutes(app) { app.use('/api', routes);}
export function setupMiddleware(app) { app.use(middleware);}