关于nodejs服务端开发hmr的方案

背景

我们常用nodemon做node.js进程管理工具,当内容发生变化时候即reload,严格来说不算hmr。

本文主要是总结下当前node.js开发方式。和聊下hmr的几种做法

注:本文以koa来举例,尽量不涉及到框架,只是为了简化。

主流做法

主要是区分是否使用typescrpt, 目前使用typescript开发nodejs应用已经非常普遍,但typescript无法直接在node中运行,需要先进行编译,既要编译,又要hmr,怎么实现呢?

1
2
3
4
5
6
7
go复制代码// 非typescript版本
1 全局安装nodemon; `npm install nodemon -g`
2 修改启动脚本;`"start": "nodemon main.js"`
// typescript版本
1 全局安装ts-node、nodemon; `npm install ts-node nodemon -g`
2 安装typescript和koa的依赖; `npm install --save-dev koa @types/koa typescript`
3 配置nodemon监听目录管理进程,ts-node执行typescript文件; `nodemon --watch src/**/* -e ts,tsx --watch main.ts --exec ts-node main.ts`

node-hmr/hot-module-require

在进一步了解服务端hmr之前,需要先了解下commonjs/esm的执行逻辑(更加详细的细节请查看我关于模块化的介绍)。不管是commonjs还是esm,在执行代码之前,会先处理模块之间的引用关系,再缓存到内存中,我们实际执行的是已缓存的代码(代码已执行后,修改文件内容不会影响已缓存内容)。

所以需要实现服务端hmr的关键在于:如何更新缓存内容。

这两个插件实现的基本流程:

1
2
3
javascript复制代码1 通过chokidar监听文件变化;  
2 通过node.js通过require暴露的缓存对象require.cache, 删除具体的缓存内容 `delete require.cache[moduleId]`;
3 替换新的内容;

但这里有一些需要注意的地方,例如:

1 修改require.cache[moduleId]并不只是删除后添加新的引用而已,还涉及到被多处引用等问题,所有关联的parent节点都需要清理。

2 不同框架的处理方式有些不一样,例如koa框架,在初始化时候会将所有中间件通过compose函数绑定在一起(类似链表的数据结构),如果修改了其中的某个中间件,很难去找到对应的节点修改。目前像node-hmr这个插件只是将中间件重新reload,并且分离http服务。避免重启http服务:

1
2
3
4
5
6
7
8
9
10
ini复制代码const hmr = require('node-hmr');

let callback;
hmr(() => {
const app = require('./app');
callback = app.callback();
});

const server = http.createServer((req, res) => callback(req, res));
server.listen(3000);

相关工具

nodemon

nodemon主要实现两个功能,监听文件变化(通过chokidar插件实现,后面有介绍),重启应用;

其实我一开始以为重启node.js应用是很简单的事情,因为平时就是ctrl+c,或者用nodemon或者是pm2这样的进程管理工具。其实不是这么简单的事情,特别是还需要cross platform.

nodemon重启流程如下(因为我是mac环境,所以只聊下mac下重启流程):

1
2
3
ini复制代码1 通过pstree获取所有子进程,并关闭所有子进程;  
2 关闭主进程;
3 再次启动服务;

关于重启进程涉及过多,我后面会有一篇更详细的文章说明。

ts-node

ts-node是typescript的执行引擎,可以通过ts-node命令直接执行typescript命令而不需要编译为js再执行;

1
2
3
4
arduino复制代码// 安装
npm install -g ts-node
// 执行
ts-node *.ts

思考

1 客户端(浏览器)hmr和服务端hmr有什么区别?

先看下两者的执行流程:

1
2
rust复制代码客户端hmr流程:监听文件变化->将更新内容通知到框架->框架内部实现的render方法执行修改并渲染出来; 
服务端hmr流程:监听文件变化->修改被缓存的文件内容;

其实两者的区别在于客户端需要框架配合将内容实时渲染出来,而服务端只需要修改内容,等请求进来后按照新的内容执行即可;

参考文档

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…

5 nodejs.cn/api/modules…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%