VonaJS是如何做到文件级别精确HMR(热更新)的?

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机制,代码如下:

typescript 复制代码
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();
    });
  }
}
  1. 响应系统启动事件,通过scope.election.obtain竞争所有权
  2. 当取得所有权时,执行scope.service.watch.start,实现文件监听
  3. 当释放所有权时,执行scope.service.watch.stop,停止文件监听

2. ESM文件重新加载

当监听到源码文件变更之后,需要重新加载。我们知道一个文件import之后,系统会自动缓存,如果再次import,系统会直接使用缓存,不会重新加载。那么,我们是否可以强制清理系统缓存呢?在CJS中是可以的,但在ESM中不行。

NestJS开发时间比较早,到目前为止仍然使用的是CJS模块。在NestJS中,源码采用的是ESM语法,但是实际运行时,需要先编译成CJS模块,然后再通过require加载模块。

而VonaJS是全新设计的框架,全部使用了ESM模块。虽然不能删除系统缓存,但是可以通过变更文件名的方式来实现重新加载,代码如下:

typescript 复制代码
const file='/path/to/service.ts';
const fileUrl = `${file}?${Date.now()}`;
const fileModule = await import(fileUrl);

3. 清理运行状态值

当文件重新加载之后,就可以替换IOC容器中注册的实例。除此之外还有可能需要清理一些运行状态值。这就需要具体问题具体分析。比如,Server文件不需清理运行状态值。但是,Middleware就需要清理运行状态,从而让新的Middleware生效

下面以系统中间件为例,演示基本流程:

  1. 当系统启动时,需要注入系统中间件
typescript 复制代码
this.app.use((ctx, next) => {
  return _composeMiddlewareSystems(this.app)(ctx, next);
});
typescript 复制代码
function _composeMiddlewareSystems(app: VonaApplication) {
  // compose
  if (!app.meta[SymbolCacheComposeMiddlewareSystems]) {
    const middlewares = app.bean.onion.middlewareSystem.getOnionsEnabledWrapped(item => {
      return _wrapOnion(app, item);
    });
    app.meta[SymbolCacheComposeMiddlewareSystems] = compose(middlewares);
  }
  return app.meta[SymbolCacheComposeMiddlewareSystems];
}

_composeMiddlewareSystems方法将收集所有系统中间件,并compose成一个函数,然后缓存到app.meta[SymbolCacheComposeMiddlewareSystems]

  1. 清理运行状态
typescript 复制代码
@Hmr()
export class HmrMiddlewareSystem extends BeanBase implements IHmrReload {
  async reload(_beanOptions: IDecoratorBeanOptionsBase) {
    delete this.app.meta[SymbolCacheComposeMiddlewareSystems];
  }
}

当某个系统中间件重新加载后,就会自动执行该Class的reload方法,删除缓存app.meta[SymbolCacheComposeMiddlewareSystems]。从而让_composeMiddlewareSystems方法重新收集所有系统中间件,compose出一个新的函数

4. 支持更多场景

如上所述,不同场景的文件,需要根据不同的运行机制,提供不同的清理逻辑,确保文件级别的HMR可以正常运行

VonaJS支持大量的场景开发,清单如下:

  1. Vona Aspect
  1. Vona Bean
  1. Vona Create
  1. Vona Init
  1. Vona Meta
  1. Vona Tools

资源

相关推荐
CodeCaptain4 小时前
Cocos Creator 3.8.x 可对tiled 1.4.x进行的操作或分析有哪些
经验分享·游戏·typescript·cocos2d
Swift社区8 小时前
Flutter 页面为什么会频繁 rebuild?如何定位和优化?
flutter·前端框架·node.js
程序员爱钓鱼12 小时前
Node.js 编程实战:使用 Postman Swagger 测试接口
后端·面试·node.js
cj814012 小时前
Node.js基本概念理解
前端·node.js
程序员爱钓鱼12 小时前
Node.js 编程实战:JWT身份验证与权限管理
前端·后端·node.js
毛小茛15 小时前
pnpm 已经安装成功,但 npm 的全局 bin 目录没有进 PATH
前端·npm·node.js
冬奇Lab18 小时前
Vercel部署全攻略:从GitHub到上线,10分钟让你的前端项目免费拥有自己的域名
前端·后端·node.js
天远数科18 小时前
Node.js全栈开发:深度集成天远贷前风险报告接口打造风控中台
大数据·node.js
Hao_Harrision19 小时前
50天50个小项目 (React19 + Tailwindcss V4) ✨| AnimatedCountdown(倒计时组件)
前端·typescript·react·tailwindcss·vite7
ljh57464911919 小时前
npm run build:prod 打包后,文件中的console.log 失效
前端·npm·node.js