Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Requiring Interdependent Node.js Modules

It's nice to be able to use functions by name within a module instead of the lengthy module.exports.name. It's also nice to have a single place to look and see everything to be exported. Typically, the solution I've seen is to define functions and variables normally and then set module.exports to an object containing the desired properties at the end. This works in most cases. Where it breaks down is when two modules are inter-dependent and require each other. Setting the exports at the end leads to unexpected results. To get around this problem, simply assign module.exports at the top, before requiring the other module. The following examples of bottom and top assignment will make things clearer.

About module.exports

The module.exports is a special property allowing modules to share components with the world outside. Whatever is assigned to module.exports will be the return value when the module is loaded with require elsewhere. It is initialized with an empty object {} which can then be given properties module.exports.foo = 'bar' or overwritten module.exports = 'bar' depending on the module's function. Once a module is loaded, the module is cached and that cache is used whenever the module is requested again.

Bottom Assignment of module.exports (bad)

Walking through an example where module.exports is assigned at the bottom should make things clearer.

A_bottom.js:

A_bottom.js

B_bottom.js:

B_bottom.js

test_bottom.js:

test_bottom.js

Output:

output_bottom

The first thing to happen in test_bottom.js is requiring the module A_top.js. A_top's module gets cached, with the empty object in the exports property. When A_top.js requires B_top.js, parsing of A_top stops and parsing of B_top begins. Similar initializations and caching happens for B_top. When B_top requires A_top, it gets the cached module.exports object. Since objects in Javascript are passed by reference, any later assignments of properties to A_top's module.exports object will be available in B_top. If, however, module.exports was assigned a new object later in A_top, it will no longer be shared by B_top, which has a reference to the original. This prevents B_top from using any of A_top's new exported properties, as seen in the above output.

Top Assignment of module.exports Example (good)

To get around this problem, simply do the assignment first, before requiring the other module.

A_top.js:

A_top.js

B_top.js:

B_top.js

test_top.js:

test_top.js

Output:

output_top

Since the object stored in module.exports is not changed after any require calls, the reference remains in sync between the inter-dependent modules. There is one caveat…

Caveat for all inter-dependent modules

While the top assignment of module.exports solves the reference sync issue, keep in mind that any export in A dependent on an export of B will fail if immediately called within B. This is because the require('B') in A has not yet returned B's exports. Once both are finished parsing (e.g. back in the main calling script) everything will work as expected.

console.log('A_bottom.js: LOADING');
var B = require('./B_bottom.js');
function fooA(){
console.log('A.fooA -> B:',B);
};
module.exports = {
fooA: fooA
};
console.log('A_bottom.js: B:', B);
console.log('A_bottom.js: LOADED');
console.log('A_top.js: LOADING');
module.exports = {
fooA: fooA
};
var B = require('./B_top.js');
function fooA(){
console.log('A.fooA -> B:',B);
};
console.log('A_top.js: B:', B);
console.log('A_top.js: LOADED');
console.log('B_bottom.js: LOADING');
var A = require('./A_bottom.js');
function fooB(){
console.log('B.fooB -> A:',A);
}
module.exports = {
fooB: fooB
};
console.log('B_bottom.js: A:', A);
console.log('B_bottom.js: LOADED');
console.log('B_top.js: LOADING');
module.exports = {
fooB: fooB
};
var A = require('./A_top.js');
function fooB(){
console.log('B.fooB -> A:',A);
}
console.log('B_top.js: A:', A);
console.log('\nCAVEAT: B_top.js calling A.fooA():');
A.fooA();
console.log('\nB_top.js: LOADED');
test_bottom.js: REQUIRING A_bottom.js
A_bottom.js: LOADING
B_bottom.js: LOADING
B_bottom.js: A: {}
B_bottom.js: LOADED
A_bottom.js: B: { fooB: [Function: fooB] }
A_bottom.js: LOADED
test_bottom.js: REQUIERD A_bottom.js
test_bottom.js: REQUIRING B_bottom.js
test_bottom.js: REQUIRED B_bottom.js
Final Status:
test_bottom.js: A: { fooA: [Function: fooA] }
test_bottom.js: B: { fooB: [Function: fooB] }
A.fooA -> B: { fooB: [Function: fooB] }
B.fooB -> A: {}
test_top.js: REQUIRING A_top.js
A_top.js: LOADING
B_top.js: LOADING
B_top.js: A: { fooA: [Function: fooA] }
CAVEAT: B_top.js calling A.fooA():
A.fooA -> B: undefined
B_top.js: LOADED
A_top.js: B: { fooB: [Function: fooB] }
A_top.js: LOADED
test_top.js: REQUIERD A_top.js
test_top.js: REQUIRING B_top.js
test_top.js: REQUIRED B_top.js
Final Status:
test_top.js: A: { fooA: [Function: fooA] }
test_top.js: B: { fooB: [Function: fooB] }
A.fooA -> B: { fooB: [Function: fooB] }
B.fooB -> A: { fooA: [Function: fooA] }
console.log('test_bottom.js: REQUIRING A_bottom.js');
var A = require('./A_bottom.js');
console.log('test_bottom.js: REQUIERD A_bottom.js');
console.log('test_bottom.js: REQUIRING B_bottom.js');
var B = require('./B_bottom.js');
console.log('test_bottom.js: REQUIRED B_bottom.js');
console.log("\nFinal Status:");
console.log('test_bottom.js: A:',A);
console.log('test_bottom.js: B:',B);
A.fooA();
B.fooB();
console.log('test_top.js: REQUIRING A_top.js');
var A = require('./A_top.js');
console.log('test_top.js: REQUIERD A_top.js');
console.log('test_top.js: REQUIRING B_top.js');
var B = require('./B_top.js');
console.log('test_top.js: REQUIRED B_top.js');
console.log("\nFinal Status:");
console.log('test_top.js: A:',A);
console.log('test_top.js: B:',B);
A.fooA();
B.fooB();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment