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

资源

相关推荐
星栈12 小时前
从装一堆工具到看懂 Node 工程化思维:我的项目复盘记录
后端·node.js
东方小月14 小时前
从零开发一个Coding Agent:monorepo项目搭建
前端·后端·node.js
Lihua奏16 小时前
# 用 Node.js 连接数据库:一次请求是怎么跑起来的?
node.js
kisshyshy19 小时前
给端侧大模型装上“发动机”:React 合成事件 + 进度条组件全解
前端·react.js·node.js
晓说前端19 小时前
TypeScript 核心语法进阶 —— 字面量类型与类型推论
前端·typescript
csdn2015_1 天前
nodejs安装
node.js·vue
布兰妮甜1 天前
从 0 搭建企业级 Vue3/Vite 脚手架(规范、eslint、husky、打包、环境变量全流程)
typescript·vue3·vite·脚手架·前端工程化
东方小月1 天前
从0开发一个 Coding Agent(一):前言
前端·人工智能·typescript
gis开发之家2 天前
《Vue3 从入门到大神35篇》Vue3 源码详解(五):effect 依赖收集原理——track 与 trigger 是如何工作的?
javascript·typescript·前端框架·vue3·vue3源码
前端双越老师2 天前
如何以前端视角(非0基础)学 Java ?
java·node.js·全栈