Learning Objectives
- Understand ES6 module syntax and benefits
- Use named exports and imports
- Apply default exports and imports
- Master dynamic imports for code splitting
- Organize code with module patterns
Why Modules?
Before ES6 modules, JavaScript had no native module system. Developers used global variables, IIFEs, CommonJS, or AMD. ES6 modules provide a clean, standardized solution.
Named Exports and Imports
Exporting
// utils.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export class Calculator {
multiply(a, b) {
return a * b;
}
}
Importing
// app.js
import { PI, add, Calculator } from './utils.js';
console.log(PI); // 3.14159
console.log(add(2, 3)); // 5
const calc = new Calculator();
console.log(calc.multiply(4, 5)); // 20
Import All
import * as utils from './utils.js';
console.log(utils.PI);
console.log(utils.add(2, 3));
const calc = new utils.Calculator();
Rename Imports
import { add as sum, PI as pi } from './utils.js';
console.log(sum(2, 3));
console.log(pi);
Default Exports
Each module can have one default export:
// user.js
export default class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, I'm ${this.name}`;
}
}
// Or
class User {
constructor(name) {
this.name = name;
}
}
export default User;
Importing Default
import User from './user.js';
const user = new User('John');
console.log(user.greet()); // "Hello, I'm John"
Note: You can name the default import anything:
import MyUser from './user.js'; // Works!
import AnyName from './user.js'; // Also works!
Mixing Default and Named Exports
// api.js
export default function fetchData() {
return fetch('/api/data');
}
export const API_URL = 'https://api.example.com';
export const timeout = 5000;
// Import both
import fetchData, { API_URL, timeout } from './api.js';
console.log(API_URL);
const data = await fetchData();
Re-exporting
Aggregate exports from multiple modules:
// shapes/index.js
export { Circle } from './circle.js';
export { Square } from './square.js';
export { Triangle } from './triangle.js';
// Or re-export everything
export * from './circle.js';
export * from './square.js';
export * from './triangle.js';
// app.js
import { Circle, Square, Triangle } from './shapes/index.js';
const circle = new Circle(5);
const square = new Square(10);
Dynamic Imports
Load modules on demand for code splitting:
// Static import - loaded immediately
import { heavyFunction } from './heavy.js';
// Dynamic import - loaded when needed
button.addEventListener('click', async () => {
const module = await import('./heavy.js');
module.heavyFunction();
});
Conditional Loading
if (userIsAdmin) {
const adminModule = await import('./admin.js');
adminModule.init();
}
// Feature detection
if ('IntersectionObserver' in window) {
const { lazyLoad } = await import('./lazy-load.js');
lazyLoad();
}
Lazy Loading Routes
const routes = {
'/home': () => import('./pages/home.js'),
'/about': () => import('./pages/about.js'),
'/contact': () => import('./pages/contact.js')
};
async function loadRoute(path) {
const module = await routes[path]();
module.render();
}
// Usage
await loadRoute('/home');
Real-World Examples
Utility Functions Module
// utils/math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
export const divide = (a, b) => b !== 0 ? a / b : 0;
// utils/string.js
export const capitalize = (str) =>
str.charAt(0).toUpperCase() + str.slice(1);
export const slugify = (str) =>
str.toLowerCase().replace(/\s+/g, '-');
export const truncate = (str, length) =>
str.length > length ? str.slice(0, length) + '...' : str;
API Service Module
// services/api.js
const BASE_URL = 'https://api.example.com';
async function request(endpoint, options = {}) {
const response = await fetch(`${BASE_URL}${endpoint}`, {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
export async function fetchUsers() {
return request('/users');
}
export async function fetchUser(id) {
return request(`/users/${id}`);
}
export async function createUser(userData) {
return request('/users', {
method: 'POST',
body: JSON.stringify(userData)
});
}
export async function updateUser(id, userData) {
return request(`/users/${id}`, {
method: 'PUT',
body: JSON.stringify(userData)
});
}
export async function deleteUser(id) {
return request(`/users/${id}`, {
method: 'DELETE'
});
}
Configuration Module
// config/index.js
const isDevelopment = process.env.NODE_ENV === 'development';
export const config = {
apiUrl: process.env.API_URL || 'http://localhost:3000',
timeout: 5000,
retries: 3,
debug: isDevelopment
};
export const endpoints = {
users: '/api/users',
posts: '/api/posts',
comments: '/api/comments'
};
export default config;
Singleton Pattern with Modules
// database.js
class Database {
constructor() {
this.connection = null;
}
connect(connectionString) {
if (!this.connection) {
this.connection = createConnection(connectionString);
}
return this.connection;
}
query(sql) {
if (!this.connection) {
throw new Error('Not connected to database');
}
return this.connection.execute(sql);
}
}
// Export a single instance
export default new Database();
// app.js
import db from './database.js';
await db.connect('mongodb://localhost:27017');
const users = await db.query('SELECT * FROM users');
Module Features
Strict Mode by Default
// Modules are automatically in strict mode
// No need for 'use strict';
// This will error in modules
undeclaredVariable = 5; // ReferenceError
Top-Level this is undefined
// In modules
console.log(this); // undefined
// In scripts
console.log(this); // window (in browsers)
Deferred Execution
// Modules are deferred by default
// Similar to