背景
我们常用nodemon做node.js进程管理工具,当内容发生变化时候即reload,严格来说不算hmr。
本文主要是总结下当前node.js开发方式。和聊下hmr的几种做法
注:本文以koa来举例,尽量不涉及到框架,只是为了简化。
主流做法
主要是区分是否使用typescrpt, 目前使用typescript开发nodejs应用已经非常普遍,但typescript无法直接在node中运行,需要先进行编译,既要编译,又要hmr,怎么实现呢?
1 | go复制代码// 非typescript版本 |
node-hmr/hot-module-require
在进一步了解服务端hmr之前,需要先了解下commonjs/esm的执行逻辑(更加详细的细节请查看我关于模块化的介绍)。不管是commonjs还是esm,在执行代码之前,会先处理模块之间的引用关系,再缓存到内存中,我们实际执行的是已缓存的代码(代码已执行后,修改文件内容不会影响已缓存内容)。
所以需要实现服务端hmr的关键在于:如何更新缓存内容。
这两个插件实现的基本流程:
1 | javascript复制代码1 通过chokidar监听文件变化; |
但这里有一些需要注意的地方,例如:
1 修改require.cache[moduleId]并不只是删除后添加新的引用而已,还涉及到被多处引用等问题,所有关联的parent节点都需要清理。
2 不同框架的处理方式有些不一样,例如koa框架,在初始化时候会将所有中间件通过compose函数绑定在一起(类似链表的数据结构),如果修改了其中的某个中间件,很难去找到对应的节点修改。目前像node-hmr这个插件只是将中间件重新reload,并且分离http服务。避免重启http服务:
1 | ini复制代码const hmr = require('node-hmr'); |
相关工具
nodemon
nodemon主要实现两个功能,监听文件变化(通过chokidar插件实现,后面有介绍),重启应用;
其实我一开始以为重启node.js应用是很简单的事情,因为平时就是ctrl+c,或者用nodemon或者是pm2这样的进程管理工具。其实不是这么简单的事情,特别是还需要cross platform.
nodemon重启流程如下(因为我是mac环境,所以只聊下mac下重启流程):
1 | ini复制代码1 通过pstree获取所有子进程,并关闭所有子进程; |
关于重启进程涉及过多,我后面会有一篇更详细的文章说明。
ts-node
ts-node是typescript的执行引擎,可以通过ts-node命令直接执行typescript命令而不需要编译为js再执行;
1 | arduino复制代码// 安装 |
思考
1 客户端(浏览器)hmr和服务端hmr有什么区别?
先看下两者的执行流程:
1 | rust复制代码客户端hmr流程:监听文件变化->将更新内容通知到框架->框架内部实现的render方法执行修改并渲染出来; |
其实两者的区别在于客户端需要框架配合将内容实时渲染出来,而服务端只需要修改内容,等请求进来后按照新的内容执行即可;
参考文档
1 Node.js 也能 HMR 热更新: zhuanlan.zhihu.com/p/260441242
2 ts-node: typestrong.org/ts-node/
3 node-hmr: github.com/serhiinkh/n…
4 hot-module-require: github.com/imcuttle/ho…
本文转载自: 掘金