Skip to content

Module cycles — when two modules import each other

Level: 301 · for anyone whose two modules import each other

Stub — an outline, not a lesson. There is no runnable example behind this page yet, so nothing on it has been through the check that backs every other claim in this library. The questions below are what the finished page has to answer.

One line: In a cycle one module runs before the other has finished: an ES module reading the other's const at top level throws ReferenceError (the binding is still in its temporal dead zone), while CommonJS hands over a half-filled exports and reads undefined.

Keywords: circular import, ERR_REQUIRE_CYCLE_MODULE

What the finished page will answer

  • When a.mjs and b.mjs import each other and a.mjs is the entry point, which body runs first?
  • Why does reading the other module's const throw ReferenceError, while calling its exported function declaration works?
  • What does CommonJS hand over in the same cycle, and what warning does Node print about it?
  • What does require() throw when an ES module loads a CommonJS file that requires the ES module back?
  • Which fixes break a cycle: moving the shared value to a third module, or reading it inside a function called later?

Examples it will need

  • module_cycles_esm_and_cjs_sh.sh — the same two-module cycle in ESM (ReferenceError) and in CommonJS (undefined, plus Node's warning with the PID removed)
  • module_cycles_hoisted_functions_sh.sh — an ESM cycle whose modules call each other's exported function declarations, which works
  • module_cycles_require_cycle_sh.sh — an ES module that requires a .cjs file that requires it back, and the ERR_REQUIRE_CYCLE_MODULE code

See also

Sources to start from