ESM Modules
In short: ESM (ECMAScript Modules) is the standardised import/
export syntax browsers and modern Node both understand natively.
import and export are static declarations — the engine reads and
resolves them before any code runs, so they must sit at a file's top
level and can never be conditional, inside an if or a function.
In return, ESM exports are live bindings: an imported name stays
genuinely connected to its source, and updates automatically when the
source changes it — the opposite of CommonJS's one-time copy. This
lesson's code needs real files on disk to run — read it, don't run it.

Watch an imported value update itself
Import a variable, call a function that changes it, and the imported name reflects the change — no re-import needed:
// counter.mjs
export let count = 0;
export function increment() { count++; }
// main.mjs
import { count, increment } from "./counter.mjs";
console.log(count); // 0
increment();
console.log(count); // 1 — updated automaticallyThis is the defining difference from CommonJS: count here isn't a copied
value, it's a live binding to the actual variable inside
counter.mjs. Every file that imports count sees the same, current
value, always.
The trap: trying to import conditionally
Because import is resolved statically — before the module's code even
starts running — it cannot depend on a runtime condition:
if (Math.random() > 0.5) {
import { count } from "./counter.mjs"; // SyntaxError
}This fails to even parse: import is only legal as a top-level
declaration, never nested inside an if, a function, a loop, or any other
block. Needing a module conditionally at runtime calls for dynamic
import() instead — a function-like form that returns a promise:
const mod = await import("./counter.mjs");.
Everyday differences worth knowing
A few more contrasts that matter in practice: ESM runs in strict mode
automatically, with no "use strict" needed. Browsers load ESM files with
<script type="module" src="main.mjs">; Node recognises them by a
.mjs extension or "type": "module" in package.json. ESM also
supports top-level await directly in a module's own body, something
CommonJS's synchronous require has no equivalent for.
Quick practice
What does import { count } from "./counter.mjs"; increment(); console.log(count); print, given export let count = 0; and an increment() that does count++?
Why does if (true) { import { count } from "./counter.mjs"; } fail?
What's the ESM equivalent of Node's conditional, runtime-only require()?
Which statement is true about ESM's strict mode?
What is the key behavioural difference between an ESM export and a CommonJS export?
🎯 Your turn
Given counter.mjs with
export let count = 0; export function increment() { count++; },
and main.mjs with
import { count, increment } from "./counter.mjs"; increment(); console.log(count);,
explain what gets printed and why this differs from the equivalent
CommonJS example, and explain why
if (Math.random() > 0.5) { import { count } from "./counter.mjs"; }
is not allowed.
Write your explanation and submit it — there's no code to run here, just your reasoning.
