JavaScript Modules

One File, One Module

JavaScript Modules

Modern JavaScript ships code as **modules** — each file is its own scope, with explicit imports and exports.

4 min read Level 2/5 #modules#esm#scripts
What you'll learn
  • Understand that each file is a separate scope
  • Know how to enable modules in HTML and Node
  • Recognize the differences from old-style scripts

A module is a JavaScript file with its own scope. Things you declare at the top of the file are private to that file — unless you export them. Other files use import to bring them in.

This is how modern apps split code across many small files.

Module Scope

Top-level const, let, function, and class declarations in a module file are module-scoped. They don’t leak to the global scope.

math.js
const PI = 3.14159;            // visible only inside math.js
export function area(r) {
  return PI * r * r;            // PI is in scope here
}
app.js
import { area } from "./math.js";
console.log(area(5));   // 78.54...
// console.log(PI);     // ReferenceError — PI is not exported

Enabling Modules

In the browser

Use <script type="module">:

<script type="module" src="./app.js"></script>

This:

  • Treats the file as a module.
  • Defers it (runs after the HTML is parsed).
  • Lets it use import and export.
  • Makes it run in strict mode automatically.

In Node.js

Two ways:

  • Name the file .mjs, OR
  • Set "type": "module" in your package.json.

Either tells Node to treat .js files as ES modules.

Modules Run In Strict Mode

All modules are automatically in strict mode — even if you don’t write "use strict". This catches more bugs at parse time. You won’t have to think about it much.

Modules Run Once

Even if many files import the same module, the module is loaded and executed only once. All importers see the same exports.

counter.js
console.log("counter.js loaded");
export let count = 0;

If three other files import from counter.js, you’ll see "counter.js loaded" exactly once.

Top-Level await

Modules support top-level await — you can await at the top of a module without wrapping it in an async function.

data.js
const response = await fetch("/api/data");
export const data = await response.json();

Other modules that import from data.js will wait for its setup to complete before running.

How This Differs From Old Scripts

ModulePlain script
File scope (import/export)Everything global
Strict mode by defaultSloppy unless you opt in
Deferred by defaultRuns in order, blocking parsing
this at top is undefinedthis at top is window
Loaded once across importersEach <script> runs independently

Modules are strictly better for any non-trivial app. Use them.

Up Next

The actual syntax — import and export.

JavaScript import & export →