JavaScript ES6 Modules Import Export Guide
A practical reference for JavaScript ES6 module syntax. Learn every import and export pattern, from basic named exports to dynamic imports, with clear examples you can use immediately.
ES6 modules give JavaScript a native way to split code across files. Before modules, developers relied on global variables and script loading order. Today, every modern JavaScript project uses import and export to organize code into reusable, maintainable pieces.
This guide covers the complete syntax. Each pattern includes a short example you can copy and adapt. For deeper explanations of specific topics, follow the linked articles.
The Core Pattern
A module is just a file. Anything you want to share, you export.
Anything you need from another file, you import. That is the entire mental model:
// math.js - exports what it shares
export function add(a, b) { return a + b; }
// app.js - imports what it needs
import { add } from "./math.js";
console.log(add(2, 3)); // 5The script tag in HTML tells the browser this is a module. Modules are deferred by default, meaning they load after the HTML parses:
<script type="module" src="app.js"></script>The browser fetches each imported module once and caches it. Multiple files importing the same module share a single instance.
Named Exports
Named exports let a module share multiple values. Each exported value has a name that importers use to reference it. You can export inline on the declaration or group exports at the bottom of the file.
Exporting inline keeps the export visible next to the declaration:
export const API_URL = "https://api.example.com";
export function fetchUser(id) { /* ... */ }
export class UserService { /* ... */ }Grouping exports at the bottom creates a clean summary of the module's public API in one convenient place at the end:
function formatDate(date) { /* ... */ }
function formatCurrency(amount) { /* ... */ }
export { formatDate, formatCurrency };Import named exports with curly braces around the name. The names must match exactly between the export and import statements:
import { formatDate, formatCurrency } from "./utils.js";For detailed coverage including aliases and barrel files, see named exports.
Default Exports
A default export is the one primary value a module shares. Modules can have at most one default export. Import it without curly braces and give it any local name:
// greet.js
export default function greet(name) {
return `Hello, ${name}!`;
}
// app.js
import greet from "./greet.js";
console.log(greet("Alice")); // Hello, Alice!A module can combine a default export with named exports in the same file. The default comes first in the import statement:
import greet, { formatDate } from "./utils.js";For more patterns and tradeoffs, see default exports.
Renaming Imports and Exports
Use the as keyword to rename when names collide or when you want a clearer local name:
// Rename on import
import { formatDate as format } from "./dates.js";
// Rename on export
export { fetchUserData as getUser };This avoids naming collisions without changing either source module. It is especially useful when two libraries export functions with the same name.
Re-Exporting from Barrel Files
A barrel file collects exports from multiple modules and re-exports them from one place. Consumers import from a single clean path:
// utils/index.js
export { login, logout } from "./auth.js";
export { formatDate } from "./dates.js";Now any file imports everything from one location, keeping the import list clean and predictable across the entire project codebase:
import { login, logout, formatDate } from "./utils/index.js";Avoid the wildcard form export * from when you want precise control over what gets re-exported. Explicit re-exports work better with tree shaking and make the public API auditable.
Namespace Imports
When you need everything from a module, import it all under a single namespace object:
import * as MathUtils from "./math.js";
console.log(MathUtils.add(2, 3)); // 5
console.log(MathUtils.subtract(10, 2)); // 8The namespace object contains every named export as a property. Default exports are not included in the namespace.
Dynamic Imports
Static imports load at parse time. Dynamic imports load on demand using the import() function, which returns a Promise:
// Load a module only when needed
const { heavyFunction } = await import("./heavy-module.js");
heavyFunction();Dynamic imports enable code splitting and conditional loading. The bundler automatically extracts dynamically imported modules into separate chunks.
Module Execution Order
ES modules execute exactly once, even when imported multiple times. The first import triggers execution, and subsequent imports return the cached module object. This guarantees predictable initialization:
// config.js
console.log("Config loaded"); // Runs once
export const API_URL = "https://api.example.com";
// a.js
import { API_URL } from "./config.js"; // Logs "Config loaded"
// b.js
import { API_URL } from "./config.js"; // No log - module is cachedImports are hoisted to the top of the module scope, so they execute before any other code in the file. This means you can reference imported values anywhere in the module body.
Browser vs Node.js Usage
In the browser, use the type attribute on script tags:
<script type="module" src="app.js"></script>In Node.js, either use the .mjs file extension or set type to module in package.json for full ES module support:
{
"type": "module"
}Both environments support the same import and export syntax, but a few setup details differ:
| Browser | Node.js | |
|---|---|---|
| How to enable | type="module" script tag | .mjs extension or "type": "module" |
| Path specifiers | Full paths with extensions required | Resolves paths like traditional require |
| Top-level await | Supported | Supported (ES modules only) |
For how this compares to the older module system, see CommonJS vs ES modules.
Rune AI
Key Insights
- Use export to share values from a module and import to bring them into another file.
- Named exports let you share multiple values; default exports share one primary value.
- Dynamic import() loads modules on demand and returns a Promise.
- Re-export with export from to create barrel files that consolidate multiple modules.
- ES modules run in strict mode with their own scope, unlike traditional scripts.
Frequently Asked Questions
What is the difference between ES modules and regular scripts?
Can I use ES modules in Node.js?
Do I need a bundler to use ES modules?
Conclusion
ES6 modules are the standard way to organize JavaScript code. The import and export syntax is clean, declarative, and supported everywhere from browsers to Node.js. Master the patterns in this guide and you will write modular code that bundlers can optimize, tools can analyze, and other developers can understand. For deeper dives into specific patterns, explore the linked articles on named exports, default exports, and dynamic imports.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.