热模块替换 (Hot module replacement)
热模块替换 (HMR) 是指在修改后重新加载 JavaScript 模块,而无需重新启动整个进程。HMR 通常会带来更快的反馈循环,因为在文件更改后,您不必等待整个进程重新启动。
HMR 这个术语在前端生态系统中已经使用了很多年,像 Vite 这样的工具可以热重载模块并将更改应用于网页,同时保持其现有状态。
然而,AdonisJS 执行的 HMR 要简单得多,并且与 Vite 或 Webpack 等工具截然不同。我们使用 HMR 的目标是提供更快的重载,仅此而已。
关键概念
没有更新传播到浏览器
由于 AdonisJS 是一个后端框架,我们不负责维护前端应用程序的状态或将 CSS 应用于网页。因此,我们的 HMR 集成无法与您的前端应用程序对话并协调其状态。
事实上,并非每个 AdonisJS 应用程序都是浏览器渲染的 Web 应用程序。许多人使用 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-hook 创建应用程序的依赖树,从 bin/server.ts 文件开始,并将监视属于此依赖树的所有文件。
这意味着,如果您在应用程序源代码中创建一个 TypeScript 文件,但从未在应用程序中的任何位置导入它,则此文件将不会触发任何重载。它将被忽略,就好像该文件不存在一样。
识别边界
接下来,hot-hook 将使用配置中的 boundaries 数组来识别符合 HMR 条件的文件。
根据经验,您不应将配置文件、服务提供者或预加载文件注册为边界。这是因为这些文件通常会导致一些副作用,如果我们重载它们而不清除副作用,这些副作用将会再次发生。以下是一些示例:
-
config/database.ts文件建立与数据库的连接。热重载此文件意味着关闭现有连接并重新创建它。通过重新启动整个进程也可以实现同样的效果,而无需增加任何额外的复杂性。 -
start/routes.ts文件用于注册路由。热重载此文件意味着删除向框架注册的现有路由并重新注册它们。同样,重新启动进程很简单。
换句话说,我们可以说在 HTTP 请求期间导入/执行的模块应该是 HMR 边界的一部分,而启动应用程序所需的模块不应该是。
执行重载
一旦 hot-hook 识别出边界,它将对属于边界的动态导入模块执行 HMR,并对其余文件重新启动进程。