JavaScript ES6 Modules Import Export Guide
A complete guide to JavaScript ES6 modules. Learn the import and export syntax, how modules differ from scripts, named vs default exports, re-exporting, module scope and strict mode, browser module support with type=module, and organizing large codebases with ES modules.
ES6 modules are the native module system for JavaScript. They let you split code across files, declare public APIs with export, and consume them with import. Modules run in strict mode, have their own scope, and are statically analyzable, enabling tree-shaking and other build optimizations.
Modules vs Scripts
| Feature | Script (<script>) | Module (<script type="module">) |
|---|---|---|
| Scope | Global (shared window) | Module-level (isolated) |
| Strict mode | Optional | Always strict |
Top-level this | window | undefined |
import / export | Not available | Available |
| Loading | Synchronous by default | Deferred by default |
| CORS | Not required for same-origin | Required for cross-origin |
Named Exports
Named exports make specific bindings available by name:
// math.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}Exporting at the Bottom
You can also declare first and export at the end:
// math.js
const PI = 3.14159;
function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }
export { PI, add, multiply };Both approaches create identical named exports. The bottom-export style is useful when you want a clear "public API" section. See JavaScript named exports a complete tutorial for a deep dive.
Default Exports
Each module can have at most one default export:
// Logger.js
export default class Logger {
constructor(prefix) {
this.prefix = prefix;
}
log(message) {
console.log(`[${this.prefix}] ${message}`);
}
}See JavaScript default exports complete tutorial for the full guide on defaults.
Importing Named Exports
// Import specific named exports
import { add, multiply } from "./math.js";
console.log(add(2, 3)); // 5
// Import with alias (rename)
import { add as sum, PI } from "./math.js";
console.log(sum(2, 3)); // 5
console.log(PI); // 3.14159Import All as Namespace
import * as math from "./math.js";
console.log(math.PI); // 3.14159
console.log(math.add(2, 3)); // 5
console.log(math.multiply(4, 5)); // 20Importing Default Exports
// No curly braces — you choose the local name
import Logger from "./Logger.js";
import MyLogger from "./Logger.js"; // same thing, different name
const log = new Logger("app");
log.log("started"); // [app] startedImporting Default and Named Together
// utils.js
export default function main() { /* ... */ }
export function helper1() { /* ... */ }
export function helper2() { /* ... */ }
// consumer.js
import main, { helper1, helper2 } from "./utils.js";Re-Exporting
Barrel files aggregate and re-export from multiple modules:
// components/index.js
export { Button } from "./Button.js";
export { Modal } from "./Modal.js";
export { Tooltip } from "./Tooltip.js";
// Re-export a default as a named export
export { default as Header } from "./Header.js";
// Re-export everything from a module
export * from "./utils.js";Consumers import from the barrel:
import { Button, Modal, Header } from "./components/index.js";Renaming Exports
// internal.js
function internalName() { /* ... */ }
export { internalName as publicName };// consumer.js
import { publicName } from "./internal.js";This is useful when a module's internal name differs from the API name you want to expose. For the destructuring equivalent, see renaming variables during JS destructuring guide.
Module Scope
Each module has its own top-level scope. Variables declared in a module are not global:
// counter.js
let count = 0;
export function increment() {
count++;
}
export function getCount() {
return count;
}// main.js
import { increment, getCount } from "./counter.js";
increment();
increment();
console.log(getCount()); // 2
// count is not accessible here — it's private to counter.jsModules are singletons -- the module body runs once, and all importers share the same instance. count above is shared state.
Browser Usage
<!-- type="module" enables ES module syntax -->
<script type="module" src="./main.js"></script>
<!-- Inline module -->
<script type="module">
import { add } from "./math.js";
console.log(add(1, 2));
</script>
<!-- Fallback for browsers without module support -->
<script nomodule src="./bundle-legacy.js"></script>Module scripts are deferred automatically (no need for defer attribute) and execute after the DOM is parsed.
Common Patterns
Conditional Exports Object
// config.js
const dev = { apiUrl: "http://localhost:3000", debug: true };
const prod = { apiUrl: "https://api.example.com", debug: false };
export default process.env.NODE_ENV === "production" ? prod : dev;Exporting Constants and Enums
// status.js
export const Status = Object.freeze({
PENDING: "pending",
ACTIVE: "active",
INACTIVE: "inactive",
});Rune AI
Key Insights
- Modules have isolated scope: Variables in a module are not global; only explicitly exported bindings are accessible to importers
- Named exports use curly braces on import:
import { name } from "./mod.js"-- the braces are not destructuring, they are module import syntax - Default exports allow any local name on import:
import Whatever from "./mod.js"-- the name is your choice - Modules are singletons: The module body executes once; all importers share the same live bindings and state
- Static imports enable tree-shaking: Bundlers can analyze the import graph at build time and eliminate unused exports from the final bundle
Frequently Asked Questions
Can I use import inside a function or if block?
Do circular imports cause errors?
Are ES modules synchronous or asynchronous?
Should I always include the .js extension in import paths?
What is the difference between export default and export as default?
Conclusion
ES6 modules provide a clean, standardized way to organize JavaScript code. Named exports give precise, tree-shakable imports. Default exports provide a convenient single-value entry point. Re-exports enable barrel patterns for large codebases. Modules run in strict mode with isolated scope, preventing global pollution. For deeper coverage of each export style, see JavaScript named exports a complete tutorial and JavaScript default exports complete tutorial.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.