This question is about the persistance of variables across different modules in nodejs when they don't directly "require" each other, but do "require" a common ancestor.
It is also the generalised version of this stackoverflow question. Whilst I received an answer there that helped me solve my specific case, I'm still unclear as to the answer to the architectural problem I posed. I hoped that the general problem was a good fit for a programmers.stackexchange question.
Assume we have a nodejs program structured like so:
- Module Alpha – exports variable a.
- Module Beta – requires module Alpha, reads variable a -> prints it to console periodically
- Module Gamma – requires module Alpha, periodically writes to variable a
Is the instance of module Alpha, and thus variable a, common across all three modules. e.g. can module Gamma make changes to variable a that are successfully printed to the console by module Beta.
If this is true, why is this true even though there is no direct "requires" relationship between modules Gamma and Beta?
If this is not true, can you explain the best way to share a variable than needs to be modified at runtime between multiple node modules?
Can you explain the underlaying data model which is causing this behaviour in nodejs modules?
Best Answer
I think the critical piece you're missing is that the result of
require("foo")
is always the same object. Consider this REPL example:The second call to
require("http")
did not re-create thehttp
module -- it summoned up the onlyhttp
module that exists in Node's current execution environment, which had been altered on the previous line.So, in your example, there's only one Alpha module. When Beta and Gamma call
require("alpha")
, they're each getting the same reference to the one-and-only Alpha module singleton object.(Or, to be perfectly precise, they're getting the same reference to the
exports
value of the Alpha module.require
creates aModule
object, but returns only theexports
property of that module. If you don't fully understand the distinction right now, it's not a big deal.)Behind the scenes: what's really going on with
require
caching?The first time you use
require
to include a module, Node actually runs the code in that module. The resulting module is stored inrequire.cache
. Subsequent attempts to load the module withrequire
first check for an already-loaded module object inrequire.cache
. (Note: environment-native built-in Node modules likehttp
are exceptions in that they don't userequire.cache
-- you'll need to test out my code below with a custom module.)require.cache
is an object whose keys are module file path, with associated values that are module objects. For example, assume some module named "foo
" inC:\node_modules\foo.js
:The current value of the
foo
module is inrequire.cache["C:\node_modules\foo.js"].exports
. We can userequire.resolve
to get the file path of the module from the name, so we can express it also asrequire.cache[require.resolve("foo")].exports
.If we call
require("foo")
a second time, Node sees thatrequire.cache[require.resolve("foo")]
is defined, and so it returns the value ofrequire.cache[require.resolve("foo")].exports
instead of re-running the module creation code. Thisexports
property of thefoo
module inrequire.cache
is the single instance of thefoo
module export value, returned with every subsequent call torequire("foo")
.One interesting implication here is that you can
delete require.cache[require.resolve("foo")]
to force a reload of thefoo
module with the next call torequire("foo")
, because the object describing the module is removed from therequire.cache
object.So what does this mean for me?
Without
require
, you could still share values between modules using global variables. For example, your Alpha/Beta/Gamma case would work just as well with just Beta and Gamma setting and reading the global valueglobal.a
. Instead, with Node'srequire
system, you're actually setting and readingglobal.require.cache[require.resolve("alpha")].exports.a
, which Node lets you read neatly as justrequire("alpha").a
.In fact, if you just want to share a value and don't need to import your shared data as a module, you could also use a namespacing object, i.e., have Beta and Gamma set and read properties of the object
global.alpha
. The major advantage to usingrequire
is that you don't need to set uprequire("alpha")
external to the other scripts, whereas you would need to defineglobal.alpha = {}
beforerequire
ing Beta and Gamma. (Alternatively, you could conditionally defineglobal.alpha
in each module based on atypeof global.alpha == 'undefined'
check instead, to see if it's the first module to use it.)