ES modules in JavaScript: the language's official module system

An ES module is a file that keeps its variables to itself and publishes what it shares. Here are its rules, how it differs from a script, and how to enable it.
4 min read
Believemy logo

Two JavaScript files dropped into the same page share everything: each one sees the other's variables, and a total declared in the first quietly overwrites the one in the second. That missing boundary was for years the number one source of breakage in any sizable project.

ES modules fixed it by giving every file its own scope, plus an official way to state what it agrees to share.


Definition

An ES module is a JavaScript file whose values stay private until it publishes them with export, and that other files pick up with import. It is the language's official module system, understood by browsers and by Node.js alike.

JAVASCRIPT
// counter.js
export let views = 0;

export function add() {
  views += 1;
}

// app.js
import { views, add } from './counter.js';

console.log(views); // 0
add();
console.log(views); // 1

The second read prints 1, which often comes as a surprise: an import is not a copy of the value, it is a permanent link to the original variable.

What an ES module resolves before running, and what waits for run timeTwo timelines on the same scale. On top, static imports: the four files are resolved in a single wait, before the first line runs. Below, four awaited import() calls: each file waits for its own line to run, and the lower timeline is four times as long as the upper one.import … fromresolved before any code runsthe engine reads every import before the first linecart.jsapi.jsui.jsutils.js1 wait, all four fetched togetherimport(…)resolved while the code runseach awaited import() waits for its line to runcart.jsapi.jsui.jsutils.js4 waits, one fetch per lineThe same graph: one wait instead of four


What separates a module from a plain script

  • A scope of its own: anything not exported stays invisible from the outside, with no need to wrap the file in a function.
  • Strict mode, always: there is no point writing "use strict", a module applies it by default.
  • Analysis before execution: the engine reads every import and export before running a single line, which lets it fetch dependencies ahead of time.
  • One evaluation only: a module imported by ten files runs once, and all ten receive the very same copy.
  • Deferred loading: in the browser, a module waits for the page to be parsed, exactly as the defer attribute would.
Good to know

At the top level of a module, this is undefined rather than the global object. Older code that relied on that value to install a global variable therefore stops working the moment the file becomes a module.


Turning modules on in a project

In a web page, the attribute is enough: <script type="module" src="app.js"></script>. That file and everything it imports are then treated as modules.

On the server, two routes reach the same result: declaring "type": "module" in the package.json file, which covers every .js in the project, or naming the file with the .mjs extension, which forces module treatment whatever the rest of the project says.


Frequently asked questions

Question

Why does my module fail when I open the HTML file by hand?

Because a double click opens the page under the file:// address, and modules follow the security rules of network loading, which reject that protocol. Serve the folder through a real server, even the one bundled with a development tool, and the error disappears.


Question

Can ES modules and CommonJS be mixed?

An ES module can import a CommonJS file, with a few limits around named exports. The other direction is trickier: a CommonJS file cannot always require an ES module synchronously, and the universal answer remains import() (dynamic import), which works both ways.


Question

Do I need a build tool to use ES modules?

No: every current browser understands them natively, and a small application can skip tooling entirely. A Bundler becomes worthwhile once the file count climbs, since each import triggers a network request, or when you want Tree shaking and Minification on top.

Related terms

Discover our javaScript glossary

Every word of JavaScript explained simply: keywords, built-in objects, methods, errors and concepts. Clear definitions and examples that actually run, to learn and to troubleshoot.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.