Javascript 热代码推送节点
我一直在试图弄清楚Node.js上的“热代码推送”。基本上,我的主文件(在键入Javascript 热代码推送节点,javascript,node.js,livereload,Javascript,Node.js,Livereload,我一直在试图弄清楚Node.js上的“热代码推送”。基本上,我的主文件(在键入node app.js时运行)包含一些设置、配置和初始化。在该文件中,我有一个文件监视程序,使用chokidar。添加I文件后,我只需require文件即可。如果文件已更改或更新,我将删除缓存delete require.cache[path],然后重新请求它。所有这些模块都不导出任何内容,它只与单个全局Storm对象一起工作 Storm.watch = function() { var chokidar, d
node app.js
时运行)包含一些设置、配置和初始化。在该文件中,我有一个文件监视程序,使用chokidar。添加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);
}
工作完美。但任何异步代码都无法更新
问题=异步代码
如果在nodejs进程运行时更新文件,则不会发生任何事情,尽管它会通过文件监视程序,缓存会被删除,然后重新建立。另一个不起作用的例子是:
// middleware.js
function hello(req, res, next) {
// code here...
}
// another file:
app.use(hello);
As app.use仍将使用该方法的旧版本
问题:
我怎样才能解决这个问题?有什么我遗漏的吗
请不要像forever那样建议使用第三方模块。我正在尝试将功能合并到单个实例中
编辑:
在研究meteors代码库(node.js或browser中关于“热代码推送”的资源少得出奇)并修补我自己的实现之后,我成功地提出了一个可行的解决方案。这仍处于发展的早期阶段,但现在看来很稳固。我也将实现一个浏览器解决方案,只是为了完成 删除
require
的缓存实际上不会“卸载”旧代码,也不会撤消该代码所做的操作
以以下函数为例:
var callbacks=[];
registerCallback = function(cb) {
callbacks.push(cb);
};
现在假设有一个模块调用这个全局函数
registerCallback(function() { console.log('foo'); });
应用程序启动后,回调将有一项。现在我们将修改模块
registerCallback(function() { console.log('bar'); });
“热补丁”代码运行,删除require.cache
d版本并重新加载模块
registerCallback(function() { console.log('bar'); });
您必须了解的是,现在回调
有两个项。首先,它引用了记录foo的函数(在应用程序启动时添加)和记录bar的函数(刚刚添加)
即使删除了对模块的导出的缓存引用,您也无法实际删除模块。就JavaScript运行时而言,您只需从多个引用中删除一个引用即可。应用程序的任何其他部分仍然可以挂起对旧模块中某些内容的引用
这正是你的HTTP应用所发生的事情。当应用程序首次启动时,您的模块会将匿名回调附加到路由。当您修改这些模块时,它们会将新回调附加到相同的路由;旧回调不会被删除。我猜您使用的是Express,它会按照添加的顺序调用路由处理程序。因此,新回调永远没有机会运行
老实说,我不会用这种方法在修改时重新加载你的应用程序。大多数人在干净的环境下编写应用程序初始化代码;您在肮脏的环境中运行初始化代码违反了这一假设——即,已经启动并运行的环境
试图清理环境以允许您的初始化代码运行几乎肯定会带来更多的麻烦。当您的底层文件发生更改时,我只需重新启动整个应用程序。Meteor通过允许模块在热代码推送过程中“注册”自己来解决此问题
他们在他们的重新加载
包中实现了这一点:
我已经看到GitHub上的一些插件中使用了Meteor.reload
API,但他们也在会话
包中使用它:
因此,基本上,当页面/窗口加载时,meteor会运行一个“迁移”,由包定义在热代码推送时重新计算的数据/方法等
他们也在使用它(搜索重新加载)
在刷新之间,他们将使用保存“状态”。谢谢。是的,这很有道理。如果我要使用我以前的方法,我必须编写大量的边缘案例来删除旧引用,但这在大规模上是行不通的。我只需重新启动整个应用程序。再次感谢您的澄清。
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;
}
})();
}