目前不能投入太多时间, 简单说一下
前面看了 Webpack 在前端做热替换的方案, 而且在前端的效果也比较明显的
后端的话, 为了做到修改代码不会重启 WebSocket, 这个功能最近非常想要
Webpack 用的模块化方案基于 CommonJS, 大致的思路是构建模块的 tree
然后 tree 的节点可以冒泡, 跟 DOM 类似, 然后每个模块的 module.hot
上加入了一些代码
这个代码可以用来检测子模块的更新事件, 决定是否热替换代码
比如这样: http://segmentfault.com/a/1190000003879041
var requestHandler = require("./handler.js");
var server = require("http").createServer();
server.on("request", requestHandler);
server.listen(8080);
// check if HMR is enabled
if(module.hot) {
// accept update of dependency
module.hot.accept("./handler.js", function() {
// replace request handler of server
server.removeListener("request", requestHandler);
requestHandler = require("./handler.js");
server.on("request", requestHandler);
});
}
在 Node 方面, 现在看的的一个是百度有一篇文章, 可以手动替换一下代码, 短期可以尝试一下: http://fex.baidu.com/blog/2015/05/nodejs-hot-swapping/ 另外找到个 hotswap 模块, 还不清楚具体机制是怎样的, 也打算尝试一下: https://github.com/rlidwka/node-hotswap 单纯从原理上讲, 在 Node 上实现一套热替换的难度并不会比 Webpack 大, 我就奇怪为什么没人做 也许对些 HTTP 无状态请求的同学来说整个服务器重启无所谓吧, node-dev nodemon supervisor 还够用 但是考虑一下, 修改代码, WebSocket 不受影响, 后端的代码直接更新了, 新的请求直接用新的代码 打算花点时间研究一下
这个对于nodejs提供不中断服务很有用!
内存泄漏!内存泄漏!内存泄漏!
对于无状态的http请求来说,确实没有太大价值。对于长连接的socket请求来说,做到连接和逻辑完全分离,也就变成无状态请求了,也变得没多大价值,反观热部署模块或者热部署服务,要么手动触发要么定时器逻辑开销比较大,所以github上才没有相应的包吧
如果要替换 setInterval 为 1000呢,在你的例子中
@jiyinyiyong 如果一个模块里建立了很多监听呢,不是很麻烦嘛替换的时候 (^O^)/
@jiyinyiyong 这样的话,这种方案能够用在哪些方面呢
如果写模块的人在模块文件中定义了全局变量,就泄漏了。浏览器端刷新一下就解决,作为服务器端运行的node进程,这种方案只能在开发环境玩玩。别搞到生产环境。 自豪地采用 CNodeJS ionic
我个人认为如果仅仅是为了开发过程不用手动重启,直接fs.watch开发目录,发现文件更改,直接杀掉进程,让进程重启就行了,重启一切都是最新的,就没有这些头疼问题了,但这仅仅对于没有状态的网页应用,对游戏类的,那就要搞一套规范出来,而且大家都要严格遵守,反正很麻烦,不要羡慕erlang,人家erlang变量都没有,javascript这种随意的语言就算了。灵活是巨大的优点同时也是巨大的缺点。
只是为了连接不中断的话,建议使用socket.io
来自炫酷的 CNodeMD
@jiyinyiyong 不能,只是会自动重连,可能理解错误,我以为你最主要的是想要客户端保持链接。 热替换牵扯复杂,稍有不慎就会内存泄露,我觉得两相其害取其轻者。尽量保持简单明了为好。
热替换的功能在开发环境下还是非常有必要的(尤其有 WebSocket 之类的功能时候)。ThinkJS 从 1.0 版本就已经实现,现在 2.0 已经非常完善了。下面说下具体的思路。 热替换分成 2 个部分: 1)监听文件修改 2)清除修改文件的缓存和对应的依赖文件
监听文件修改
监听文件修改最常见的方式是用 fs.watch
方法,但这个方法有很多问题,官网文档里也有说明。为此还有一个模块 chokidar
用来监听文件变化的,这个模块在 Gulp/Babel 等很多工具有使用,一般情况下都是没有问题的。但有时候也有问题,因为本质还是主要基于 fs.watch
来实现的。
如果监听的文件不是很多的话,最保险的办法是通过定时器来实现,获取目录下所有文件的修改时间,跟上一个状态做对比,如果不一样的话,表示文件有修改,然后对比出修改的文件。这里要注意 1 个地方,如果使用 Babel 之类的编译器,如果源文件有删除的话,需要把编译后的文件删掉。
清除文件缓存
Node.js 下所有模块的缓存都放在 Module._cache
下 ,模块下可以通过 require.cache
获取。如果这个文件没有被其他文件依赖的话,只要删除掉 require.cache
下对应的数据就可以了。如果是配置之类的文件,并且是在服务启动之前就加载,需要重新加载下。
如果这个模块被其他模块依赖的话,处理就复杂一些,不光要清除这个模块的缓存,还要清除依赖这个模块的模块。
Node.js 在模块依赖的处理有 bug,require.cache
里每个模块虽然都有 children
字段,表示依赖的子模块,但如果 A 模块被 B 模块依赖的话, B 模块的 children 含有 A 模块,这时候如果 C 模块也依赖 A 模块的话,C 模块的 children 里并不包含 A 模块。下面是 Node.js(V4.2.2) 里 module.js 里的实现:
Module._load = function(request, parent, isMain) {
...
var filename = Module._resolveFilename(request, parent);
var cachedModule = Module._cache[filename];
//如果这个模块已经有缓存,直接返回
if (cachedModule) {
return cachedModule.exports;
}
...
};
上面的代码去除了一些不相关的代码,可以看到如果一个模块已经加载的话,那么直接返回。这也导致了为啥 C 模块的 children 属性里没有包含 A 模块。 改进的话也很简单,目前我已经给官方发了 pull request, 具体见:https://github.com/nodejs/node/pull/3933/files
有了完整的模块依赖关系后,清除缓存就比较简单了,ThinkJS 里实现如下:
clearFileCache(file){
let mod = require.cache[file];
if(!mod){
return;
}
//remove children
if(mod && mod.children){
mod.children.length = 0;
}
// clear module cache which dependents this module
for(let fileItem in require.cache){
if(fileItem === file || fileItem.indexOf(NODE_MODULES) > -1){
continue;
}
let item = require.cache[fileItem];
if(item && item.children && item.children.indexOf(mod) > -1){
this.clearFileCache(fileItem);
}
}
//remove require cache
delete require.cache[file];
}
ThinkJS 里本身对这些处理更加完善下,可以直接使用 ES6/7 特性开发,然后自动使用 Babel 编译,自动清除文件的缓存。开发过程中完全不用借助第三方模块清除缓存。
@haozxuan 往往说的无状态还要做到逻辑和数据分离,不能替换逻辑的时候把当前的数据替换掉,做到逻辑数据分离分离到什么粒度还是有难度的
研究不多,只提一个可能被大家忽略的方式
node-inspector可以直接修改正在执行中的代码,包括已经被外部引用的函数(例如已注册的回调函数) 曾经跟进去看过几眼,貌似v8 debug context里面有个LiveEdit工具
@fengmk2 1.不会泄露,失去引用v8自动回收.主要是块级作用域的变量无法更新