Back to Blog
Tutorial2025-01-03

JavaScript Modules: The Complete Import/Export Guide

Master ES6 modules. Learn named exports, default exports, dynamic imports, and how to organize your code effectively.

Why Modules?

Before ES6, JavaScript had no built-in module system. Code organization was a mess of global variables and script tags. ES6 modules solved this by letting you:

- Split code into separate files - Explicitly declare dependencies - Avoid polluting the global namespace - Enable tree-shaking (removing unused code)

Named Exports

Export multiple things from a module by name.

Exporting

``javascript // math.js

// Export individually export const PI = 3.14159;

export function add(a, b) { return a + b; }

export function multiply(a, b) { return a * b; }

// Or export all at once const subtract = (a, b) => a - b; const divide = (a, b) => a / b;

export { subtract, divide }; `

Importing

`javascript // Import specific items import { PI, add, multiply } from './math.js';

console.log(PI); // 3.14159 console.log(add(2, 3)); // 5

// Import with alias import { multiply as mult } from './math.js'; console.log(mult(2, 3)); // 6

// Import everything as namespace import * as math from './math.js'; console.log(math.PI); // 3.14159 console.log(math.add(2, 3)); // 5 `

Default Exports

Each module can have ONE default export - the "main" thing.

Exporting

`javascript // User.js

// Default export (can be anonymous) export default class User { constructor(name) { this.name = name; }

greet() { return Hello, I'm ${this.name}; } }

// Or export separately class User { /* ... */ } export default User; `

Importing

`javascript // Default imports don't use braces // Name can be anything you want import User from './User.js'; import MyUser from './User.js'; // Same thing, different name

const alice = new User('Alice'); `

Mixing Named and Default

`javascript // api.js export const API_URL = 'https://api.example.com'; export const timeout = 5000;

export default function fetchData(endpoint) { return fetch(API_URL + endpoint); } `

`javascript // Importing both import fetchData, { API_URL, timeout } from './api.js';

// Or with alias import { default as fetch, API_URL } from './api.js'; `

Re-exporting

Combine exports from multiple modules into one.

`javascript // utils/index.js

// Re-export everything export * from './math.js'; export * from './string.js';

// Re-export specific items export { format, parse } from './date.js';

// Re-export with rename export { helper as utilHelper } from './helper.js';

// Re-export default as named export { default as User } from './User.js'; `

`javascript // Now import from single location import { add, multiply, format, User } from './utils/index.js'; `

Dynamic Imports

Load modules on demand with import().

`javascript // Static import (top of file, always loaded) import { heavy } from './heavy.js';

// Dynamic import (loaded when needed) async function loadHeavyModule() { const module = await import('./heavy.js'); module.heavy(); }

// Conditional loading if (needsChart) { const { Chart } = await import('./chart.js'); new Chart(data); }

// With error handling try { const module = await import('./optional.js'); module.doSomething(); } catch (error) { console.log('Module not available'); } `

Code Splitting Example

`javascript // Route-based code splitting const routes = { '/': () => import('./pages/Home.js'), '/about': () => import('./pages/About.js'), '/contact': () => import('./pages/Contact.js'), };

async function loadPage(path) { const loader = routes[path]; if (loader) { const module = await loader(); module.default.render(); } } `

Module Patterns and Best Practices

1. Barrel Exports (Index Files)

`javascript // components/index.js export { Button } from './Button.js'; export { Input } from './Input.js'; export { Modal } from './Modal.js';

// Usage import { Button, Input, Modal } from './components'; `

2. Avoid Default Export for Utilities

`javascript // BAD - hard to know what you're getting import utils from './utils';

// GOOD - explicit about what you need import { formatDate, parseDate } from './utils'; `

3. One Component Per File

`javascript // Button.js export function Button({ children, onClick }) { return ; } `

4. Constants in Separate Files

`javascript // constants.js export const API_URL = 'https://api.example.com'; export const MAX_RETRIES = 3; export const TIMEOUT = 5000; `

Common Mistakes

Mistake 1: Forgetting File Extension

`javascript // In browsers, extension is required import { add } from './math'; // Error! import { add } from './math.js'; // Correct

// In bundlers (Webpack, Vite), extension often optional import { add } from './math'; // Works `

Mistake 2: Circular Dependencies

`javascript // a.js import { b } from './b.js'; export const a = 'A' + b;

// b.js import { a } from './a.js'; export const b = 'B' + a; // Problem!

// Fix: Restructure or use dynamic imports `

Mistake 3: Side Effects in Modules

`javascript // BAD - runs on import let count = 0; export function increment() { return ++count; }

// GOOD - explicit initialization export function createCounter() { let count = 0; return { increment: () => ++count }; } `

Summary

- Named exports: Multiple exports, import with { } - Default export: One per module, import without { } - Re-exports: Combine modules with export ... from - Dynamic imports: Load on demand with import()` - Use named exports for utilities, default for main components - Create index.js barrel files for cleaner imports