ES Modules
Each file is its own module with its own scope, and export/import wire live values between files, not copies.
Loading visual…
Definition
An ES module is just a JavaScript file that uses export to expose bindings and import to read bindings exposed by other files. Each module has its own top level scope, so a variable declared in one file is not visible in another unless it is explicitly exported. A module can export multiple named values with export, and at most one default export with export default, which is meant to be the file's main thing and is imported without curly braces. An import is a live, read only binding to the exporting module's variable, not a copied snapshot taken at import time. If the exporting module later reassigns an exported let, every module that imported it sees the updated value the next time it reads that binding, because they are all looking at the same underlying variable. Modules are also singletons: no matter how many files import a given module, its code runs once, the first time it is loaded, and every importer shares that same single instance. On top of that, module code always runs in strict mode automatically, even without a "use strict" directive.
Examples
// math.js
export const PI = 3.14159;
export function double(n) {
return n * 2;
}
// app.js
import { PI, double } from "./math.js";
double(PI); // 6.28318math.js exposes two named exports. app.js imports them by name inside curly braces, matching the names they were exported with.
// logger.js
export default function log(msg) {
console.log(msg);
}
// app.js
import log from "./logger.js";
log("ready"); // readylogger.js has one default export, so app.js imports it without curly braces and can name it anything on the way in.
// counter.js
export let total = 0;
export function increment() {
total++;
}
// app.js
import { total, increment } from "./counter.js";
total; // 0
increment();
total; // 1increment() mutates counter.js's total. Because the import is a live binding and not a copy, app.js reads the updated value of total after the call, without importing anything new.
Where you see this
- Splitting an app into focused files (a math helper, a logger, a set of constants) instead of one giant script.
- Sharing a single configuration object or cache across every file that imports it, since modules run once and are shared.
- Choosing a default export for a file's one main component or function, and named exports for its smaller supporting pieces.
- Bundlers and build tools like webpack, Rollup, and Vite relying on static export/import syntax to tree shake unused code.