4

我遇到了一个奇怪的问题,Coffeescript 没有正确缓存/完成加载循环引用的 require() 语句。

当我使用节点 main.js运行此代码时...

main.js

module.exports = {
    name: 'John'
}

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

配置.js

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

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

...我得到以下输出:

你好约翰

但是,当我使用咖啡 main.coffee运行等效的咖啡脚本代码时...

主咖啡

module.exports =
    name: 'John'

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

配置.coffee

main = require './main'

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

...我明白了:

TypeError: Object # has no method 'hello'

当我将代码编译为普通的 Javascript 并通过节点运行时,这很好。

咖啡怎么了?

4

1 回答 1

4

我的理论是这种行为是 node.jsrequire缓存如何工作、node 如何处理循环依赖关系以及咖啡转译器在将.coffee文件直接加载到 node 时如何工作的组合。

我制作了不同的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 要求系统模块缓存的工作方式和 transpile-on-the-fly coffeescript 解释器有关。基本上,如果maincs = require "maincs"导致重新执行模块中的顶级代码maincs,我们会遇到循环依赖情况,节点将提供导出对象maincs的未完成副本。configcs

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

现在,有趣的是,如果您确保在require之前hello导出了该函数,则可以部分解决。但是,从代码中引用将不起作用,因为 main 在第一次执行时将是未定义的。maincsmain.namehello

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

所以基本上,这种行为是模块如何被缓存的组合,它如何require与循环依赖交互,以及咖啡转译器本身的某些方面。请注意,将 .coffee 转换为 .js 并使用 node 执行可以避免在具有 IIFE 包装器的常规咖啡脚本和没有包装器的裸咖啡脚本中出现此问题,因此 IIFE 包装器似乎无关紧要(无论如何,节点基本上都会添加自己的)。

于 2013-01-10T16:22:58.160 回答