6

Node.jsでこの「ホットコードプッシュ」を理解しようとしています。基本的に、私のメイン ファイル (入力すると実行されるnode app.js) は、いくつかの設定、構成、および初期化で構成されます。そのファイルには、chokidar を使用するファイル ウォッチャーがあります。I ファイルが追加されたら、I は単純requireにファイルです。ファイルが変更または更新された場合、キャッシュを削除してdelete require.cache[path]から再度要求します。これらのモジュールはすべて何もエクスポートせず、単一のグローバルStormオブジェクトで動作するだけです。

Storm.watch = function() {
    var chokidar, directories, self = this;
    chokidar = require('chokidar');
    directories = ['server/', 'app/server', 'app/server/config', 'public'];
    clientPath = new RegExp(_.regexpEscape(path.join('app', 'client')));
    watcher = chokidar.watch(directories, {
    ignored: function(_path) {
        if (_path.match(/\./)) {
            !_path.match(/\.(js|coffee|iced|styl)$/);
        } else {
            !_path.match(/(app|config|public)/);
        }
    },
    persistent: true
    });


    watcher.on('add', function(_path){
    self.fileCreated(path.resolve(Storm.root, _path));
    //Storm.logger.log(Storm.cliColor.green("File Added: ", _path));
    //_console.info("File Updated");
    console.log(Storm.css.compile('     {name}: {file}', "" +
        "name" +
        "{" +
        "color: white;" +
        "font-weight:bold;" +
        "}" +
        "hr {"  +
        "background: grey" +
        "}")({name: "File Added", file: _path.replace(Storm.root, ""), hr: "=================================================="}));
    });

    watcher.on('change', function(_path){
    _path = path.resolve(Storm.root, _path);
    if (fs.existsSync(_path)) {
        if (_path.match(/\.styl$/)) {
            self.clientFileUpdated(_path);
        } else {
            self.fileUpdated(_path);
        }
    } else {
        self.fileDeleted(_path);
    }
    //Storm.logger.log(Storm.cliColor.green("File Changed: ", _path));
    console.log(Storm.css.compile('     {name}: {file}', "" +
        "name" +
        "{" +
        "color: yellow;" +
        "font-weight:bold;" +
        "}" +
        "hr {"  +
        "background: grey" +
        "}")({name: "File Changed", file: _path.replace(Storm.root, ""), hr: "=================================================="}));
    });

    watcher.on('unlink', function(_path){
    self.fileDeleted(path.resolve(Storm.root, _path));
    //Storm.logger.log(Storm.cliColor.green("File Deleted: ", _path));
    console.log(Storm.css.compile('     {name}: {file}', "" +
        "name" +
        "{" +
        "color: red;" +
        "font-weight:bold;" +
        "}" +
        "hr {"  +
        "background: grey" +
        "}")({name: "File Deleted", file: _path.replace(Storm.root, ""), hr: "=================================================="}));
    });

    watcher.on('error', function(error){
    console.log(error);
    });


};


Storm.watch.prototype.fileCreated = function(_path) {

    if (_path.match('views')) {
    return;
    }

    try {
    require.resolve(_path);
    } catch (error) {
    require(_path);
    }

};


Storm.watch.prototype.fileDeleted = function(_path) {
    delete require.cache[require.resolve(_path)];
};

Storm.watch.prototype.fileUpdated = function(_path) {
    var self = this;
    pattern = function(string) {
    return new RegExp(_.regexpEscape(string));
    };

    if (_path.match(pattern(path.join('app', 'templates')))) {
    Storm.View.cache = {};
    } else if (_path.match(pattern(path.join('app', 'helpers')))) {
    self.reloadPath(path, function(){
        self.reloadPaths(path.join(Storm.root, 'app', 'controllers'));
    });
    } else if (_path.match(pattern(path.join('config', 'assets.coffee')))) {
    self.reloadPath(_path, function(error, config) {
        //Storm.config.assets = config || {};
    });
    } else if (_path.match(/app\/server\/(models|controllers)\/.+\.(?:coffee|js|iced)/)) {
    var isController, directory, klassName, klass;

    self.reloadPath(_path, function(error, config) {
        if (error) {
            throw new Error(error);
        }
    });

    Storm.serverRefresh();

    isController = RegExp.$1 == 'controllers';
    directory    = 'app/' + RegExp.$1;
    klassName = _path.split('/');
    klassName = klassName[klassName.length - 1];
    klassName = klassName.split('.');
    klassName.pop();
    klassName = klassName.join('.');
    klassName = _.camelize(klassName);

    if (!klass) {
        require(_path);
    } else {
        console.log(_path);
        self.reloadPath(_path)
    }

    } else if (_path.match(/config\/routes\.(?:coffee|js|iced)/)) {
    self.reloadPath(_path);
    } else {
    this.reloadPath(_path);
    }

};

Storm.watch.prototype.reloadPath = function(_path, cb) {

    _path = require.resolve(path.resolve(Storm.root, path.relative(Storm.root, _path)));
    delete require.cache[_path];
    delete require.cache[path.resolve(path.join(Storm.root, "server", "application", "server.js"))];
    //console.log(require.cache[path.resolve(path.join(Storm.root, "server", "application", "server.js"))]);
    require("./server.js");

    Storm.App.use(Storm.router);

    process.nextTick(function(){
    Storm.serverRefresh();
    var result = require(_path);
    if (cb) {
        cb(null, result);
    }
    });
};


Storm.watch.prototype.reloadPaths = function(directory, cb) {



};

さまざまな方法を試しているため、一部のコードは不完全/使用されていません。

機能しているもの:

次のようなコードの場合:

function run() {
   console.log(123);
}

完璧に動作します。ただし、非同期コードは更新に失敗します。

問題 = 非同期コード

app.get('/', function(req, res){
   // code here..
});

その後、nodejs プロセスの実行中にファイルを更新すると、何も起こりませんが、ファイル ウォッチャーを通過し、キャッシュが削除されてから再確立されます。それが機能しない別の例は次のとおりです。

// middleware.js
function hello(req, res, next) {
  // code here...
}

// another file:
app.use(hello);

app.use はまだそのメソッドの古いバージョンを使用しているためです。

質問:

どうすれば問題を解決できますか? 足りないものはありますか?

永遠にサードパーティのモジュールを使用するよう提案しないでください。単一のインスタンス内に機能を組み込むことを試みています。

編集:

meteors のコードベース (node.js またはブラウザーの「ホット コード プッシュ」に関するリソースは驚くほど少ない) を研究し、独自の実装をいじくり回した後、実用的なソリューションを作成することに成功しました。https://github.com/TheHydroImpulse/Refresh.js . これはまだ開発の初期段階ですが、今のところしっかりしているようです。完成させるために、ブラウザ ソリューションも実装します。

4

2 に答える 2

3

のキャッシュを削除requireしても、実際には古いコードが「アンロード」されたり、そのコードが行ったことを元に戻したりすることはありません。

たとえば、次の関数を考えてみましょう。

var callbacks=[];
registerCallback = function(cb) {
    callbacks.push(cb);
};

ここで、そのグローバル関数を呼び出すモジュールがあるとします。

registerCallback(function() { console.log('foo'); });

アプリの起動後、callbacks1つのアイテムがあります。次に、モジュールを変更します。

registerCallback(function() { console.log('bar'); });

「ホットパッチ」コードが実行され、 require.cachedバージョンが削除され、モジュールが再ロードされます。

あなたが認識しなければならないのは、今でcallbacks2つのアイテムがあるということです。まず、fooをログに記録する関数(アプリの起動時に追加された)の参照と、barをログに記録する関数(追加されたばかり)への参照があります。

モジュールのキャッシュされた参照を削除したとしても、exports実際にモジュールを削除することはできません。 JavaScriptランタイムに関する限り、多くの参照から1つの参照を削除しただけです。アプリケーションの他の部分は、まだ古いモジュール内の何かへの参照にぶら下がっている可能性があります。

これはまさにHTTPアプリで起こっていることです。アプリが最初に起動すると、モジュールは匿名のコールバックをルートにアタッチします。これらのモジュールを変更すると、同じルートに新しいコールバックがアタッチされます。古いコールバックは削除されません。Expressを使用していると思いますが、Expressは、追加された順序でルートハンドラーを呼び出します。したがって、新しいコールバックを実行する機会はありません。


正直なところ、変更時にアプリをリロードするためにこのアプローチを使用することはありません。ほとんどの人は、クリーンな環境を前提としてアプリ初期化コードを記述します。ダーティな環境、つまり、すでに稼働している環境で初期化コードを実行することにより、その仮定に違反しています。

初期化コードを実行できるように環境をクリーンアップしようとすると、ほぼ間違いなく、その価値よりも厄介です。基になるファイルが変更されたら、アプリ全体を再起動するだけです。

于 2012-11-23T05:19:01.190 に答える
2

Meteor は、モジュールがホット コード プッシュ プロセスの一部として自身を「登録」できるようにすることで、この問題を解決します。

彼らはこれをreloadパッケージに実装しています:

https://github.com/meteor/meteor/blob/master/packages/reload/reload.js#L105-L109

Meteor.reloadAPI が GitHub の一部のプラグインで使用されているのを見てきましたが、sessionパッケージでも使用されています。

https://github.com/meteor/meteor/blob/master/packages/session/session.js#L103-L115

if (Meteor._reload) {
  Meteor._reload.onMigrate('session', function () {
    return [true, {keys: Session.keys}];
  });

  (function () {
    var migrationData = Meteor._reload.migrationData('session');
    if (migrationData && migrationData.keys) {
      Session.keys = migrationData.keys;
    }
  })();
}

基本的に、ページ/ウィンドウが読み込まれると、流星は「移行」を実行し、データ/メソッド/などを定義するのはパッケージ次第です。ホットコードプッシュが行われたときに再計算されます。

livedata パッケージ(検索)でも使用されていますreload

更新の合間に、を使用して「状態」を保存していますwindow.sessionStorage

于 2012-11-24T15:19:15.603 に答える