Scripts vs Modules in JavaScript Runtimes
In native JavaScript runtimes, code is executed as either a classic
script or an ECMAScript module (ESM). The core difference lies in how
the runtime handles scoping, execution behavior, dependency management,
and strictness. While classic scripts execute in a shared global context
by default, modules provide an isolated file-level scope, enforce strict
mode automatically, and allow the use of native import and
export statements for modular architecture.
Scoping and the Global Object
- Scripts: Top-level variables and functions declared
with
varor standard function declarations are attached directly to the global object (such aswindowin browsers orglobalin Node.js). This can lead to namespace pollution and accidental variable overwrites. - Modules: Each module has its own top-level module scope. Variables, functions, and classes defined in a module are private by default and cannot be accessed by other files unless explicitly exported and imported.
Strict Mode
- Scripts: Run in non-strict (“sloppy”) mode by
default. Developers must explicitly include the
"use strict";directive at the top of the file to enable strict mode. - Modules: Run in strict mode automatically.
Directives like
"use strict";are unnecessary, and behaviors like assigning values to undeclared variables will throw runtime errors.
Value of Top-Level
this
- Scripts: The value of
thisat the top level refers to the global execution context (windowin browsers). - Modules: The value of top-level
thisisundefined.
Import and Export Capabilities
- Scripts: Cannot use static
importorexportdeclarations. Attempting to use them in a traditional script results in a syntax error. - Modules: Natively support
importandexportstatements, enabling static analysis and tree-shaking by modern build tools and runtimes. Modules also natively support top-levelawaitwithout needing to wrap asynchronous code inside an immediately invoked function expression (IIFE).
Loading and Execution in Browsers
- Scripts: Loaded using
<script src="file.js">. By default, they block HTML parsing until the file is downloaded and executed, unless attributes likedeferorasyncare added. - Modules: Loaded using
<script type="module" src="file.js">. They are automatically deferred by default, meaning they download in parallel with HTML parsing and execute only after the document has been fully parsed. Additionally, module scripts are fetched using Cross-Origin Resource Sharing (CORS) rules.
Execution Frequency (Deduplication)
- Scripts: If the same script file is included multiple times in an application, the runtime will download (if not cached) and execute it multiple times.
- Modules: Modules are singletons. A module is only executed once, regardless of how many times it is imported across different files in the runtime dependency graph. Subsequent imports receive references to the already evaluated exports.
Runtime Resolution (Node.js)
- Scripts: Historically treated as CommonJS modules
in Node.js using
require()andmodule.exports. - Modules: Treated as native ES modules if the file
has an
.mjsextension or if the nearestpackage.jsoncontains"type": "module".