咖啡require()语句导致循环引用?

我有一个奇怪的问题与Coffeescript没有正确caching/完成加载require()语句被循环引用的方式。

当我运行这个代码与节点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); } } 

…我得到以下输出:

你好约翰

然而,当我运行咖啡main.coffee等效coffeescript代码…

main.coffee

 module.exports = name: 'John' config = require './config' config.hello() 

config.coffee

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

…我得到这个:

TypeError:Object#没有方法'hello'

当我将代码编译成普通的“Javascript”并通过节点运行时,没问题。

咖啡怎么了?

我的理论是,这种行为是node.js如何cachingrequire的结合,节点如何处理循环依赖,以及咖啡转换器如何将.coffee文件直接加载到节点时的工作方式。

我使用说明性日志logging来制作截然不同的coffeescript和javascript版本的程序,如下所示。 我用“maincs”和“mainjs”来确保这两种语言没有混合。

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}" 

所以当我们运行它们时,我们会得到不同的输出(如你所见)。 我已经突出了下面的有趣的一点。

 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' 

所以我认为这个行为与node.js需要系统模块caching的工作方式以及飞行中的coffeescript解释器有关。 基本上,如果maincs = require "maincs"导致在maincs模块中重新执行顶级代码,我们会遇到一个循环依赖的情况,那就是节点将为maincs提供configcs exports对象的未完成副本。

请阅读关于循环依赖的node.js文档,以解释这种行为 (至less部分)。

现在,有趣的是,如果你确定你需要maincs 之前导出了hello函数,你可以部分解决这个问题。 但是,从hello代码中引用main.name将不起作用,因为在第一次执行时,main将是未定义的。

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 

所以基本上,这个行为是组合模块如何被caching,如何与周期性依赖相互作用,以及咖啡转发器本身的某些方面。 请注意,将.coffee转换为.js并使用节点执行,可以避免在使用IIFE包装器的普通咖啡文本和无包装的纯咖啡文件中出现此问题,因此IIFE包装器似乎并不重要(节点基本上自行添加)。