刚思考这个话题的时候,首先想到的是 Vue 或 React 的组件热更新(基于 Webpack HMR),后来又想到了 Lua、Erlang 等语言的热更新,不过在实际开发 Node.js 后台时,使用 remy/nodemon 之类的热重启(侦测代码改动重启程序)工具也够用,于是 Node.js 的热更新(替换模块,无须重启)的验证就一直搁置。
直到最近在使用「微信机器人」)(Node.js) 时,遇到了强烈的需求。这类机器人程序就是:启动了一个网页,登录 Web 微信,通过抓取识别页面中的元素获得一些状态信息,如:消息、好友请求等等,由于它的启动时间也比较长,如果每次修改业务代码后都要重启,那么等待程序启动就要消耗不少时间,导致开发体验很差,于是实践 Node.js 的热更新就迫在眉睫了。
目标
以下是机器人的核心用法:
|
|
那么我们的目标:增/删/改 业务逻辑(事件处理器)的时候程序无须重启,自动热更新业务逻辑代码,从而提高开发效率。
思路一:基于 Webpack 验证可行
从 Webpack Wiki hot module replacement · webpack/docs Wiki 了解到,Webpack 能知道「哪个模块需要热更新」,并提供一些钩子,另外 webpack 自有一套模块管理,能够管理替换模块,让你访问的是热更新之后的模块。另外,要实现热加载的不仅要满足「再次加载」,还要考虑如何清空相关的「持久资源」。
所以说,如果基于 webpack HMR 来实现的话,需要完成几件事情:
- 把事件处理器的代码模块化,便于 webpack 管理。
- 自动加载所有处理器模块
- 某个事件处理模块更新后需要拿到老的模块,用来移除老的监听处理器。
- 要知道文件的增加和删除,并且拿到模块内容。
1. 业务代码模块化
简单地把每个事件处理器定义为一个文件 *.biz.js
:
|
|
其中 evt
是事件名, fn
是处理器,于是加载一个业务模块后就能拿到事件名称和处理器。
(可能不满足实际要求,先简单验证热更新是否可行哈!)
2. 自动加载
我们约定,业务模块 *.biz.js
都放在 /biz
目录下,该目录下的 index.js
会加载所有业务模块,而 main.js
就只需加载 /biz/index.js
|
|
借助 webpack 的 require-context 加载所有 *.biz.js
模块,避免手写 require:
|
|
3. 修改后热更新
参考 Wiki 的例子 Example 3,知道 require.context 如何使用热更新机制
|
|
到了这一步,修改任何 *.biz.js
的代码都能自动热更新了。
4. 增删文件后热更新
上面的代码已经不小心实现了 「增加文件后热更新」,因为 module.hot.accept(requireContext.id
表示检测 ./biz/*.biz.js
的更新,如果增加一个 c.biz.js
,那么 requireContext.keys()
就变成 [ ..., './c.biz.js']
,于是新模块不等于老模块(不存在),从而使用 c.biz.js
注册事件监听器。
对于删除文件后的热更新,则在上面代码基础上增加:
|
|
经过以上四步,算是初步验证了,借助 Webpack 来玩是可以的,当然我们作了不少严格约定,不过不影响这一阶段的思路。
思路二:基于 Webpack 进阶
上面一种思路存在一些问题
- 业务代码的格式限制太死,不够灵活
- 在生产阶段也耦合了 webpack
于是我想,约定业务代码格式是为了方便通过模块管理事件的注册和移除,假如说在不侵入代码,不作任何约定的情况下,也能知道某个模块注册了哪些事件,是不是就不需约定了,好像是的:
|
|
但是问题来了,我们的目标包括「自动加载所有业务模块,增删文件都能热更新」,那么在开发阶段我们还是借助 webpack 的 require.context 方法,并且约定每个业务模块的入口文件命名为 *.biz.js
,至于里面代码怎么写就随意了,而在生产阶段可以遍历文件找到所有 *.biz.js
进行加载,无须依赖 webpack。
剩下的大部分思路跟 #思路一 类似,代码可参考 zhenyong/webpack-hot-nodejs-demo: Webpack HMR demo use in Node.js, showing how to auto add/remove listeners.
更多思路
最开始写这篇文章是想深扒一下 Node.js 的模块管理和缓存结构,然后验证一下通过清除模块缓存来做热更新是否可行,后来感觉 webpack 给我们作了很多工作,于是就先用 webpack 玩了一轮,看来择日还得再写一篇(二)了
问题
热更新的主要目的是为了提高开发效率,并不是为了在生产上玩热更新,毕竟还有很多潜在问题,例如,模块中涉及全局状态或者单例资源,通过热更新可能会引起混乱……
参考
- Webpack 做 Node.js 代码热替换, 第一步 - 题叶 - SegmentFault
- Backend Apps with Webpack (Part I)
- Backend Apps with Webpack: Driving with Gulp (Part II)
CC BY-NC-SA 3.0 CN
本著作采用 署名-非商业性使用-相同方式共享 3.0 中国大陆 进行许可
欢迎转载,但转载请注明来自 zhenyong.site,并保持转载后文章内容的完整