← All concepts
Modern JavaScript

ES Modules

Each file is its own module with its own scope, and export/import wire live values between files, not copies.

Loading visual…

Definition

An ES module is just a JavaScript file that uses export to expose bindings and import to read bindings exposed by other files. Each module has its own top level scope, so a variable declared in one file is not visible in another unless it is explicitly exported. A module can export multiple named values with export, and at most one default export with export default, which is meant to be the file's main thing and is imported without curly braces. An import is a live, read only binding to the exporting module's variable, not a copied snapshot taken at import time. If the exporting module later reassigns an exported let, every module that imported it sees the updated value the next time it reads that binding, because they are all looking at the same underlying variable. Modules are also singletons: no matter how many files import a given module, its code runs once, the first time it is loaded, and every importer shares that same single instance. On top of that, module code always runs in strict mode automatically, even without a "use strict" directive.

Examples

// math.js
export const PI = 3.14159;
export function double(n) {
  return n * 2;
}

// app.js
import { PI, double } from "./math.js";
double(PI); // 6.28318

math.js exposes two named exports. app.js imports them by name inside curly braces, matching the names they were exported with.

// logger.js
export default function log(msg) {
  console.log(msg);
}

// app.js
import log from "./logger.js";
log("ready"); // ready

logger.js has one default export, so app.js imports it without curly braces and can name it anything on the way in.

// counter.js
export let total = 0;
export function increment() {
  total++;
}

// app.js
import { total, increment } from "./counter.js";
total; // 0
increment();
total; // 1

increment() mutates counter.js's total. Because the import is a live binding and not a copy, app.js reads the updated value of total after the call, without importing anything new.

Where you see this

Practice this

Further reading