Nodejs 代码热更新实现

实现原理:利用node file system模块的watch接口监视文件夹的文件变更事件事件触发后,移除require.cache内的对应的缓存使用vm模块编译新加载的代码(基础检查语法,后续可在vm content内测试运行)成功后 使用require加载代码,新代码就会缓存在 require.cache内如果失败 恢复require.cache的缓存数据代码如下:

const fs = require("fs");
const path = require("path");
const vm = require("vm");


const handlerMap = {};
const cacheMap = {};

/**
* 加载文件内的代码并监视更新热加载
*	[@return](/user/return) {Promise.<void>}
*/
const loadHandlers = async function () {
	/// 查看指定文件夹下的所有文件
 	const files = await new Promise((resolve, reject) => {
    	fs.readdir(path.join(__dirname, 'hots'), function (err, files) {
        	if (err) {
            	reject(err);
        	} else {
            	resolve(files);
        	}
   	 	});
 	});
	/// 遍历加载文件
	for (let f in files) {
   	 	if (/.*?\.js$/.test(files[f])) {
        	handlerMap[files[f]] = await loadHandler(path.join(__dirname, 'hots', files[f]));
    	}
	}
	/// 监视文件变动
	watchHandlers();
	};

/**
* 监视文件变动
*/
const watchHandlers = function () {
	console.log('watching ', path.join(__dirname, 'hots'));
	fs.watch(path.join(__dirname, 'hots'), {recursive: true}, function (eventType, filename) {
   	 	if (/.*?\.js$/.test(filename)) {
			/// 这里先删除旧的缓存代码 防止内存泄漏
			if( cacheMap[require.resolve(path.join(__dirname, 'hots', filename))] )
				delete cacheMap[require.resolve(path.join(__dirname, 'hots', filename))];
			/// 这里缓存现在运行的代码,热加载失败后恢复用,还有就是防止现有运行的代码异步没有返回就删除会因为逻辑可能没有执行完毕引起逻辑bug
        	cacheMap[require.resolve(path.join(__dirname, 'hots', filename))] = require.cache[require.resolve(path.join(__dirname, 'hots', filename))];
			///重置require.cache缓存
        	require.cache[require.resolve(path.join(__dirname, 'hots', filename))] = null;
			
        	loadHandler(path.join(__dirname, 'hots', filename)).then(function (data) {
            	if (data) {
                	handlerMap[filename] = data;
            	} else {
                	delete handlerMap[filename];
            	}
            	console.log("热更成功", filename, "当前代码", handlerMap);
        	}).catch(function (err) {
            	console.log("热更失败: 代码包含以下错误:", err, "当前代码:", handlerMap);
            	require.cache[require.resolve(path.join(__dirname, 'hots', filename))] = cacheMap[require.resolve(path.join(__dirname, 'hots', filename))];
            	cacheMap[require.resolve(path.join(__dirname, 'hots', filename))] = null;
        	});
    	}
	});
};

/**
* 加载文件
* [@param](/user/param) filename
* [@return](/user/return) {Promise.<*>}
*/
const loadHandler = async function (filename) {
	const exists = await new Promise(resolve => {
    	/// 查看代码文件是否存在
    	fs.access(filename, fs.constants.F_OK | fs.constants.R_OK, err => {
        	if (err) {
            	resolve(false);
        	} else {
            	resolve(true);
        	}
    	});
	});
	if (exists) {
    	return await new Promise((resolve, reject) => {
        	fs.readFile(filename, function (err, data) {
            	if (err) {
                	resolve(null);
            	} else {
                	try {
                    	/// 使用vm script编译热加载的代码
                    	new vm.Script(data);
                    	//const script = new vm.Script(data);
                    	// const context = new vm.createContext({
                    	//     require: require,
                    	//     module: {}
                    	// });
                    	// script.runInContext(context);
                	} catch (e) {
                    	/// 语法错误,编译失败
                    	reject(e);
                    	return;
                	}
                	/// 编译成功的代码
                	resolve(require(filename));
            	}
        	});
    	});
	} else {
    	/// 文件被删除
    	return null;
	}
};

loadHandlers().then(function () {
	console.log("run...");
	}).catch(function (e) {
	console.error(e);
});

注意:使用这种方法被管理热更新的代码 不能使用全局的require的去单独加载,如果不想统一管理 去除handleMap变量缓存,在每一处使用被管理的代码时 使用require获取代码(require会先从缓存内检查,然后才会去找文件加载)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值