VonaJS如何实现文件级精确HMR的热更新?
摘要:NestJS采用项目级HMR,文件变更需重新编译并重启App,效率低。VonaJS5.0.242实现文件级HMR,直接重新导入文件并替换IOC容器实例,无需编译或重启。通过Election机制在分布式场景中监听文件变更,利用ESM模块特性强
NestJS:项目级别HMR
如果使用过NestJS,就会知道NestJS是基于整个项目实现HMR(热更新)的。大致流程如下:当一个源码文件变更时,系统会自动将文件重新编译输出到dist目录,然后重启App。当项目非常大时,这样的HMR机制就会非常慢。
VonaJS:文件级别HMR
而VonaJS就实现了基于单文件的精确HMR(热更新)。大致流程如下:当源码文件变更时,系统会自动重新导入该文件,并替换IOC容器中注册的实例。既没有重新编译的环节,也不需要重启App。如果你要开发大型项目,没有比这个更爽的HMR机制了。
下面先简要看看VonaJS HMR的效果,再介绍是如何实现的:
文件级别HMR效果展示
1. 修改Service文件
当我们修改某个Service文件并保存之后,控制台显示如下:
2. 修改Controller文件
当我们修改某个Controller文件并保存之后,控制台显示如下:
3. 修改Middleware文件
当我们修改某个Middleware文件并保存之后,控制台显示如下:
文件级别HMR原理分析
1. 分布式场景中如何实现文件Watch
VonaJS原生支持分布式架构,因此在执行npm run dev时也是默认启动两个Workers,便于尽早排查分布式场景下可能遇到的问题。那么,在分布式场景中,我们需要挑选出一个Worker,用于监听文件的变更事件。
VonaJS提供了Election机制,代码如下:
export class Monkey {
async appStarted() {
const scope = this.app.scope(__ThisModule__);
scope.election.obtain('hmr', async () => {
await scope.service.watch.start();
}, async () => {
await scope.service.watch.stop();
});
}
}
响应系统启动事件,通过scope.election.obtain竞争所有权
当取得所有权时,执行scope.service.watch.start,实现文件监听
当释放所有权时,执行scope.service.watch.stop,停止文件监听
2. ESM文件重新加载
当监听到源码文件变更之后,需要重新加载。我们知道一个文件import之后,系统会自动缓存,如果再次import,系统会直接使用缓存,不会重新加载。那么,我们是否可以强制清理系统缓存呢?在CJS中是可以的,但在ESM中不行。
NestJS开发时间比较早,到目前为止仍然使用的是CJS模块。在NestJS中,源码采用的是ESM语法,但是实际运行时,需要先编译成CJS模块,然后再通过require加载模块。
而VonaJS是全新设计的框架,全部使用了ESM模块。虽然不能删除系统缓存,但是可以通过变更文件名的方式来实现重新加载,代码如下:
const file='/path/to/service.ts';
const fileUrl = `${file}?${Date.now()}`;
const fileModule = await import(fileUrl);
3. 清理运行状态值
当文件重新加载之后,就可以替换IOC容器中注册的实例。除此之外还有可能需要清理一些运行状态值。这就需要具体问题具体分析。比如,Server文件不需清理运行状态值。
