Compile Ready
Module 10 · ES6+ Features

ES Modules

Advanced11m read6m practice17m total
ES ModulesESMBundlersTree ShakingCommonJS

Introduction

ES Modules are JavaScript's standard module system. They use static import and export declarations so tools can understand dependencies before code runs.

This static structure enables strong editor tooling, bundler optimization, tree-shaking, circular-dependency handling through live bindings, and a cleaner alternative to older CommonJS require patterns.

Why This Matters

Production JavaScript is shipped as modules. Staff-level engineers must explain how module graphs load, why imports are live read-only bindings, why static imports help bundlers, and how ESM differs from CommonJS in Node and browser environments.

Theory

Static structure

ESM imports and exports are declarations, not ordinary runtime function calls. They must appear at the top level of a module, and their specifiers are string literals. This lets parsers and bundlers build a module graph without executing the program.

Module loading phases

Conceptually, ESM has three phases:

  1. Parse and link — discover imports/exports and connect bindings across modules.
  2. Instantiate — create the module environment records.
  3. Evaluate — run module code in dependency order.

Live bindings

Named exports are live bindings. If a module exports let count, importers observe updates to count; they do not receive a frozen copy. Importers cannot reassign imported bindings, but they can see the exporter update them.

Tree-shaking

Because imports and exports are static, bundlers can remove unused exports when modules are side-effect safe. This is much harder with dynamic CommonJS patterns.

ESM vs CommonJS

CommonJS uses require() and module.exports, traditionally evaluated dynamically at runtime. ESM uses import and export, has live bindings, is always strict mode, and supports asynchronous loading semantics in environments that need it.

Visual Diagrams

Static module graph
app.js
  |
  +--> user-service.js
  |       |
  |       +--> http-client.js
  |
  +--> format-date.js

Parser can discover this graph before running app.js

Static imports let tools link, analyze, and optimize the dependency graph ahead of evaluation.

Code Examples

Named exports and live bindings

This read-only example shows standard module syntax. Do not paste it into the sandbox as one file; each comment represents a separate module file.

Loading…

ESM contrasted with CommonJS

Static ESM is easier for tools to analyze than dynamic CommonJS require patterns.

Loading…

Playground

Loading editor…
Console

Press Run to execute the code and see output here.

Output Prediction

Predict the output #1

javascript
1const moduleScope = { count: 0 };
2const namespace = {};
3
4Object.defineProperty(namespace, 'count', {
5 get: function () {
6 return moduleScope.count;
7 }
8});
9
10const snapshot = namespace.count;
11moduleScope.count += 1;
12
13console.log(snapshot);
14console.log(namespace.count);

Coding Exercises

Find unused exports from static metadata

Medium

A bundler has collected static metadata. Implement unusedExports(exports, used) so it returns export names that are not used by any importer.

Interview Questions

1Why are ES Modules easier to tree-shake than CommonJS?

ESM uses static top-level import and export declarations, so bundlers can build the dependency graph and determine which bindings are referenced before executing code. CommonJS require() can be conditional or computed at runtime, which makes safe static analysis harder.

Asked at:GoogleMetaVercel

Follow-ups

  • What can still prevent tree-shaking?
  • Why do package side effects matter?
2What does it mean that ESM imports are live bindings?

An imported binding is a read-only view of the exported binding, not a copied value. If the exporting module updates the binding, importers observe the new value. The importer itself cannot reassign the imported name.

3How does ESM differ from CommonJS?

ESM uses static import/export, live bindings, strict mode, and a linked module graph. CommonJS uses runtime require() and module.exports, often returns object snapshots, and historically loads synchronously in Node.

Quiz

1. Which ESM property enables tree-shaking?

2. What is true about an imported ESM binding?

Summary

  • ES Modules are JavaScript's standard static module system.
  • Static `import`/`export` lets tools build dependency graphs before execution.
  • ESM imports are live read-only views of exported bindings.
  • Tree-shaking depends on static structure and side-effect-safe modules.
  • CommonJS is runtime-oriented; ESM is statically linked.

Cheat Sheet

Named export: export function fn() {} and import { fn } from './mod.js'.

Default export: one primary value per module: export default value.

Live binding: importer sees exporter updates but cannot reassign imports.

Tree-shaking: static imports/exports plus side-effect information.

CommonJS: require() / module.exports; more dynamic, harder to analyze.