Modules Overview
A module is a JavaScript file that has its own private scope and explicitly chooses what to share with the outside world. Anything declared inside a module — variables, functions, classes — is hidden by default. To make something usable from another file you export it; to consume something from another file you import it. Modules are how real JavaScript codebases stay organised once they grow past a single page of script.
Before modules: one big global
In old-school browser code, every <script> shared the same global object (window in the browser). Variables declared at the top level of one file were visible everywhere, which made name collisions almost guaranteed and load order load-bearing.
Pre-module pain
<script src="utils.js"></script> <!-- defines window.format --> <script src="vendor.js"></script> <!-- also defines window.format (oops) --> <script src="app.js"></script> <!-- which one wins? whoever loaded last -->
Workarounds — IIFEs, the "namespace object" pattern, AMD, CommonJS — kept the lights on for years. ES2015 finally added modules to the language itself.
What a module gives you
File-level scope. Top-level
let,const,function, andclassdeclarations are private to the file. They never leak ontowindowor the global object.Explicit boundaries. A glance at the
importandexportlines tells you exactly what a file consumes and what it offers — no hidden globals.Strict mode by default. Every module runs in strict mode, even without the
"use strict"directive.Static structure. Imports and exports are resolved before code runs, which lets bundlers tree-shake unused exports and lets tools statically check what each file uses.
Top-level
await. A module is allowed toawaitdirectly at the top level (more on this in Top-Level await).
A minimal example
math.js
// Anything you want callers to use, you export.
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;
// Anything else stays private to this file.
function internalHelper() {
return "you can't see me from outside";
}app.js
import { add, PI } from "./math.js";
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159
// console.log(internalHelper); // ReferenceError — not exported5 3.14159
Loading modules in the browser
Browsers only treat a file as a module when you ask them to. Mark the entry script with type="module":
index.html
<!DOCTYPE html>
<html>
<body>
<script type="module" src="./app.js"></script>
</body>
</html>A few rules follow from that:
Module scripts are deferred by default — they run after the HTML is parsed, in document order.
Module specifiers must be a URL or a relative path (e.g.
"./math.js"). Bare names like"lodash"need a bundler or an import map.Modules are fetched with CORS, so you usually need to serve them from a real HTTP server, not
file://.Each module is fetched and executed exactly once, even if many files import it.
Why modules are worth the small ceremony
Organisation. You can split a 5000-line script into small files with names that say what they do.
Encapsulation. Helper functions can stay helpers — no risk of becoming an accidental public API.
Reuse. The same module can be imported from many places without re-declaring globals.
Lazy loading. With
import()you can defer loading a chunk until the user actually needs it. See Dynamic Imports.Tooling. Static analysis, dead-code elimination, type checking and refactors all rely on knowing what each file imports.
Modules vs classic scripts at a glance
Top-level scope — module: private to the file; classic script: shared global.
Strict mode — module: always; classic: only with
"use strict".thisat the top level — module:undefined; classic: the global object.Load timing — module: deferred; classic: synchronous unless you add
defer/async.import/export— module: works; classic: syntax error.
The next pages dig into the syntax — import / export, Dynamic Imports, and how ES modules compare to Node's older CommonJS format.