coffee require() statements causing circular references?

I've got a weird problem with Coffeescript not properly caching/finishing loading require() statements that are being referenced in a circular fashion.

When I run this code with node main.js...

main.js

module.exports = {
    name: 'John'
}

var config = require('./config');
config.hello();

config.js

var main = require('./main');

module.exports = {
    hello: function() {
        console.log("Hello " + main.name);
    }
}

... I get the following output:

Hello John

However, when I run the equivalent coffeescript code with coffee main.coffee...

main.coffee

module.exports =
    name: 'John'

config = require './config'
config.hello()

config.coffee

main = require './main'

module.exports =
    hello: ->
        console.log "hello #{main.name}"

... I get this:

TypeError: Object # has no method 'hello'

When I compile the code to plain ol' Javascript and run via node, it's fine.

What's going on with coffee?

My theory is this behavior is a combination of how the node.js require cache works, how node handles cyclical dependencies, and something about how the coffee transpiler works when loading .coffee files straight into node.

I made distinct coffeescript and javascript versions of the program with illustrative logging as follows. I used "maincs" and "mainjs" to make sure there was no mixing of the two languages.

mainjs.js

console.log("mainjs starting");
console.log("mainjs exporting name");
module.exports = {
  name: 'John'
};
console.log("mainjs requiring configjs");

var configjs = require('./configjs');

console.log("mainjs calling configjs.hello()");

configjs.hello();

configjs.js

console.log("configjs starting");
console.log("configjs requiring mainjs");
var mainjs = require("./mainjs");
console.log("configjs exporting hello");
module.exports = {
  hello: function() {
    return console.log("hello " + mainjs.name);
  }
};

maincs.coffee

console.log "maincs starting"
console.log "maincs exporting name"
module.exports =
  name: 'John'

console.log "maincs requiring configcs"
configcs = require './configcs'
console.log "maincs calling configcs.hello()"
configcs.hello()

configcs.coffee

console.log "configcs starting"
console.log "configcs requiring maincs"
maincs = require "./maincs"
console.log "configcs exporting hello"
module.exports =
  hello: ->
    console.log "hello #{maincs.name}"

So when we run them, we get different output (as you have seen). I've highlighted the interesting bit below.

node mainjs.js
mainjs starting
mainjs exporting name
mainjs requiring configjs
configjs starting
configjs requiring mainjs   #<--- Note the top-level mainjs.js code does not re-execute
configjs exporting hello
mainjs calling configjs.hello()
hello John

coffee maincs.coffee 
maincs starting
maincs exporting name
maincs requiring configcs
configcs starting
configcs requiring maincs
maincs starting      # <-- Look, the top-level maincs.coffee code is re-executing
maincs exporting name
maincs requiring configcs
maincs calling configcs.hello()
TypeError: Object #<Object> has no method 'hello'

So I think this behavior has to do with how the node.js require system module cache works and the transpile-on-the-fly coffeescript interpreter. Basically, if the maincs = require "maincs" causes a re-executing of the top-level code in the maincs module, we hit a circular dependency situation where node is going to provide maincs an unfinished copy of the configcs exports object.

Please read the node.js documentation on cyclic dependencies that explains this behavior (at least partially).

Now, the interesting bit is that if you make sure that the hello function is exported before you require maincs, you can get away with this partially. However, referencing main.name from within the hello code won't work because main will be undefined when that first executes.

maincs2.coffee

console.log "maincs2 starting"
console.log "maincs2 exporting name"
module.exports =
  name: 'John'

console.log "maincs2 requiring configcs2"
configcs2 = require './configcs2'
console.log "maincs2 calling configcs2.hello()"
configcs2.hello()

configcs2.coffee

console.log "configcs2 starting"
console.log "configcs2 exporting hello"
module.exports =
  hello: ->
    console.log "hello from configcs"
console.log "configcs2 requiring maincs2"
maincs2 = require "./maincs2"

coffee maincs2.coffee
maincs2 starting
maincs2 exporting name
maincs2 requiring configcs2
configcs2 starting
configcs2 exporting hello
configcs2 requiring maincs2
maincs2 starting
maincs2 exporting name
maincs2 requiring configcs2
maincs2 calling configcs2.hello()
hello from configcs
maincs2 calling configcs2.hello()
hello from configcs

So basically, this behavior is the combination of both how modules are cached by require, how that interacts with cyclical dependencies, and some aspect of the coffee transpiler itself. Note that transpiling the .coffee to .js and executing with node avoids this problem both in regular coffeescript with the IIFE wrapper and bare coffeescript with no wrapper, so the IIFE wrapper doesn't seem to matter (node basically adds its own anyway).