In node.js, you need to be careful when you export functions out of a module using the module.exports =
style of export.
Try re-creating the two files a.js
and b.js
on your computer now, and then run node a
. You should see an error like
the following:
b.js:4
a()
^
TypeError: Property 'a' of object #<Object> is not a function
at module.exports (b.js:4:5)
at Object.<anonymous> (a.js:7:1)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:901:3
How does this happen? Let's walk the steps.
Node loads module a
because you specified loading it on the command-line. Internally, node creates a new empty object {}
for the exports for the object; this object will actually be the value of the exports
variable when module a
is run.
The first thing module a
does is load module b
. So, let's move to module b
.
Again, node will create a new empty object {}
for the exports for the module. The first thing module b
does is to require
module a
. Node is actually still in the process of loading module a
- it hasn't finished - but it did already create
the exports for module a
, and so it returns them. At this point, the variable a
in module b
will be set to the new
empty object {}. When a
is invoked in the exported function below this, the error will result.
You can see in module a
that it's supposed to be exporting a function. The problem is that this isn't done before
module b
does a require()
on a
, so b will end up getting access to the empty module export initially set for a
.
Ya, I've only seen this "problem" in mutually require()'d modules, and it's fixed by assigning your module.exports before doing other require()'s (or the require()'s that cause the problem anyway).
In practice, I've seen this is in some of my packages where I try doing things like "every module is a class!", etc. These things typically don't survive very long. :-) And it's can be painful to deal with superclasses this way as well, since then you need to require() the superclass early (for CoffeeScript).