js代码热更新
发布于 10 年前 作者 rayosu 17225 次浏览 最后一次编辑是 8 年前 来自 分享

实现原理 使用代理的方式让所有类和实例能够在js文件更改后, 对被代理对象进行替换, 实现热更新.

作用 使用这个模块引入的js文件, 在修改后不需要重启服务器, 可以达到下列效果:

  1. require.cache更新了, 新的require使用新的js
  2. 原来旧的类对应的实例也更新了 具体就是: 绑定在prototype下的函数, 绑定在类上的函数等也更新了, 旧实例调用的是新的代码.

注意

  1. 因为代理会消耗一定资源, 所以建议只在逻辑js上使用.
  2. 目前建议开发环境中用, 因为还在完善

安装 npm install hot-require

引入 // app启动时候引入这个模块, 引入一次即可 require('hot-require');

示例 // 需要热更新js, 用下面方式代替原有的require函数 var yourJs = _require(__dirname, '[your js path]');

源码 github: https://github.com/rayosu/hot-require 欢迎大家给意见, 完善和扩展这个方案.

9 回复

看上去实现非常简单啊,有不少热更新的框架都比你这个要复杂。。。

Chrome 的 Workspace 功能也实现了 JS 的热替换

支持绑定在prototype下的函数变动的热更新, 并且不影响初始化后的实例对象的属性值

不太懂…

我理解热更新,就是不缓存到Module.cache 那么可以直接在需要的地方删除缓存

var some_thing = require('some_need_hot_require')

// delete cache
var Module = module.constructor
var filename = Module._resolveFilename('some_need_hot_require',module)
delete Module._cache[filename]

可以封装一个方法 module.require

/*
    require -> Module.prototype.require -> Module.load(request,module)

    require.reslove -> Module._resolveFilename(request, module);
*/


var Module = module.constructor

Module.prototype._require = function(request) {
    var res = this.require(request)

    // delete cache
    var filename = Module._resolveFilename(request, this);
    delete Module._cache[filename]

    return res;
};

需要热加载,使用 module.require(xxx) 即可,可以使用任何在 require 里面使用的东西 … 完整代码见gist https://gist.github.com/magicdawn/5d6f53e1d3020cec112f

@magicdawn 楼主的意思是只更新代码的逻辑,而不影响正在运行的数据,你的代码运行时至少存在局部变量吧!你不能把局部变量的值搞潮吹了啊!

@hainee 还是不懂 ⊙﹏⊙!

[[[[[[[[@magicdawn](/user/magicdawn)](/user/magicdawn)](/user/magicdawn)](/user/magicdawn)](/user/magicdawn)](/user/magicdawn)](/user/magicdawn)](/user/magicdawn) 我举个例子 有一个js文件user.js如下:

var User = function(cfg){
	this.name = cfg.name;
	this.age = cfg.age;
};
User.prototype.getName = function(){
	return this.name + "A";
}; 
module.exports = User;
// end user.js

你在程序中引入了user.js, 并实例化一个user对象

var User = require("user.js");
var user = User({"张三", 15}); 
app.addOnlineUser(user);

并且你将user是来存储起来, 不需要每次都进行实例化.

然后你修改了user.js, 并且删除缓存 假如你将User.prototype.getName修改为:

User.prototype.getName = function(){
	return this.name + "B";;
} 

然后你删除了cache, 让require时从新载入新的user.js文件

此时, 你的程序哪怕重新执行var User = require("user.js"); 当你执行

var user = app.getOnlineUser("张三");
user.getName(); // "张三A" 而非 "张三B"

时, 依然是就得User类的getName函数.return this.name + "A"; 除非你一一找到你旧User生成的user实例, 并且将他们跟新的User类原型进行关联, 否则你的原有user数据依然沿用旧User类的函数处理代码. 新旧User类是同时存在的, 只是你用了同一个引用名字而已. 但对应的内存对象是不同的, 而且user实例当初对应的是旧的User类原型.

简而言之, 按照你的方式, 无论如何, 你也无法改变你之前载入的User类已经生成的user实例, 去关联到你的新User类中. 除非你舍弃所有旧的类产生的实例. 当然, 如果你的服务器程序不存在停留js内存的数据, 你的每一个操作都需要重新调用require, 那我的方案就显得多此一举了, 呵呵.

@rayosu 我原来也想写一个这个… 但是最后只能实现 .json 的更新 (无刷新 reload) … .js 实在无从下手… 因为引用都是缓存的… 就算更新了对象, 原本的还是引用到旧对象上.

@ellansin 由于js的奔放, 导致我目前也发现有个别情况会有问题, 例如我有一个js这么写的 囧RZ:

var Formula = function () {
    return {
        // 英雄公式
        hero: function (cfg) {
            return {
				basehp: function(){
					return 1;
				}
            }
        }
	}
}
module.exports = Formula;

等我清闲了我就再折腾下

@ellansin 你的排版挺干净的,我决定向你学习

有新版本了, 解决了一个bug, 原来以为是对动态return对象的实现有问题, 后来发现是我忘记了对非prototype绑定函数忘记了return~~~

回到顶部