2013-01-10 81 views
4

我有一個奇怪的問題與Coffeescript沒有正確緩存/完成加載require()語句以循環方式引用。coffee 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); 
    } 
} 

...我得到以下輸出:

Hello John

然而,當我運行咖啡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 # has no method 'hello'

當我將代碼編譯爲普通ol'Javascript並且通過節點運行時,它很好。

怎麼回事咖啡

+0

afaik咖啡腳本始終要在執行前進行編譯。 – Amberlamps

+0

它的確如此,但是使用npm install -g coffee-script免費獲得的'咖啡'應用產生了上述行爲 - 我試圖避免每次我想測試開發代碼更改時的編譯週期 –

回答

4

我的理論是這樣的行爲是Node.js的require緩存是如何工作的,節點如何處理週期性的依賴,以及一些關於組合如何加載.coffee文件直入節點時transpiler工作的咖啡。

我做了不同的咖啡腳本和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上即時CoffeeScript的解釋做。基本上,如果maincs = require "maincs"導致重新執行maincs模塊中的頂級代碼,那麼我們會遇到循環依賴情況,此時節點將提供未完成的configcs exports對象副本。

請閱讀node.js documentation on cyclic dependencies that explains this behavior(至少部分)。

現在,有趣的一點是,如果你確保你需要maincshello功能輸出,你可以逃脫這個部分。但是,從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 

所以基本上,這種行爲是組合兩個模塊是如何被require緩存, 怎麼樣 與週期性依賴關係相互作用,以及咖啡譯者本身的某些方面。請注意,將.coffee轉換爲.js並使用節點執行可以避免在使用IIFE包裝器的普通coffeescript和沒有包裝器的普通coffeescript中出現此問題,因此IIFE包裝器似乎並不重要(無論如何,節點基本上都會自行添加)。

+0

awesome詳細,謝謝你!除了重新設計模式或恢復爲普通JS之外,還有什麼「治療」? –

+0

通常,循環依賴是一種設計氣味。如果你有一個叫做「config」的模塊,它可能不需要主模塊。我想你可以通過檢測它何時發生並重新需要模塊來明確地在咖啡文本中編碼。儘管如此,還沒有嘗試過。 –

+0

我使用'express',並且需要訪問全局環境變量 - 理想情況下,沒有明確地將它們設置爲全局變量。這是我見過很多基於快件的應用程序使用的設計模式,儘管可能有更好的方法來實現它。 –