JavaScript ES6 Modules: Understanding import & export
One of the biggest upgrades in modern JavaScript came with ES6 Modules.
They didn’t just add new syntax — they changed how we structure, scale, and reason about JavaScript applications.
The Problem
Before ES6 Modules there exists, a single massive JavaScript file, in which things quickly become unmanageable. Every feature, helper function, and variable lives in the same place, making the file hard to read, harder to debug, and risky to change. A small modification in one part can unintentionally break something elsewhere.
Then there are global variables, where it’s often unclear which part of the code modified them. Before ES6 Modules, variables declared in one file often leaked into the global scope. This means:
These bugs were especially painful because the code looked correct, but failed at runtime.
Also, tightly coupled code made reuse almost impossible. Functions depended on shared globals instead of explicit inputs. We couldn’t move logic to another file or reuse it in a different project without dragging half the codebase along with it.
How ES6 Modules Fix This
ES6 Modules solve all three problems by introducing explicit boundaries:
This means:
Instead of one giant script controlling everything, our application becomes a collection of small, focused modules that work together — which is exactly how modern JavaScript applications are built.
What Are ES6 Modules?
An ES6 Module is simply a JavaScript file that:
Each file becomes a self-contained unit of logic.
// math.js
export const add = (a, b) => a + b;
// index.js
import { add } from "./math.js";
This small change enables clean architecture in JavaScript.
Why Do We Need Modules?
Before ES6, JavaScript didn’t have a native module system. Everything lived in the global scope, and that shaped how applications were written.
This approach worked for small scripts and simple pages, but as applications scaled:
That growing pain is exactly why ES6 Modules were introduced—to bring structure, isolation, and predictable dependency management to JavaScript.
What ES6 Modules solved:
With ES6 Modules in place, JavaScript unlocked several powerful benefits that simply weren’t possible before:
✔ Predictable dependencies : Dependencies are declared explicitly using import and export. You can instantly see what a file needs and what it exposes. No more guessing based on script order or hunting through globals—the module graph is clear, deterministic, and easy to reason about.
✔ Better tooling : Because the dependency graph is static and analyzable, tools like bundlers, linters, and IDEs can work smarter. This enables:
✔ Tree-shaking & optimization : Since ES6 modules use static imports, bundlers can remove unused exports during build time. If you import only one function from a library, the rest can be excluded from the final bundle—resulting in smaller files, faster loads, and better performance, especially for large applications.
Together, these features transformed JavaScript from “script-based” code into a scalable, production-ready module system.
Exporting in ES6
1. Named Exports
Used when a module exposes multiple utilities.
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
We can also export at the bottom:
const multiply = (a, b) => a * b;
const divide = (a, b) => a / b;
export { multiply, divide };
2. Default Export
Used when the module has one primary responsibility.
// calculator.js
export default function calculate(a, b) {
return a + b;
}
A module can have only one default export.
Importing in ES6
1. Import Named Exports
import { add, subtract } from "./math.js";
2. Import with Alias
import { addas sum } from "./math.js";
3. Import Default Export
import calculate from "./calculator.js";
4. Import Everything
import * as math from "./math.js";
math.add(2,3);
Module Scope (Very Important)
Each ES module has its own private scope.
Recommended by LinkedIn
// config.js
const secret ="123"; // private
export const apiKey = "abc"; // public
Unlike classic scripts:
This is one of the biggest architectural wins of ES6 Modules.
How ES6 Modules Work Internally
>> Modules are loaded only once
When a module is imported, JavaScript loads and executes it a single time. If multiple files import the same module, they all share the same instance.
// config.js
export let count = 0;
export function increment() {
count++;
}
// a.js
import { increment } from "./config.js";
increment();
// b.js
import { increment } from "./config.js";
increment();
✔ count becomes 2
The module is not re-executed for each import
This behavior allows modules to safely maintain shared state.
>> Imports are live bindings (not copies)
When you import something, you’re not getting a copy of its value — you’re getting a live reference to it. If the exported value changes, all imports see the updated value.
// state.js
export let value = 10;
export function update() {
value = 20;
}
// app.js
import { value, update } from "./state.js";
console.log(value); // 10
update();
console.log(value); // 20
This is why imported variables stay in sync across files.
>> Execution is deferred
ES modules are executed after the HTML is parsed, even without defer.
<script type = "module" src = "app.js"></script>
This means:
It makes module scripts safer and more predictable.
>> Modules run in strict mode by default
All ES modules automatically run in JavaScript strict mode. This prevents common mistakes:
x = 10; // ❌ ReferenceError (no implicit globals)
function test(a, a) {} // ❌ Duplicate parameters not allowed
Benefits:
We don’t need to write:
"use strict";
It’s already enforced.
Using ES Modules in the Browser
<script type = "module" src = "index.js"></script>
This enables:
Module scripts follow CORS rules.
ES Modules in Node.js
You can enable ES Modules by either:
{
"type":"module"
}
Now Node.js understands import / export natively.
ES Modules vs CommonJS
ES Modules (ESM) and CommonJS (CJS) differ significantly in their implementation of JavaScript modularity.
ESM uses import and export syntax to support static loading and native browser compatibility, whereas CJS relies on require and module.exports for dynamic loading, which lacks native browser support.
ES Modules enable tree shaking for better performance and operate in strict mode by default, CommonJS does not support tree shaking and requires strict mode to be enabled manually.
This is why modern tooling prefers ES Modules.
Real-World Usage
We use ES6 Modules without even noticing in:
They are the foundation of modern JavaScript architecture.