On this page
ES Modules
What are ES modules?
ES modules (ESM) are JavaScript's native module system, standardized in ES2015. They allow you to split code into independent files with their own dependencies and exports.
Before ES modules, JavaScript had no native module system. Patterns such as IIFE, CommonJS (require), and AMD were used. ES modules replace all of them.
Key characteristics
- Each file is a module with its own scope (does not pollute the global)
- Dependencies are declared with import/export explicitly
- They are evaluated in strict mode automatically
- Imports are live bindings (they reflect changes from the exporting module)
Exports
Named exports
A module can have multiple named exports:
// utils.js
export function format(date) { /* ... */ }
export function validate(email) { /* ... */ }
export const VERSION = '1.0.0';Default export
Each module can have a single default export. It is useful when the module exports one main thing:
// Logger.js
export default class Logger {
log(msg) { console.log(msg); }
}When to use each
| Type | When to use |
|---|---|
| Named | Multiple utilities, constants, functions |
| Default | Classes, components, one main function |
Imports
// Named - exact names, with braces
import { add, subtract } from './math.js';
// Default - free name, without braces
import MyClass from './MyClass.js';
// Both
import MyClass, { utility } from './module.js';
// Rename
import { add as sum } from './math.js';
// Everything as namespace
import * as Utils from './utils.js';Re-exports (barrels)
An index.js file can re-export from multiple modules, creating a single entry point:
// features/index.js
export { UserList } from './user-list.js';
export { UserDetail } from './user-detail.js';
export { UserForm } from './user-form.js';This allows importing everything from a single path:
import { UserList, UserDetail, UserForm } from './features/index.js';Dynamic imports
import() as a function returns a promise and allows loading modules on demand. It is key for:
- Lazy loading — Loading code only when needed
- Code splitting — Splitting the bundle into smaller chunks
- Conditional loading — Loading modules based on platform or configuration
const button = document.querySelector('#open-editor');
button.addEventListener('click', async () => {
const { Editor } = await import('./editor.js');
const editor = new Editor();
editor.mount('#container');
});Modules in the browser
To use modules directly in HTML:
<script type="module" src="app.js"></script>Module-type scripts are automatically deferred and run in strict mode. In practice, bundlers (Vite, Webpack, esbuild) process modules for production.
Practice
- Create a module with named exports: Create a file
utils.jsthat exports two functions (capitalizeandtruncate) and a constantMAX_LENGTH. Import them in another file and use them. - Implement a barrel export: Create three small modules and an
index.jsfile that re-exports everything. Import from the barrel and verify that all exports are available. - Use dynamic import: Write a button that on click loads a module dynamically with
import()and executes a function from the loaded module. Observe in the network tab that the module loads on demand.
In the next lesson we will explore modern JavaScript patterns.
// === math.js ===
// Named exports
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export const PI = 3.14159;
// === user.js ===
// Default export (one per file)
export default class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
greet() {
return `Hello, I am ${this.name}`;
}
}
// === constants.js ===
export const CONFIG = Object.freeze({
API_URL: 'https://api.example.com',
VERSION: '2.0.0',
MAX_RETRIES: 3,
});
export const ROLES = Object.freeze({
ADMIN: 'admin',
EDITOR: 'editor',
VIEWER: 'viewer',
});
// === app.js ===
// Named imports
import { add, subtract, PI } from './math.js';
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159
// Default import (free name)
import User from './user.js';
const carlos = new User('Carlos', '[email protected]');
// Rename imports
import { add as sum, subtract as minus } from './math.js';
// Import everything as namespace
import * as MathUtils from './math.js';
console.log(MathUtils.add(1, 2));
console.log(MathUtils.PI);
// Re-export (barrels)
// === index.js ===
export { add, subtract } from './math.js';
export { default as User } from './user.js';
export { CONFIG, ROLES } from './constants.js';
// Dynamic import (lazy loading)
async function loadEditor() {
const module = await import('./editor.js');
const editor = new module.default();
editor.init();
}
// Conditional
if (needsCharts) {
const { renderChart } = await import('./charts.js');
renderChart(data);
}
Sign in to track your progress