Why import creates a "Live Binding" and how ESM differs from CommonJS.
Timothy was standing at the chalkboard, looking frustrated. He had written three different files for his application, but they were crashing.
// user.js
var name = "Timothy";
// admin.js
var name = "Admin"; // Error! or Overwrite!
// main.js
console.log(name); // Which one is it?
"I keep overwriting my own variables," Timothy said. "Every time I add a script tag, I pollute the global namespace."
Margaret walked in. She didn’t hold a prop. She just pointed to the code.
"That is because you are not using Modules," she said. "You are loading scripts into the Global Scope. We need to encapsulate your code."
The Old World vs. The Module
Margaret wiped the board clean...
Why import creates a "Live Binding" and how ESM differs from CommonJS.
Timothy was standing at the chalkboard, looking frustrated. He had written three different files for his application, but they were crashing.
// user.js
var name = "Timothy";
// admin.js
var name = "Admin"; // Error! or Overwrite!
// main.js
console.log(name); // Which one is it?
"I keep overwriting my own variables," Timothy said. "Every time I add a script tag, I pollute the global namespace."
Margaret walked in. She didn’t hold a prop. She just pointed to the code.
"That is because you are not using Modules," she said. "You are loading scripts into the Global Scope. We need to encapsulate your code."
The Old World vs. The Module
Margaret wiped the board clean.
"In the past," she explained, "JavaScript files shared a single global scope (window). To fix this, we used IIFEs (Immediately Invoked Function Expressions) or libraries like CommonJS."
"But today," she continued, "we have ES Modules (ECMAScript Modules) built directly into the language. They change the fundamental rules of how files behave."
She wrote the activation key:
<script type="module" src="main.js"></script>
"By adding type='module', you trigger three strict rules:"
- File-Level Scope: Variables are no longer global by default.
- Strict Mode:
use strictis enabled automatically. - Deferred Execution: The code waits until the HTML is parsed.
Rule 1: Encapsulation (File Scope)
Margaret rewrote Timothy’s code using the Module syntax.
// user.js
const name = "Timothy";
// This variable is now PRIVATE to this file.
// It does not exist on 'window'.
export const publicName = name;
// We explicitly choose what leaves the file.
"Think of a Module as a closure for the entire file," Margaret said. "Nothing leaves unless you export it. Nothing enters unless you import it."
Rule 2: Static Analysis (The Dependency Graph)
Timothy started writing an import inside a function:
function loadAdmin() {
import { admin } from './admin.js'; // Syntax Error!
}
"Stop," Margaret said. "Standard import statements must be at the top level. They cannot be dynamic."
"Why?" Timothy asked.
"Because the Engine performs Static Analysis," Margaret explained. "Before it runs a single line of code, it scans every file to build a Dependency Graph. It needs to know exactly what connects to what before execution begins."
The Secret: Live Bindings
"Now for the most important difference," Margaret said. "This sets ES Modules apart from CommonJS (Node.js legacy)."
She drew a comparison table:
CommonJS (require) | ES Modules (import) |
|---|---|
| Copies the value | Creates a Live Reference |
| Dynamic (can run anywhere) | Static (must be at top) |
"What is a Live Reference?" Timothy asked.
Margaret wrote a demonstration:
// counter.js
export let count = 0;
export function increment() {
count++;
}
// main.js
import { count, increment } from './counter.js';
console.log(count); // 0
increment();
console.log(count); // 1 <-- The value updated automatically!
"Look closely," Margaret said. "In main.js, we never reassigned count. In fact, we can’t reassign it—imports are read-only bindings. But the value updated."
"How?"
"Because import does not give you a copy of 0. It gives you a direct link to the count variable inside counter.js. If counter.js changes the value, your import updates instantly."
"Warning," Margaret added. "This magic only works with Named Exports (export { count }). If you use export default count, you break the link and just get a copy of the value."
Rule 3: Singletons
Timothy looked at the board. "What if I import counter.js in ten different files? Do I get ten counters?"
"No," Margaret said. "Modules are Singletons."
"The Engine evaluates a module exactly once," she explained. "The first time you import it, the code runs, and the exports are cached in memory. Every subsequent import just hands back a reference to that same memory."
The Conclusion
Timothy looked at his separated files. They were clean. They were safe.
"It is not just about organizing code," Timothy realized. "It is about controlling scope."
"Correct," Margaret said. "Modules give us boundaries. They let us build complex systems without everything collapsing into the global scope."
Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.