当前位置: 首页 > 后端技术 > Node.js

Node.js热更新(一)

时间:2023-04-03 16:59:28 Node.js

背景最初想到这个话题的时候首先想到的是Vue或者React组件热更新(基于WebpackHMR),后来又想到了Lua、Erlang等语言的热更新update,但是在Node.js后台的实际开发中,使用remy/nodemon等热重启工具(检测代码变化并重启程序)也是足够的,所以Node.js热更新(更换模块)的验证无需重新启动)已被搁置。直到最近,在使用“微信机器人”)(Node.js)时,遇到了一个强烈的需求。这类机器人程序是:启动一个网页,登录微信,通过抓取识别页面上的元素,获取一些状态信息,比如消息,好友请求等。由于启动时间比较长,如果你每次修改后业务代码都要重启,等待程序启动会耗费大量时间,导致开发体验很差,所以实现Node.js的热更新迫在眉睫。下面是robot的核心用法:robot=newRobot()robot.addEventListener('msg',...)robot.removeEventListener('msg',...)然后我们的目标:增/删/改业务logic(Eventhandler)程序无需重启,业务逻辑代码自动热更新,提高开发效率。思路一:基于Webpack的Validation是可行的。从WebpackWiki模块热更新·webpack/docsWiki,Webpack可以知道“哪个模块需要热更新”,并提供一些hooks。另外,webpack有自己的一套模块管理,可以对替换模块进行管理。让你访问的是热更新后的模块。另外,要实现热加载,不仅要满足“重载”,还要考虑如何清除相关的“持久化资源”。因此,如果基于webpackHMR来实现,需要做几件事:将事件处理器的代码模块化,方便webpack管理。自动加载所有处理器模块更新事件处理模块后,您需要获取旧模块以移除旧的监听处理器。了解文件的增删改查,获取模块内容。1.业务代码模块化简单地将每个事件处理程序定义为一个文件*.biz.js://msg.biz.jsmodule.exports={evt:'msg',fn(){console.log('msghanlder....')}};其中evt是事件名,fn是处理器,所以加载一个业务模块后,可以得到事件名和处理器。(可能不符合实际需求,先简单验证一下热更新是否可行!)2.自动加载我们约定业务模块*.biz.js放在/biz目录下,该目录下的index.js会加载所有服务模块,而main.js只需要加载/biz/index.jssrc|---/biz|---a.biz.js|---b.biz.js|---index.js|---main.js使用webpack的require-context加载所有*.biz.js模块,避免手写require://index.js//加载当前目录下的所有`*.biz.js`constrequireContext=require.context('./',true,/\.biz.js/);//此时requireContext.keys()为['./a.biz.js','./b.biz.js']requireContext.keys().forEach(key=>{constmodule=requireContext(key);//等同于module=require('./biz/a.biz.js')//然后获取事件名称和处理程序,然后监听事件//robot.addEventListener(module.evt,module.fn)});3.修改后热更新参考wiki示例例3了解require.context如何使用热更新机制//index.js//当启动webpackHRM时则module.hot为trueif(module.hot){//表示必须检测和更新此上下文下的模块module.hot.accept(requireContext.id,()=>{constrequireContext=require.context('./',true,/\.biz.js/);requireContext.keys().forEach(key=>{constnewModule=requireContext(key);//第一次自动加载所有模块后,会记录在oldModules对象中()//如果模块内容是不同,表示需要进行热更新处理newmodulenewModule//同时更新缓存记录oldModules[key]=newModule;}});});}此时修改任何*.biz.js代码都可以自动热更新4.添加或删除文件后热更新Hotupdate",因为module.hot.accept(requireContext.id表示检测更新./biz/*.biz.js,如果加了一个c.biz.js,那么requireContext.keys()就变成了[...,'./c.biz.js'],所以新模块不等于到旧模块(不存在),所以使用c.biz.js注册事件监听器。对于删除文件后的热更新,在上述代码的基础上增加:目录,复制旧记录constoldKeysRetain={};Object.keys(oldModules).forEach(k=>(oldKeysRetain[k]=true));constrequireContext=require.context('./',true,/\.biz.js/);requireContext.keys().forEach(key=>{//如果当前目录下有模块,则从临时记录中删除deleteoldKeysRetain[key];constnewModule=requireContext(key);if(oldModules[key]!==newModule){...}});//未擦除的部分表示当前目录不存在,即删除Object.keys(oldKeysRetain).forEach(key=>{//...监听旧模块移除事件deleteoldModules[key];});});}经过以上四步,就是初步的验证了。玩Webpack是可以的,当然我们做了很多严格的约定,但是不影响现阶段的思考。完整代码请移步:zhenyong/webpack-hot-nodejs-demo:WebpackHMRdemouseinNode.js,演示如何自动添加/删除监听器。思路二:上述基于Webpack的思路存在一些问题。业务代码格式限制过于死板,不够灵活。在生产阶段,也耦合了webpack。所以我认为约定好的业务代码格式是为了方便通过模块管理实现事件的注册和移除。如果不侵入代码,不约定,也可以知道哪些事件是某个模块注册的,所以不需要约定,好像是://##a.biz.js不约定关于业务代码格式robot.addLisenter('msg',...)//##entry.jsrobot=newRobot();_add=robot.addLisenterrobot.addLisenter=()=>{//拦截注册事件方法//记录在a.biz模块中注册了哪些事件处理器}require('a.biz')robot.addLisenter=_add但是问题来了。我们的目标包括“自动加载所有业务模块,增删文件可以热更新”,所以我们在开发阶段仍然使用webpack的require.context方法,并且约定每个业务模块的入口文件命名为*.biz.js。至于里面的代码怎么写,随意。生产阶段可以遍历文件找到所有*.biz.js加载,不依赖webpack。其余大部分思路与#思想一类似,代码可参考zhenyong/webpack-hot-nodejs-demo:WebpackHMRdemouseinNode.js,演示如何自动添加/删除监听器。更多想法从这里开始写这篇文章是为了深入了解Node.js的模块管理和缓存结构,然后验证通过清除模块缓存来做热更新是否可行。后来觉得webpack帮我们做了很多工作,就先用webpack玩了一局,看来改天又得写一篇(二)了。热更新的主要目的是提高开发效率,而不是在生产中玩热更新。毕竟,有很多潜在的问题。Statusorsingletonresource,hotupdate可能会造成混乱...参考Webpack做Node.js代码热替换,第一步-题叶-SegmentFaultBackendAppswithWebpack(PartI)BackendAppswithWebpack:DrivingwithGulp(第二部分)