CommonJS modules in nodejs
basic · Node.js — Server-side JavaScript
In Node.js, how modules are loaded and managed involves two important internal behaviors: Caching (to improve performance) and Cycles (to handle files that depend on each other). 1. Module Caching Every module is cached after the first time it is loaded. This means if you require() or import the same file multiple times across your application, the code inside that file only executes once . How it Works The first call to require('./myModule') executes the code and stores the result in require.cache . Subsequent calls return the exact same exported object from memory. // counter.js console.log("Module initialized!"); module.exports = { count: 0 }; // app.js const instance1 = require('./counter'); // Prints: "Module initialized!" const instance2 = require('./counter'); // Prints nothing (returns cached version) instance1.count = 10; console.log(instance2.count); // Output: 10 (it's the same object) 2. Circular Dependencies (Cycles) A cycle occurs when Module A requires Module B, and Module B requires Module A. Node.js handles this to prevent infinite loops, but it can lead to "unfinished" objects if not handled carefully. CommonJS Behavior In CommonJS, if a cycle occurs, a module might receive an incomplete version of the other module’s exports. a.js Simplified Code Example Let’s use names instead of "done" booleans to make it clearer. fileA.js exports.name = "I am File A"; const fileB = require('./fileB.js'); console.log("File A checked File B and saw:", fileB.name); fileB.js exports.name = "I am File B"; const fileA = require('./fileA.js'); // This happens while fileA is still stuck on line 3! console.log("File B checked File A and saw:", fileA.name); The Step-by-Step Execution Run node fileA.js: Node marks fileA as "started." fileA exports its name: "I am File A". fileA hits the require: It stops and goes into fileB.js. Inside fileB.js: It exports its name: "I am File B". It hits require('./fileA.js'). The "Cache" Moment: Node sees fileA is already being processed. Instead of starting fileA over again, it gives fileB whatever fileA has exported so far. The Output: fileB prints: "File B checked File A and saw: I am File A". (This works because fileA exported its name before the require call). fileB finishes. Back in fileA: It finally gets the result from fileB. fileA prints: "File A checked File B and saw: I am File B". Why is this dangerous? If fileA.js had waited to export its name after the require call, fileB would have seen undefined . The Rule of Thumb: If you have a circular dependency, always make sure your exports are defined at the very top of the file, before any require calls. If you don't, the other file will try to use a variable that doesn't exist yet.