Module 19: Modules (Import/Export)
Modules allow you to split code into reusable, maintainable files. ES6 modules (ESM) are now the standard in JavaScript, replacing older systems like CommonJS.
1. Understanding Modules
1.1 Why Modules?
Benefits:
- Code organization and reusability
- Namespace isolation (avoid global scope pollution)
- Dependency management
- Better maintainability
- Lazy loading and code splitting
// Without modules: Everything in global scope
var utils = {
add: function(a, b) { return a + b; }
};
var config = { apiUrl: '...' };
// Risk of naming conflicts!
// With modules: Clean separation
// utils.js
export const add = (a, b) => a + b;
// config.js
export const apiUrl = '...';
2. ES6 Modules (ESM)
2.1 Named Exports
// math.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export class Calculator {
multiply(a, b) {
return a * b;
}
}
// Or export all at once
const PI = 3.14159;
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
export { PI, add, subtract };
2.2 Named Imports
// app.js
import { PI, add, subtract } from './math.js';
console.log(PI); // 3.14159
console.log(add(5, 3)); // 8
console.log(subtract(5, 3)); // 2
// Import all as namespace
import * as math from './math.js';
console.log(math.PI);
console.log(math.add(5, 3));
2.3 Default Exports
// user.js - Default export (one per file)
export default class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}`);
}
}
// Or
class User {
// ...
}
export default User;
// Or function
export default function greet(name) {
console.log(`Hello, ${name}`);
}
// Or value
export default 42;
2.4 Default Imports
// app.js
import User from './user.js';
const user = new User('John');
user.greet(); // "Hello, John"
// Can use any name for default import
import MyUser from './user.js'; // Same as above
import WhateverName from './user.js'; // Still works
2.5 Mixing Named and Default Exports
// api.js
export default class API {
constructor(baseURL) {
this.baseURL = baseURL;
}
}
export const API_VERSION = '1.0';
export const API_TIMEOUT = 5000;
// Import
import API, { API_VERSION, API_TIMEOUT } from './api.js';
const api = new API('https://api.example.com');
console.log(API_VERSION);
3. Import/Export Variations
3.1 Renaming Exports
// utils.js
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
export {
add as sum,
subtract as difference
};
// app.js
import { sum, difference } from './utils.js';
3.2 Renaming Imports
// app.js
import { add as sum, subtract as diff } from './math.js';
console.log(sum(5, 3)); // 8
console.log(diff(5, 3)); // 2
// Useful for avoiding naming conflicts
import { User as AdminUser } from './admin.js';
import { User as RegularUser } from './user.js';
3.3 Re-exporting
// math/index.js - Barrel export
export { add, subtract } from './basic.js';
export { multiply, divide } from './advanced.js';
export { PI, E } from './constants.js';
// Or re-export everything
export * from './basic.js';
export * from './advanced.js';
// Re-export with rename
export { add as sum } from './basic.js';
// Re-export default
export { default } from './calculator.js';
export { default as Calculator } from './calculator.js';
// Usage
import { add, multiply, PI } from './math/index.js';
// Or simply
import { add, multiply, PI } from './math';
4. Dynamic Imports
4.1 import()
// Static import (loaded at parse time)
import { add } from './math.js';
// Dynamic import (loaded at runtime)
button.addEventListener('click', async () => {
const { add } = await import('./math.js');
console.log(add(5, 3));
});
// With .then()
import('./math.js')
.then(module => {
console.log(module.add(5, 3));
})
.catch(error => {
console.error('Failed to load module:', error);
});
4.2 Conditional Loading
// Load different modules based on condition
async function loadUtils() {
if (process.env.NODE_ENV === 'development') {
const module = await import('./utils.dev.js');
return module.default;
} else {
const module = await import('./utils.prod.js');
return module.default;
}
}
// Feature-based loading
async function loadFeature(featureName) {
try {
const module = await import(`./features/${featureName}.js`);
return module.default;
} catch (error) {
console.error(`Failed to load feature: ${featureName}`);
return null;
}
}
4.3 Lazy Loading Components
// React example
const LazyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}
// Vanilla JavaScript
async function loadComponent(name) {
const spinner = showSpinner();
try {
const module = await import(`./components/${name}.js`);
hideSpinner(spinner);
return new module.default();
} catch (error) {
hideSpinner(spinner);
console.error('Failed to load component:', error);
}
}
5. Module Patterns
5.1 Barrel Exports (Index Files)
// components/index.js
export { Button } from './Button.js';
export { Input } from './Input.js';
export { Select } from './Select.js';
export { Checkbox } from './Checkbox.js';
// Usage
import { Button, Input, Select } from './components';
5.2 Singleton Pattern
// config.js
class Config {
constructor() {
this.settings = {
apiUrl: 'https://api.example.com',
timeout: 5000
};
}
get(key) {
return this.settings[key];
}
set(key, value) {
this.settings[key] = value;
}
}
// Export singleton instance
export default new Config();
// Usage
import config from './config.js';
console.log(config.get('apiUrl'));
5.3 Factory Pattern
// api-factory.js
class APIClient {
constructor(config) {
this.baseURL = config.baseURL;
this.timeout = config.timeout;
}
async get(endpoint) {
// ...
}
}
export function createAPIClient(config) {
return new APIClient(config);
}
// Usage
import { createAPIClient } from './api-factory.js';
const api = createAPIClient({
baseURL: 'https://api.example.com',
timeout: 5000
});
6. Module Systems Comparison
6.1 ES6 Modules (ESM)
// Modern standard (browser and Node.js)
// Static imports
import { add } from './math.js';
export const PI = 3.14;
// Dynamic imports
const module = await import('./module.js');
6.2 CommonJS (Node.js Legacy)
// Node.js traditional system
// Synchronous require
const fs = require('fs');
const { add } = require('./math');
// Exports
module.exports = {
add,
subtract
};
// Or
exports.add = add;
exports.subtract = subtract;
6.3 AMD (RequireJS - Legacy)
// Asynchronous Module Definition (browser legacy)
define(['jquery', 'underscore'], function($, _) {
return {
doSomething: function() {
// ...
}
};
});
6.4 UMD (Universal Module Definition)
// Works with CommonJS, AMD, and global
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['exports'], factory); // AMD
} else if (typeof exports === 'object') {
factory(exports); // CommonJS
} else {
factory((root.myModule = {})); // Browser global
}
}(this, function (exports) {
exports.add = function(a, b) {
return a + b;
};
}));
Use ES6 Modules
ES6 modules are the standard. Use them for new projects. CommonJS is only needed for legacy Node.js code.
7. Best Practices
7.1 File Organization
src/
components/
Button.js
Input.js
index.js # Barrel export
utils/
string-utils.js
array-utils.js
index.js
services/
api.js
auth.js
config/
config.js
app.js
7.2 Naming Conventions
// PascalCase for classes/components
export class UserService { }
export class Button { }
// camelCase for functions/utilities
export function formatDate() { }
export function validateEmail() { }
// UPPER_SNAKE_CASE for constants
export const API_URL = '...';
export const MAX_RETRIES = 3;
7.3 Circular Dependencies
// ❌ Bad: Circular dependency
// a.js
import { b } from './b.js';
export const a = b + 1;
// b.js
import { a } from './a.js';
export const b = a + 1;
// ✅ Good: Move shared code to third module
// shared.js
export const baseValue = 10;
// a.js
import { baseValue } from './shared.js';
export const a = baseValue + 1;
// b.js
import { baseValue } from './shared.js';
export const b = baseValue + 2;
7.4 Side Effects
// ❌ Bad: Side effects in module
// config.js
console.log('Config loaded'); // Runs on import!
localStorage.setItem('init', 'true'); // Side effect!
export const config = { };
// ✅ Good: Explicit initialization
// config.js
export const config = { };
export function initConfig() {
console.log('Config initialized');
localStorage.setItem('init', 'true');
}
// app.js
import { config, initConfig } from './config.js';
initConfig(); // Explicit call
8. Practical Examples
8.1 API Module
// api/client.js
class APIClient {
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
}
get(endpoint) {
return this.request(endpoint);
}
post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
}
}
export default APIClient;
// api/users.js
import APIClient from './client.js';
const client = new APIClient('https://api.example.com');
export async function getUsers() {
return await client.get('/users');
}
export async function getUser(id) {
return await client.get(`/users/${id}`);
}
export async function createUser(userData) {
return await client.post('/users', userData);
}
// api/index.js
export * from './users.js';
export * from './posts.js';
export { default as APIClient } from './client.js';
// app.js
import { getUsers, getUser, createUser } from './api';
const users = await getUsers();
8.2 Utilities 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;
}
// utils/array.js
export function unique(arr) {
return [...new Set(arr)];
}
export function groupBy(arr, key) {
return arr.reduce((acc, item) => {
const group = item[key];
acc[group] = acc[group] || [];
acc[group].push(item);
return acc;
}, {});
}
// utils/index.js
export * as string from './string.js';
export * as array from './array.js';
export * as date from './date.js';
// app.js
import { string, array } from './utils';
console.log(string.capitalize('hello'));
console.log(array.unique([1, 2, 2, 3]));
8.3 Configuration Module
// config/development.js
export default {
apiUrl: 'http://localhost:3000',
debug: true,
logLevel: 'debug'
};
// config/production.js
export default {
apiUrl: 'https://api.example.com',
debug: false,
logLevel: 'error'
};
// config/index.js
const env = process.env.NODE_ENV || 'development';
let config;
if (env === 'production') {
config = await import('./production.js');
} else {
config = await import('./development.js');
}
export default config.default;
// app.js
import config from './config';
console.log('API URL:', config.apiUrl);
9. Module Loading in HTML
9.1 Script Type Module
<!DOCTYPE html>
<html>
<head>
<title>ES6 Modules</title>
</head>
<body>
<!-- Type="module" enables ES6 modules -->
<script type="module">
import { add } from './math.js';
console.log(add(5, 3));
</script>
<!-- Or external module file -->
<script type="module" src="app.js"></script>
<!-- Fallback for old browsers -->
<script nomodule src="app-legacy.js"></script>
</body>
</html>
9.2 Module Features
<script type="module">
// Modules are deferred by default
// Modules are in strict mode by default
// Modules have their own scope
import { something } from './module.js';
// Top-level await (in modules)
const data = await fetch('/api/data');
</script>
10. Node.js Modules
10.1 package.json Configuration
{
"name": "my-app",
"version": "1.0.0",
"type": "module",
"main": "index.js",
"exports": {
".": "./index.js",
"./utils": "./utils/index.js"
}
}
10.2 File Extensions
// ES6 modules (with "type": "module")
import { add } from './math.js'; // .js extension required
// Or use .mjs extension (always ESM)
import { add } from './math.mjs';
// CommonJS with .cjs extension
const { add } = require('./math.cjs');
Summary
In this module, you learned:
- ✅ What modules are and why they're important
- ✅ ES6 module syntax: import and export
- ✅ Named exports, default exports, and re-exports
- ✅ Dynamic imports for lazy loading
- ✅ Module patterns and best practices
- ✅ Comparison of module systems (ESM, CommonJS, AMD)
- ✅ File organization and project structure
- ✅ Using modules in browsers and Node.js
Next Steps
In Module 20, you'll learn about Classes and OOP for object-oriented programming in JavaScript.
Practice Exercises
- Refactor a large file into multiple modules
- Create a utilities library with barrel exports
- Build a modular API client
- Implement lazy loading for application features
- Create a plugin system using dynamic imports
- Build a configuration system with environment-specific modules
- Organize a project with proper module structure
- Convert CommonJS code to ES6 modules