4

Coffeescript が、循環的に参照されている require() ステートメントの読み込みを適切にキャッシュ/終了しないという奇妙な問題があります。

このコードを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);
    }
}

...次の出力が得られます。

こんにちはジョンさん

ただし、同等の coffeescript コードを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}"

...私はこれを取得します:

TypeError: オブジェクト # にはメソッド 'hello' がありません

コードをプレーンな古い Javascript にコンパイルし、ノード経由で実行すると問題ありません。

コーヒーに何が起こっているのですか?

4

1 に答える 1

4

私の理論では、この動作は、node.jsキャッシュがどのように機能するか、ノードが循環依存関係を処理する方法、およびファイルをノードに直接requireロードするときにコーヒートランスパイラーがどのように機能するかを組み合わせたものです。.coffee

私は、次のように、例示的なロギングを使用して、プログラムの個別のcoffeescriptバージョンとjavascriptバージョンを作成しました。「maincs」と「mainjs」を使用して、2つの言語が混在していないことを確認しました。

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"内のトップレベルコードの再実行を引き起こす場合、ノードがエクスポートオブジェクトの未完成のコピーを提供しようとしている循環依存の状況に遭遇します。maincsmaincsconfigcs

この動作を(少なくとも部分的に)説明している循環依存に関するnode.jsのドキュメントをお読みください。

ここで興味深いのは、必要になる前に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にトランスパイルし、ノードで実行すると、IIFEラッパーを使用する通常のコーヒースクリプトとラッパーを使用しないベアコーヒースクリプトの両方でこの問題が回避されるため、IIFEラッパーは重要ではないようです(ノードは基本的に独自のラッパーを追加します)。

于 2013-01-10T16:22:58.160 に答える