I tried to find out this "Hot Code Push" on Node.js. Basically, my main file (which starts when node app.js ) consists of some settings, configurations, and initializations. In this file, I have a file watcher using chokidar. When I added the file, I just require file. If the file was modified or updated, I would delete the cache delete require.cache[path] and then re-requested it. All of these modules do not export anything, but simply work with a 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 try to use many different methods.
What works:
For code like the following:
function run() { console.log(123); }
Works great. But any asynchronous code is not updated.
Problem = Asynchronous Code
app.get('/', function(req, res){
If I then update the file when the nodejs process starts, nothing happens, although it goes through the file watcher and the cache is deleted and then restored. Another example in which it does not work:
// middleware.js function hello(req, res, next) { // code here... } // another file: app.use(hello);
Since app.use will still use the old version of this method.
Question:
How can I fix the problem? Is something missing?
Please do not offer to use third-party modules, as forever. I am trying to include functionality in one instance.
EDIT:
After learning meteors codebase (surprisingly few resources on โHot Code Pushโ in Node.js or in a browser.) And messing around with my own implementation, I successfully made a working solution. https://github.com/TheHydroImpulse/Refresh.js . It is still at an early stage of development, but now it seems solid. I will also implement a browser solution, just to complete.