ES Modules
Import/export syntax, dynamic imports, module patterns, and bundling
ES Modules (ESM)
ES Modules are the official, standardized module system for JavaScript. They provide a clean syntax for organizing code into reusable pieces with explicit imports and exports. ESM is supported in all modern browsers and Node.js.
Key Features
- Static analysis — Imports/exports are determined at compile time
- Strict mode — Modules are always in strict mode
- Deferred execution — Module scripts are deferred by default
- Single instance — Modules are singletons (cached after first load)
Named Exports
// math.js — Named exports
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// Can also export at the end
const subtract = (a, b) => a - b;
const divide = (a, b) => a / b;
export { subtract, divide };
// Rename on export
export { subtract as sub, divide as div };
// main.js — Named imports
import { add, multiply, PI } from "./math.js";
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
console.log(PI); // 3.14159
// Rename on import
import { add as sum, multiply as mult } from "./math.js";
// Import all as namespace
import * as math from "./math.js";
console.log(math.add(1, 2));
console.log(math.PI);
Default Exports
// logger.js — Default export
export default function log(message) {
console.log(`[LOG] ${message}`);
}
// Or with class
export default class Logger {
log(msg) {
console.log(msg);
}
}
// Or export at the end
function log(message) {
console.log(message);
}
export default log;
// main.js — Default import (any name works)
import log from "./logger.js";
import myLogger from "./logger.js"; // Same thing, different name
log("Hello!");
// Mix default and named
import log, { formatMessage, LogLevel } from "./logger.js";
Re-exporting
// utils/index.js — Barrel file (re-exports)
export { add, subtract } from "./math.js";
export { formatDate } from "./date.js";
export { default as Logger } from "./logger.js";
// Re-export everything
export * from "./math.js";
// Re-export with rename
export { add as sum } from "./math.js";
// main.js — Clean imports from barrel
import { add, formatDate, Logger } from "./utils/index.js";
// or
import { add, formatDate, Logger } from "./utils"; // With bundler
Dynamic Imports
// Static import (top of file, always loads)
import { heavyFunction } from "./heavy-module.js";
// Dynamic import (loads on demand, returns Promise)
async function loadModule() {
const module = await import("./heavy-module.js");
module.heavyFunction();
}
// Conditional loading
if (needsFeature) {
const { feature } = await import("./feature.js");
feature();
}
// Load based on user action
button.addEventListener("click", async () => {
const { showModal } = await import("./modal.js");
showModal();
});
// With error handling
try {
const module = await import(`./locales/${language}.js`);
applyTranslations(module.translations);
} catch (error) {
console.error("Failed to load locale:", error);
}
Using Modules in HTML
<!-- Module script (deferred by default) -->
<script type="module" src="main.js"></script>
<!-- Inline module -->
<script type="module">
import { greet } from "./utils.js";
greet("World");
</script>
<!-- Fallback for older browsers -->
<script nomodule src="legacy-bundle.js"></script>
<!-- Preload modules for performance -->
<link rel="modulepreload" href="./utils.js">
CommonJS vs ES Modules
| Feature | CommonJS (CJS) | ES Modules (ESM) |
|---|---|---|
| Syntax | require() / module.exports |
import / export |
| Loading | Synchronous | Asynchronous |
| Analysis | Dynamic (runtime) | Static (compile time) |
| Tree shaking | Difficult | Built-in support |
| Browser | Needs bundler | Native support |
Module Patterns
// Singleton pattern
let instance = null;
export function getInstance() {
if (!instance) {
instance = createInstance();
}
return instance;
}
// Factory with private state
const privateData = new WeakMap();
export class User {
constructor(name) {
privateData.set(this, { name });
}
getName() {
return privateData.get(this).name;
}
}
// Configuration module
export const config = Object.freeze({
API_URL: "https://api.example.com",
TIMEOUT: 5000,
VERSION: "1.0.0"
});
// Side-effect only import
// analytics.js
console.log("Analytics loaded");
window.analytics = { track: () => {} };
// main.js
import "./analytics.js"; // Just runs the module
Node.js ESM
// package.json - Enable ESM for entire project
{
"type": "module"
}
// Or use .mjs extension for ESM files
// utils.mjs
// Import JSON (requires assertion)
import data from "./data.json" with { type: "json" };
// Import from node_modules
import express from "express";
import { useState } from "react";
// Node built-ins
import fs from "node:fs";
import path from "node:path";
// __dirname equivalent in ESM
import { fileURLToPath } from "node:url";
import { dirname } from "node:path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
📚 Learn More
// import.meta
console.log(import.meta.url); // file:///path/to/module.js
💡 Key Takeaways
- • Use named exports for multiple values, default for main export
- • Dynamic import() for code splitting and lazy loading
- • Barrel files (index.js) simplify imports
- • ESM enables tree shaking for smaller bundles
- • Modules are singletons and always use strict mode
- • Use
"type": "module"in package.json for Node.js ESM