ECMAScript has two goal symbols for source text: Script and Module. The host decides which one a file is parsed as: <script> versus <script type="module"> in a browser, and .mjs or "type": "module" in Node.js.
| Script | Module | |
|---|---|---|
| Top-level scope | Shared global scope: top-level var and functions become properties of the global object |
Own module scope: nothing leaks to globalThis |
| Strict mode | Opt in with 'use strict' |
Always strict |
Top-level this |
The global object | undefined |
import / export |
Static syntax not allowed (dynamic import() is) |
Allowed |
Top-level await |
Not allowed | Allowed |
| Evaluated | Every time it is run or included | Once per module; later imports reuse the same instance (browsers key it by URL) |
| Browser loading | Blocks parsing unless async/defer |
Fetched asynchronously and deferred by default |
Modules also have live bindings: an import is a read-only view of the exporting module's variable, not a copy. Modules form a graph whose dependencies are resolved and evaluated before the importing module.
Node.js adds a third form that is neither: CommonJS (require) wraps a file in a function, so its top-level var is local and this is module.exports.