热模块替换
热模块替换 (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 应用程序的所有关键部分默认都是动态导入的。例如,Controllers、middleware 和事件监听器都是动态导入的,因此,你可以从今天开始利用 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
让我们了解 AdonisJS 何时会执行完全重载(重启进程)以及何时会热重载模块。
创建依赖树
当使用 --hmr 标志时,AdonisJS 将使用 hot-hook 从 bin/server.ts 文件开始创建应用程序的依赖树,并将监视属于此依赖树的所有文件。
这意味着如果你在应用程序源代码中创建了一个 TypeScript 文件,但从未在应用程序的任何地方导入它,这个文件将不会触发任何重载。它将被忽略,就好像该文件不存在一样。
识别边界
接下来,hot-hook 将使用配置中的 boundaries 数组来识别符合 HMR 条件的文件。
作为经验法则,你永远不应该将配置文件、服务提供者或预加载文件注册为边界。这是因为这些文件通常会导致一些副作用,如果我们在不清除副作用的情况下重载它们,这些副作用会再次发生。以下是一些例子:
config/database.ts文件建立与数据库的连接。热重载此文件意味着关闭现有连接并重新创建它。通过重启整个进程可以实现相同的效果,而无需添加任何额外的复杂性。start/routes.ts文件用于注册路由。热重载此文件意味着删除已向框架注册的现有路由并重新注册它们。同样,重启进程更简单。
换句话说,我们可以说在 HTTP 请求期间导入/执行的模块应该是 HMR 边界的一部分,而启动应用程序所需的模块不应该是。
执行重载
一旦 hot-hook 识别了边界,它将对属于边界且动态导入的模块执行 HMR,并对其余文件重启进程。