What are Modules?
Modules allow you to organize code into reusable files with their own scope. Each module can export values and import from other modules.
📤 Export Syntax
Named Exports
// math.js - Export individual items
export const PI = 3.14159;
export const E = 2.71828;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
export class Calculator {
add(a, b) { return a + b; }
subtract(a, b) { return a - b; }
}
// Or export all at once
const PI = 3.14159;
const E = 2.71828;
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
export { PI, E, add, multiply };
// Export with rename
const privateFunction = () => {};
export { privateFunction as publicFunction };
Default Export
// user.js - One default export per module
export default class User {
constructor(name) {
this.name = name;
}
}
// Or
class User {
constructor(name) {
this.name = name;
}
}
export default User;
// Function as default
export default function greet(name) {
return `Hello, ${name}!`;
}
// Object as default
export default {
name: 'MyApp',
version: '1.0.0',
author: 'John Doe'
};
// Can mix default and named exports
export default function main() {}
export const helper = () => {};
export const VERSION = '1.0.0';
Re-exporting
// utils/index.js - Barrel exports
export { add, subtract } from './math.js';
export { capitalize, truncate } from './string.js';
export { default as User } from './user.js';
// Export all from module
export * from './constants.js';
// Export all with namespace
export * as math from './math.js';
// Usage: import { math } from './utils'
// math.add(1, 2)
// Re-export with rename
export { default as MainComponent } from './Component.js';
export { helper as utilityFunction } from './helpers.js';
📥 Import Syntax
Named Imports
// Import specific items
import { add, multiply } from './math.js';
console.log(add(2, 3)); // 5
console.log(multiply(2, 3)); // 6
// Import with rename
import { add as sum, multiply as product } from './math.js';
console.log(sum(2, 3)); // 5
console.log(product(2, 3)); // 6
// Import multiple items
import { PI, E, add, multiply, Calculator } from './math.js';
// Import all as namespace object
import * as math from './math.js';
console.log(math.PI); // 3.14159
console.log(math.add(2, 3)); // 5
let calc = new math.Calculator();
// Empty import (just run the module)
import './setup.js'; // Executes setup code
Default Imports
// Import default export (any name)
import User from './user.js';
import MyUser from './user.js'; // Same thing, different name
let user = new User('John');
// Import default and named
import Calculator, { PI, E } from './math.js';
let calc = new Calculator();
console.log(PI);
// Import default with namespace
import { default as User, helper } from './user.js';
// Common pattern
import React, { useState, useEffect } from 'react';
Dynamic Imports
// Import on demand (returns Promise)
button.addEventListener('click', async () => {
let module = await import('./heavy-module.js');
module.doSomething();
});
// With destructuring
button.addEventListener('click', async () => {
let { add, multiply } = await import('./math.js');
console.log(add(2, 3));
});
// Conditional import
if (condition) {
let module = await import('./feature.js');
module.initialize();
}
// Dynamic module name
async function loadLocale(language) {
let locale = await import(`./locales/${language}.js`);
return locale.default;
}
let messages = await loadLocale('en');
// Error handling
try {
let module = await import('./module.js');
module.run();
} catch (error) {
console.error('Failed to load module:', error);
}
🏗️ Module Patterns
Utility Module
// utils/string.js
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function truncate(str, length) {
return str.length > length ? str.slice(0, length) + '...' : str;
}
export function slugify(str) {
return str.toLowerCase().replace(/\s+/g, '-');
}
// Usage in another file
import { capitalize, truncate } from './utils/string.js';
let title = capitalize('hello world');
let short = truncate('This is a long text', 10);
Constants Module
// constants.js
export const API_URL = 'https://api.example.com';
export const API_KEY = 'your-api-key';
export const TIMEOUT = 5000;
export const STATUS = {
PENDING: 'pending',
SUCCESS: 'success',
ERROR: 'error'
};
export const COLORS = {
PRIMARY: '#007bff',
SECONDARY: '#6c757d',
SUCCESS: '#28a745',
ERROR: '#dc3545'
};
// Usage
import { API_URL, STATUS, COLORS } from './constants.js';
console.log(STATUS.PENDING);
console.log(COLORS.PRIMARY);
Class Module
// user.js
export default class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
getFullName() {
return this.name;
}
static fromJSON(json) {
return new User(json.name, json.email);
}
}
// Also export helper
export function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// Usage
import User, { validateEmail } from './user.js';
let user = new User('John', 'john@example.com');
if (validateEmail(user.email)) {
console.log('Valid email');
}
Configuration Module
// config.js
const config = {
development: {
apiUrl: 'http://localhost:3000',
debug: true
},
production: {
apiUrl: 'https://api.example.com',
debug: false
}
};
const env = process.env.NODE_ENV || 'development';
export default config[env];
// Or with named exports
export const API_URL = config[env].apiUrl;
export const DEBUG = config[env].debug;
// Usage
import config from './config.js';
// or
import { API_URL, DEBUG } from './config.js';
📁 Module Organization
Barrel Exports (index.js)
// utils/index.js - Central export point
export { add, subtract, multiply, divide } from './math.js';
export { capitalize, truncate, slugify } from './string.js';
export { formatDate, parseDate } from './date.js';
export { default as User } from './user.js';
export { default as Product } from './product.js';
// Usage - cleaner imports
import { add, capitalize, formatDate, User } from './utils';
// Instead of
import { add } from './utils/math.js';
import { capitalize } from './utils/string.js';
import { formatDate } from './utils/date.js';
import User from './utils/user.js';
Feature Modules
// features/auth/index.js
import { login, logout, register } from './auth-service.js';
import { getUser, updateUser } from './user-service.js';
import AuthStore from './auth-store.js';
export { login, logout, register, getUser, updateUser, AuthStore };
// Or namespace export
import * as authService from './auth-service.js';
import * as userService from './user-service.js';
export { authService, userService };
// Usage
import { login, logout } from './features/auth';
// or
import { authService } from './features/auth';
authService.login(credentials);
Lazy Loading Modules
// app.js
class App {
async loadDashboard() {
let { Dashboard } = await import('./components/Dashboard.js');
new Dashboard().render();
}
async loadSettings() {
let { Settings } = await import('./components/Settings.js');
new Settings().render();
}
}
// Route-based loading
async function handleRoute(route) {
switch (route) {
case '/dashboard':
let { Dashboard } = await import('./pages/Dashboard.js');
return new Dashboard();
case '/profile':
let { Profile } = await import('./pages/Profile.js');
return new Profile();
default:
let { NotFound } = await import('./pages/NotFound.js');
return new NotFound();
}
}
🌐 Browser Usage
HTML Script Tag
Import Maps
💡 Practical Examples
API Service Module
// services/api.js
const BASE_URL = 'https://api.example.com';
export async function get(endpoint) {
let response = await fetch(`${BASE_URL}${endpoint}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
export async function post(endpoint, data) {
let response = await fetch(`${BASE_URL}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
export async function put(endpoint, data) {
let response = await fetch(`${BASE_URL}${endpoint}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
export async function del(endpoint) {
let response = await fetch(`${BASE_URL}${endpoint}`, {
method: 'DELETE'
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
// Usage in another file
import * as api from './services/api.js';
let users = await api.get('/users');
let newUser = await api.post('/users', { name: 'John' });
State Management Module
// store.js
class Store {
constructor(initialState = {}) {
this.state = initialState;
this.listeners = [];
}
getState() {
return this.state;
}
setState(updates) {
this.state = { ...this.state, ...updates };
this.listeners.forEach(listener => listener(this.state));
}
subscribe(listener) {
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter(l => l !== listener);
};
}
}
export default new Store({
user: null,
theme: 'light',
notifications: []
});
// Usage
import store from './store.js';
// Subscribe to changes
let unsubscribe = store.subscribe(state => {
console.log('State changed:', state);
});
// Update state
store.setState({ theme: 'dark' });
// Get current state
console.log(store.getState());
Router Module
// router.js
class Router {
constructor() {
this.routes = new Map();
this.currentRoute = null;
window.addEventListener('popstate', () => this.handleRoute());
}
register(path, handler) {
this.routes.set(path, handler);
}
navigate(path) {
history.pushState(null, '', path);
this.handleRoute();
}
async handleRoute() {
let path = window.location.pathname;
let handler = this.routes.get(path);
if (handler) {
this.currentRoute = path;
await handler();
} else {
this.handle404();
}
}
handle404() {
console.log('404 Not Found');
}
}
export default new Router();
// app.js
import router from './router.js';
router.register('/', async () => {
let { Home } = await import('./pages/Home.js');
new Home().render();
});
router.register('/about', async () => {
let { About } = await import('./pages/About.js');
new About().render();
});
router.handleRoute();
Component System
// components/Component.js
export default class Component {
constructor(selector) {
this.el = document.querySelector(selector);
}
render() {
this.el.innerHTML = this.template();
this.attachEvents();
}
template() {
return '';
}
attachEvents() {}
}
// components/TodoList.js
import Component from './Component.js';
import { get, post, del } from '../services/api.js';
export default class TodoList extends Component {
constructor(selector) {
super(selector);
this.todos = [];
}
async initialize() {
this.todos = await get('/todos');
this.render();
}
template() {
return `
${this.todos.map(todo => `
-
${todo.text}
`).join('')}
`;
}
attachEvents() {
this.el.querySelector('#addBtn').addEventListener('click', () => {
this.addTodo();
});
this.el.querySelectorAll('.delete').forEach(btn => {
btn.addEventListener('click', e => {
let id = e.target.closest('li').dataset.id;
this.deleteTodo(id);
});
});
}
async addTodo() {
let input = this.el.querySelector('#todoInput');
let todo = await post('/todos', { text: input.value });
this.todos.push(todo);
this.render();
}
async deleteTodo(id) {
await del(`/todos/${id}`);
this.todos = this.todos.filter(t => t.id !== id);
this.render();
}
}
// app.js
import TodoList from './components/TodoList.js';
let todoList = new TodoList('#app');
todoList.initialize();
⚠️ Common Issues
// Must use file extension in browser
import { add } from './math'; // ❌ Error
import { add } from './math.js'; // ✅ Works
// Circular dependencies
// a.js
import { b } from './b.js';
export const a = 'A';
// b.js
import { a } from './a.js'; // May cause issues
export const b = 'B';
// Solution: restructure or use dynamic import
// Module scope
// Every module has its own scope
// math.js
let privateVar = 42; // Not accessible outside
export const publicVar = 100; // Accessible
// Modules are singletons
// store.js
let count = 0;
export function increment() { count++; }
export function getCount() { return count; }
// Both files share same instance
// a.js
import { increment } from './store.js';
increment();
// b.js
import { getCount } from './store.js';
console.log(getCount()); // 1
// CORS issues in browser
// Must serve files via HTTP server, not file://
// Use: python -m http.server or npm serve
🎯 Key Takeaways
- Named Exports: export { name } - import { name } from './file.js'
- Default Export: export default - import name from './file.js'
- Import All: import * as name - access as name.export
- Dynamic Import: await import('./file.js') - loads on demand
- Module Scope: Each module has its own scope, executes once
- File Extensions: Must include .js in browser imports
- Barrel Exports: index.js re-exports for cleaner imports
- type="module": Required in script tag for ES6 modules