热代码推送NodeJS [英] Hot Code Push NodeJS

查看:100
本文介绍了热代码推送NodeJS的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直试图在Node.js上找出这个Hot Code Push。基本上,我的主文件(当您键入 node app.js 时运行)包含一些设置,配置和初始化。在那个文件中,我有一个文件观察者,使用chokidar。当我添加文件时,我只需要 require 该文件。如果文件已被更改或更新,我将删除缓存 delete require.cache [path] ,然后重新要求它。所有这些模块都不会导出任何内容,它只适用于单个全局 Storm 对象。

I've been trying to figure out this "Hot Code Push" on Node.js. Basically, my main file (that is run when you type node app.js) consists of some settings, configurations, and initializations. In that file I have a file watcher, using chokidar. When I file has been added, I simply require the file. If a file has been changed or updated I would delete the cache delete require.cache[path] and then re-require it. All these modules don't export anything, it just works with the single global Storm object.

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) {



};

有些代码不完整/未使用,因为我正在尝试很多不同的方法。

Some of the code is incomplete / not used as I'm trying a lot of different methods.

对于以下代码:

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

完美无缺。但是任何异步代码都无法更新。

Works perfectly. But any asynchronous code fails to update.

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

如果我在nodejs进程运行时更新文件,则没有任何反应,尽管它通过了文件观察器和缓存被删除,然后重新建立。另一个不起作用的例子是:

If I then update the file when the nodejs process is running, nothing happens, though it goes through the file watcher and the cache is deleted, then re-established. Another instance where it doesn't work is:

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

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

由于app.use仍将使用该方法的旧版本。

As app.use would still be using the old version of that method.

我该如何解决这个问题?有什么我想念的吗?

How could I fix the problem? Is there something I'm missing?

请不要提出建议,以便永远使用第三方模块。我正在尝试将功能合并到单个实例中。

Please don't throw suggestions to use 3rd party modules like forever. I'm trying to incorporate the functionality within the single instance.

在研究了流星代码库之后(节点中热代码推送的资源非常少)。 js或浏览器。)并且修改我自己的实现我已经成功地制定了一个有效的解决方案。 https://github.com/TheHydroImpulse/Refresh.js 。这仍处于发展的早期阶段,但现在似乎很稳固。我也将实现一个浏览器解决方案,只是为了完成。

After studying meteors codebase (there's surprisingly little resources on "Hot Code Push" in node.js or browser.) and tinkering around with my own implementation I've successfully made a working solution. https://github.com/TheHydroImpulse/Refresh.js . This is still at an early stage of development, but it seems solid right now. I'll be implementing a browser solution too, just for sake of completion.

推荐答案

删除 require 的缓存实际上并没有卸载您的旧代码,也不会撤消该代码所执行的操作。

Deleting require's cache doesn't actually "unload" your old code, nor does it undo what that code did.

例如使用以下函数:

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

现在假设您有一个调用该全局函数的模块。

Now let's say you have a module that calls that global function.

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

应用启动后,回调将有一个项目。现在我们将修改模块。

After your app starts up, callbacks will have one item. Now we'll modify the module.

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

您的热补丁代码运行,删除 require.cache d版本并重新加载模块。

Your 'hot patching' code runs, deletes the require.cached version and re-loads the module.

你必须意识到的是现在回调两个项目。首先,它引用了记录foo的函数(在app启动时添加)对日志栏(刚刚添加)的函数的引用。

What you must realize is that now callbacks has two items. First, it has a reference to the function that logs foo (which was added on app startup) and a reference to the function that logs bar (which was just added).

即使您删除了对模块的 exports 的缓存引用,也无法删除该模块。 As就JavaScript运行时而言,您只需删除许多一个引用。您的应用程序的任何其他部分仍然可以挂在旧模块中对某些内容的引用。

Even though you deleted the cached reference to the module's exports, you can't actually delete the module. As far as the JavaScript runtime is concerned, you simply removed one reference out of many. Any other part of your application can still be hanging on to a reference to something in the old module.

这正是您的HTTP应用程序正在发生的事情。当应用程序首次启动时,您的模块会附加到路由的匿名回调。修改这些模块时,它们会将新回调附加到相同的路径;旧的回调不会被删除。我猜你正在使用Express,它按照添加的顺序调用路由处理程序。因此,新的回调永远不会有机会运行。

This is exactly what is happening with your HTTP app. When the app first starts up, your modules attach anonymous callbacks to routes. When you modify those modules, they attach a new callback to the same routes; the old callbacks are not deleted. I'm guessing that you're using Express, and it calls route handlers in the order they were added. Thus, the new callback never gets a chance to run.

老实说,我不会用这种方法来在修改时重新加载你的app。大多数人在干净环境的假设下编写应用程序初始化代码;你是通过在肮脏的环境中运行初始化代码来违反这个假设的–也就是说,已经启动并运行的那个。

To be honest, I wouldn't use this approach to reloading you app on modification. Most people write app initialization code under the assumption of a clean environment; you're violating that assumption by running initialization code in a dirty environment – that is, one which is already up and running.

尝试清理环境以允许初始化代码运行几乎肯定比它的价值更麻烦。当您的基础文件发生变化时,我只需重新启动整个应用程序。

Trying to clean up the environment to allow your initialization code to run is almost certainly more trouble than it's worth. I'd simply restart the entire app when your underlying files have changed.

这篇关于热代码推送NodeJS的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆