热模块更换

热模块替换

热模块替换 (HMR) 指的是在修改后重新加载 JavaScript 模块的过程,而无需重启整个进程。由于文件更改后,你无需等待整个进程重启,因此 HMR 通常能实现更快的反馈循环。

HMR 一词在前端生态系统中已使用多年,像 Vite 这样的工具可以在保持网页现有状态的同时,热重载模块并应用更改。

然而,AdonisJS 执行的 HMR 要简单得多,并且与 Vite 或 Webpack 等工具有很大不同。我们在 HMR 方面的目标是提供更快的重载,仅此而已。

关键概念

不向浏览器传播更新

由于 AdonisJS 是一个后端框架,我们不负责维护前端应用程序的状态或向网页应用 CSS。因此,我们的 HMR 集成无法与你的前端应用程序通信并协调其状态。

实际上,并非所有 AdonisJS 应用程序都是浏览器渲染的网页应用。许多人使用 AdonisJS 创建纯 JSON API,它们也可以从我们的 HMR 集成中受益。

仅适用于动态导入

大多数 HMR 工具使用代码转换技术,在编译输出中注入额外代码。在 AdonisJS 中,我们并不热衷于使用转译器,而是始终努力接纳平台本身的特性。因此,我们的 HMR 方法使用 Node.js 加载器钩子,并且仅适用于动态导入。

好消息是,你的 AdonisJS 应用程序的所有关键部分默认情况下都是动态导入的。例如,控制器、中间件和事件监听器都是动态导入的,因此,你可以从今天起利用 HMR,而无需更改应用程序中的任何代码。

值得一提的是,动态导入模块的导入可以是顶层导入。例如,一个控制器(在路由文件中动态导入)可以有顶层导入的验证器、TSX 文件、模型和服务,它们都可以从 HMR 中受益。

使用方法

所有官方入门套件都已更新为默认使用 HMR。然而,如果你有一个现有应用程序,你可以按以下方式配置 HMR。

hot-hook npm 包作为开发依赖项安装。这个包由 AdonisJS 核心团队创建,也可以在 AdonisJS 应用程序之外使用。

npm i -D hot-hook

接下来,将以下配置复制粘贴到 package.json 文件中。boundaries 属性接受一个 glob 模式数组,这些模式必须被考虑用于 HMR。

{
"hotHook": {
"boundaries": [
"./app/controllers/**/*.ts",
"./app/middleware/*.ts"
]
}
}

配置完成后,你可以使用 --hmr 标志启动开发服务器。

node ace serve --hmr

此外,你可能希望更新 package.json 文件中的 dev 脚本,以使用此新标志。

{
"scripts": {
"dev": "node ace serve --hmr"
}
}

完全重载 vs HMR

本节解释了 hot-hook 的底层工作原理。如果你没有心情阅读扩展的技术理论,可以随意跳过 🤓

或者,如果你希望获得更深入的解释,请查阅该包的 README 文件

让我们来了解一下 AdonisJS 何时会执行完整重载(重启进程),以及何时会热重载模块。

创建依赖树

使用 --hmr 标志时,AdonisJS 将使用 hot-hookbin/server.ts 文件开始创建应用程序的依赖树,并监视属于此依赖树的所有文件。

这意味着,如果你在应用程序源代码中创建了一个 TypeScript 文件,但在应用程序中从未在任何地方导入它,那么这个文件不会触发任何重载。它将被忽略,就像该文件不存在一样。

确定边界

接下来,hot-hook 将使用配置中的 boundaries 数组来确定符合 HMR 条件的文件。

作为一般规则,你不应将配置文件、服务提供者或预加载文件注册为边界。这是因为这些文件通常会导致一些副作用,如果我们不清除这些副作用就重载它们,这些副作用会重新发生。以下是一些示例:

  • config/database.ts 文件建立与数据库的连接。热重载此文件意味着关闭现有连接并重新创建它。这可以通过重启整个进程来实现,而无需增加任何额外的复杂性。

  • start/routes.ts 文件用于注册路由。热重载此文件意味着移除框架中已注册的现有路由并重新注册它们。同样,重启进程很简单。

换句话说,我们可以说,在 HTTP 请求期间导入/执行的模块应该是 HMR 边界的一部分,而启动应用程序所需的模块则不应该。

执行重载

一旦 hot-hook 确定了边界,它将为属于边界的动态导入模块执行 HMR,并重启进程以处理其余文件。