Frontend Development 10 min read

Understanding ESModule Loading and Execution Process

ESModules load by fetching and parsing files to build a static dependency graph, then instantiate bindings before evaluating each module depth‑first, which ensures deterministic execution, enables concurrent loading and TreeShaking, and explains why circular imports can cause temporal‑dead‑zone ReferenceErrors when exported variables are accessed before initialization.

DaTaobao Tech
DaTaobao Tech
DaTaobao Tech
Understanding ESModule Loading and Execution Process

ESModule is the standard module system for JavaScript and is widely used in modern front‑end development. This article explains the underlying mechanisms that guarantee deterministic behavior and performance, especially when dealing with circular dependencies and TreeShaking.

Example of a circular dependency

The following three files illustrate how a circular import works and why a runtime error can occur when a variable has not been initialized.

// main.mjs
import { mod1Fn } from './mod1.mjs'
import { mod2Fn } from './mod2.mjs'

mod1Fn('main')
mod2Fn('main')

// mod1.mjs
import './mod2.mjs'

export let mod1Value = 'mod1Value'

export function mod1Fn(from) {
  console.log(`${from} call mod1Fn\n`)
}

// mod2.mjs
import { mod1Fn, mod1Value } from './mod1.mjs'

export function mod2Fn(from) {
  console.log(`${from} call mod1Fn\n`)
  console.log('log mod1Value in mod2Fn')
  console.log(mod1Value)
}

mod1Fn('mod2')
mod2Fn('mod2')

Running the code with node main.mjs shows that the circular import itself is allowed, but adding an extra call to mod2Fn('mod2') inside mod2.mjs triggers a ReferenceError because mod1Value has not been initialized yet (temporal dead zone).

ESModule loading phases

Fetch & Parse: the runtime reads each module file, records its source text, and extracts static import/export statements to build a dependency graph.

Instantiate: variable declarations and the environment record for each module are created, establishing bindings (imports become indirect references to the exported bindings of the source module).

Evaluate: modules are executed depth‑first, each module’s code runs exactly once.

This three‑step process explains why the order of import statements does not affect execution and why a variable may be accessed before its initialization.

Advantages over synchronous loading

Static analysis enables browsers and Node.js to request module files concurrently, reducing network latency.

Because imports are resolved before execution, the engine can perform “streaming” loads similar to HTML streaming.

During instantiation the engine knows which exports are never used, allowing TreeShaking to safely drop dead code.

Understanding these mechanisms helps developers write more reliable code, leverage automatic optimizations such as TreeShaking, and anticipate pitfalls like temporal dead zone errors in circular dependencies.

Front-endjavascriptmodule loadingESModuletree-shakingCircular Dependency
DaTaobao Tech
Written by

DaTaobao Tech

Official account of DaTaobao Technology

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.