Skip to main content

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​

  1. Refactor a large file into multiple modules
  2. Create a utilities library with barrel exports
  3. Build a modular API client
  4. Implement lazy loading for application features
  5. Create a plugin system using dynamic imports
  6. Build a configuration system with environment-specific modules
  7. Organize a project with proper module structure
  8. Convert CommonJS code to ES6 modules

Additional Resources​