15. Modules
Module Systems
CommonJS (Node.js)
// math.js
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
module.exports = { add, multiply };
// app.js
const math = require('./math');
console.log(math.add(5, 3)); // 8
AMD (Asynchronous Module Definition)
// math.js
define([], function() {
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
return { add, multiply };
});
// app.js
require(['math'], function(math) {
console.log(math.add(5, 3));
});
ES6 Modules
Exporting
// math.js
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
export const PI = 3.14159;
export default function subtract(a, b) {
return a - b;
}
// Named exports
export { add, multiply, PI };
Importing
// app.js
// Named imports
import { add, multiply, PI } from './math.js';
console.log(add(5, 3)); // 8
console.log(multiply(4, 2)); // 8
console.log(PI); // 3.14159
// Default import
import subtract from './math.js';
console.log(subtract(10, 3)); // 7
// Import all
import * as math from './math.js';
console.log(math.add(5, 3)); // 8
console.log(math.default(10, 3)); // 7 (default export)
// Dynamic import (ES11+)
import('./math.js').then(module => {
console.log(module.add(5, 3));
});
Module Features
Strict Mode
// Modules are automatically in strict mode
// This means:
// - Variables must be declared
// - Functions can't be called before declaration
// - 'this' is undefined in global scope
// - etc.
Top-Level Await (ES13+)
// module.js
const response = await fetch('/api/data');
export const data = await response.json();
// app.js
import { data } from './module.js';
console.log(data); // Data is already loaded
Import Attributes (ES2025) and JSON Modules
// JSON Modules are standardized; use import attributes with `with`
import config from './config.json' with { type: 'json' };
console.log(config.name);
// Dynamic import with attributes
const mod = await import('./data.json', { with: { type: 'json' } });
Module Patterns
Revealing Module Pattern
// calculator.js
const calculator = (function() {
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) {
throw new Error("Division by zero");
}
return a / b;
}
// Reveal only the public API
return {
add,
subtract,
multiply,
divide
};
})();
export default calculator;
Factory Pattern
// logger.js
export function createLogger(prefix = '') {
return {
info(message) {
console.log(`${prefix}[INFO] ${message}`);
},
warn(message) {
console.warn(`${prefix}[WARN] ${message}`);
},
error(message) {
console.error(`${prefix}[ERROR] ${message}`);
}
};
}
// app.js
import { createLogger } from './logger.js';
const logger = createLogger('[APP]');
logger.info('Application started');
Circular Dependencies
Avoiding Circular Dependencies
// Bad: Circular dependency
// moduleA.js
import { funcB } from './moduleB.js';
export function funcA() {
return funcB() + ' from A';
}
// moduleB.js
import { funcA } from './moduleA.js';
export function funcB() {
return funcA() + ' from B';
}
// Good: Restructure to avoid circular dependency
// moduleA.js
export function funcA() {
return 'Hello from A';
}
// moduleB.js
import { funcA } from './moduleA.js';
export function funcB() {
return funcA() + ' and B';
}
Module Bundlers
Webpack Configuration
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
mode: 'development'
};
Rollup Configuration
// rollup.config.js
export default {
input: 'src/main.js',
output: {
file: 'dist/bundle.js',
format: 'iife',
name: 'MyApp'
}
};
Tree Shaking
// utils.js
export function usedFunction() {
return "I'm used";
}
export function unusedFunction() {
return "I'm not used";
}
// app.js
import { usedFunction } from './utils.js';
// unusedFunction is not imported, so it can be removed by tree shaking
console.log(usedFunction());
Dynamic Imports
Code Splitting
// app.js
const button = document.getElementById('load-feature');
button.addEventListener('click', async () => {
try {
// Dynamically import the feature module
const { initFeature } = await import('./feature.js');
initFeature();
} catch (error) {
console.error('Failed to load feature:', error);
}
});
Conditional Loading
// app.js
async function loadModuleBasedOnCondition(condition) {
if (condition) {
const { moduleA } = await import('./moduleA.js');
return moduleA;
} else {
const { moduleB } = await import('./moduleB.js');
return moduleB;
}
}
Module Resolution
Relative Imports
// From /src/components/Button.js
import utils from '../utils/helpers.js'; // ../utils/helpers.js
import styles from './Button.css'; // ./Button.css
Absolute Imports
// With path mapping in bundler config
import utils from 'utils/helpers'; // resolves to /src/utils/helpers.js
import Button from 'components/Button'; // resolves to /src/components/Button.js
Next Steps
Modules help organize and maintain large codebases. Next, let's explore advanced topics in ECMAScript.