各位同仁,下午好!
今天,我们将深入探讨 Node.js 世界中一个既基础又充满挑战的话题:模块缓存(Module Caching)以及如何对其进行管理,尤其是为了实现模块热重载(Hot Module Reloading, HMR)。在现代应用开发中,尤其是在前端领域,HMR 已经成为提高开发效率的基石。然而,当我们将目光转向后端,转向 Node.js 服务器时,由于其独特的模块加载和缓存机制,实现真正意义上的 HMR 并非易事。理解这一机制,并掌握清空 require 缓存的方法,是构建响应式、高效开发环境的关键。
我们将从 Node.js 模块系统的核心工作原理出发,逐步揭示 require 缓存的奥秘。随后,我们将直面清空缓存的挑战,并探索从简单到复杂的各种策略,包括手动删除、递归清除、以及借鉴外部工具和高级技术的思路。最终,我们将通过一个实际的 Node.js 服务器热重载示例,将理论付诸实践。
模块缓存的基石:Node.js 的 require 机制
在 Node.js 中,模块是组织代码的基本单位。当我们使用 require() 函数加载一个模块时,Node.js 并非每次都从文件系统读取并执行该模块的代码。为了提高性能和避免重复工作,Node.js 实现了一个强大的模块缓存机制。
require 函数的工作流程
为了理解缓存,我们首先需要了解 require 函数的完整生命周期。当 require(request) 被调用时,Node.js 内部会执行以下几个主要步骤:
- 路径解析 (Resolution):根据
request字符串(可以是相对路径、绝对路径、模块名),Node.js 会解析出模块的完整文件路径。例如,require('./myModule')会被解析为/path/to/current/directory/myModule.js。 - 缓存检查 (Loading from Cache):Node.js 会检查其内部的
require.cache对象。如果解析出的完整文件路径已经作为键存在于require.cache中,并且对应的模块对象已经标记为loaded: true,那么 Node.js 会直接返回该缓存的模块的exports对象,而不会重新加载和执行文件。这是性能优化的核心所在。 - 加载与封装 (Loading and Wrapping):如果模块不在缓存中,Node.js 将从文件系统读取模块的源代码。然后,它会将模块代码封装在一个函数中,这个函数提供了模块特有的局部变量,如
exports、module、require、__filename和__dirname。// 模块代码实际上被包裹成类似这样: (function (exports, require, module, __filename, __dirname) { // 你的模块代码在这里 // 例如:module.exports = { a: 1 }; })(exports, require, module, __filename, __dirname); - 执行 (Execution):封装后的模块代码会被执行。在执行过程中,模块可能会通过
module.exports或exports对象导出其公共接口。 - 缓存 (Caching):模块执行完毕后,其
module对象(包含exports)会被存储到require.cache对象中,以其完整文件路径作为键。同时,module.loaded属性会被设置为true。 - 返回 (Return):最后,
require函数返回缓存中模块的module.exports对象。
这个流程确保了每个模块文件在 Node.js 进程的生命周期中只会被加载和执行一次。
require.cache 对象的剖析
require.cache 是一个全局对象,它存储了所有已加载模块的信息。这是一个普通的 JavaScript 对象,我们可以直接访问和操作它。
console.log(typeof require.cache); // object
console.log(require.cache instanceof Object); // true
require.cache 的键是模块的绝对文件路径,值是 Module 类的实例。每个 Module 实例都包含有关该模块的丰富信息:
| 属性 | 类型 | 描述 |
| :———- | :——- | :————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————**Node.js 模块缓存与热重载:深度解析与实践
引言:Node.js 模块机制与热重载的困境
在软件开发领域,效率是永恒的追求。对于前端开发者而言,模块热重载(Hot Module Replacement, HMR)已是家常便饭,它极大地缩短了开发周期,让我们可以实时看到代码修改的效果,而无需刷新整个应用。然而,当我们谈论 Node.js 后端应用时,HMR 的实现却显得尤为复杂。这并非因为 Node.js 不支持动态加载,而是因为其核心的模块加载机制——require()——自带了一层强大的缓存。
Node.js 的 require() 缓存机制旨在优化性能和保证模块的单例性。当一个模块首次被 require() 时,Node.js 会解析其路径,加载并执行其代码,然后将结果(通常是 module.exports 对象)存储在 require.cache 中。此后,任何对同一模块的 require() 调用都会直接从缓存中获取,而不会重新加载和执行文件。这种设计对于生产环境来说是高效且可靠的,它确保了模块的初始化逻辑只运行一次,并且避免了不必要的磁盘 I/O 和 CPU 开销。
然而,对于开发环境,这种缓存机制却成了 HMR 的主要障碍。当我们在开发过程中修改了一个模块文件,Node.js 进程并不知道这个文件已经被修改。由于缓存的存在,即使我们再次 require() 相同的模块,Node.js 仍然会返回旧的、已缓存的模块实例。这意味着,要让修改后的代码生效,我们通常需要手动重启整个 Node.js 进程。对于大型应用,每次重启可能需要数秒甚至数十秒,这严重拖慢了开发节奏。
因此,理解 Node.js 的模块缓存机制,并掌握如何“清空”或“绕过”这个缓存,是实现 Node.js 后端热重载的关键。本次讲座将深入探讨这些概念,从 require.cache 的内部结构到各种清除策略,再到更高级的 HMR 实现方案,希望能为大家提供一个全面而实用的视角。
第一章:Node.js 模块缓存机制的深层剖析
要有效地管理 require 缓存,我们必须首先对其工作原理有透彻的理解。
1.1 require() 函数的生命周期与缓存点
正如引言中所述,require() 的核心在于其缓存行为。让我们通过一个更详细的步骤分解来精确地定位缓存介入的时机。
-
标准化路径与模块 ID (Module ID Resolution)
当你调用require(request)时,request字符串会被解析成一个唯一的模块标识符(通常是模块的绝对路径)。例如,require('./utils/helper')在app.js中被调用时,如果app.js位于/project/src/app.js,那么helper模块的 ID 可能是/project/src/utils/helper.js。 -
缓存命中检查 (Cache Lookup)
Node.js 会检查全局对象require.cache。require.cache的键就是这些标准化的模块 ID。如果模块 ID 存在于require.cache中,并且对应的Module对象已经被完全加载(module.loaded === true),则直接返回require.cache[id].exports。这是最常见的缓存命中情况。 -
模块加载与创建 (Module Loading & Creation)
如果缓存未命中,Node.js 会创建一个新的Module实例。这个实例会关联到该模块 ID,并被放入require.cache中,但此时module.loaded仍为false。- 文件读取: 从文件系统读取模块的源代码。
- 编译: 根据文件扩展名(
.js,.json,.node等)或文件头部的#!解释器指令,对源代码进行编译。例如,.js文件会被编译成 JavaScript 函数。 - 封装: 将模块代码封装在一个函数中,提供
exports,require,module,__filename,__dirname等局部变量。
-
模块执行 (Module Execution)
封装后的函数被调用执行。在这个阶段,模块中的所有代码都会运行。模块内部通过module.exports或exports对象定义其对外接口。任何在此阶段产生的副作用(如连接数据库、启动定时器、注册全局事件监听器)都会发生。 -
更新缓存状态 (Cache State Update)
模块代码执行完毕后,module.loaded属性被设置为true。此时,模块的最终exports对象已经确定,并存储在require.cache[id].exports中。 -
返回模块导出 (Return Exports)
require()函数返回module.exports对象。
总结一下: 缓存是在模块首次加载并成功执行后,将其结果(module.exports)存储起来,并在后续请求时直接返回的过程。
1.2 require.cache 对象的结构与内容
require.cache 是一个强大的内省工具,它允许我们查看当前 Node.js 进程中所有已加载的模块。
示例代码:观察 require.cache
创建一个 main.js 文件:
// main.js
const path = require('path');
const myModule = require('./myModule'); // 第一次加载 myModule
const anotherModule = require('./anotherModule'); // 第一次加载 anotherModule
console.log('--- After initial requires ---');
console.log('Keys in require.cache:', Object.keys(require.cache).length);
// 打印部分缓存键,进行简化展示
Object.keys(require.cache).slice(0, 5).forEach(key => {
console.log(`- ${key}`);
});
// 再次 require myModule,会从缓存中获取
const myModuleCached = require('./myModule');
console.log('n--- After second require of myModule ---');
console.log('Is myModuleCached === myModule?', myModuleCached === myModule);
// 尝试修改 myModule.js 文件内容,然后不重启程序再次运行此脚本,你会发现 myModule 的内容不会改变。
创建一个 myModule.js 文件:
// myModule.js
console.log('myModule.js is being loaded!');
module.exports = {
message: 'Hello from myModule (initial)',
timestamp: Date.now()
};
创建一个 anotherModule.js 文件:
// anotherModule.js
console.log('anotherModule.js is being loaded!');
module.exports = {
name: 'Another Module',
version: '1.0.0'
};
运行 node main.js:
myModule.js is being loaded!
anotherModule.js is being loaded!
--- After initial requires ---
Keys in require.cache: 50+ (实际数量可能更多,取决于 Node.js 内部加载的模块)
- /project/path/main.js
- /project/path/myModule.js
- /project/path/anotherModule.js
- /.../node_modules/path/path.js (系统模块)
- /.../node_modules/fs/fs.js (系统模块)
...
--- After second require of myModule ---
Is myModuleCached === myModule? true
从输出中我们可以观察到:
myModule.js is being loaded!只打印了一次,证明第二次require('./myModule')确实走了缓存。require.cache中存储了main.js、myModule.js、anotherModule.js的完整路径作为键,以及 Node.js 内部使用的各种系统模块(如path,fs)的路径。
Module 对象的详细结构
require.cache 中每个键对应的值都是一个 Module 类的实例。以下是其主要属性:
// 假设这是 myModule 的 Module 实例
const myModuleInstance = require.cache[path.resolve(__dirname, 'myModule.js')];
console.log(myModuleInstance);
/*
Module {
id: '/project/path/myModule.js',
path: '/project/path',
exports: { message: 'Hello from myModule (initial)', timestamp: 1678886400000 },
filename: '/project/path/myModule.js',
loaded: true,
children: [], // 这个模块直接依赖的其他模块
paths: [ // 模块查找路径
'/project/path/node_modules',
'/project/node_modules',
'/node_modules'
],
parent: Module { /* ... main.js Module instance ... */ }, // 引入当前模块的父模块
_compile: [Function: _compile],
_dirname: '/project/path',
_filename: '/project/path/myModule.js',
_exports: { message: 'Hello from myModule (initial)', timestamp: 1678886400000 },
_cachedExports: { message: 'Hello from myModule (initial)', timestamp: 1678886400000 }
}
*/
关键属性包括:
id:模块的唯一标识符,通常是绝对路径。exports:模块导出的对象。这是require()调用实际返回的值。filename:模块文件的绝对路径。loaded:一个布尔值,指示模块是否已完全加载和执行。parent:引用该模块的父模块的Module实例。这对于构建依赖图非常重要。children:一个数组,包含该模块直接require的所有子模块的Module实例。
通过 parent 和 children 属性,我们可以构建出一个模块依赖关系图,这对于实现智能热重载至关重要。
1.3 本地模块与核心模块的缓存差异
Node.js 的模块分为几类:
- 核心模块 (Native Modules):如
fs,path,http等,它们是 Node.js 内置的模块,通常以二进制形式实现,在 Node.js 启动时就已预加载并缓存。它们不会出现在require.cache中,或者说,即使出现在require.cache中,对其进行删除也无效,因为它们的加载机制与用户态模块不同。我们几乎不需要,也无法对其进行热重载。 - 文件模块 (File Modules):我们自己编写的
.js,.json文件,或者node_modules中的第三方库。这些是require.cache主要管理的对象。
当我们谈论清空 require 缓存时,我们主要针对的是文件模块。
第二章:热重载的挑战:清空缓存并非一帆风顺
既然 require.cache 只是一个普通的 JavaScript 对象,那么直观的想法就是直接删除其中的条目。例如,如果我们修改了 myModule.js,我们可能会尝试:
delete require.cache[path.resolve(__dirname, 'myModule.js')];
const newMyModule = require('./myModule'); // 再次加载
理论上,这应该能让 Node.js 重新加载 myModule.js。然而,实践中,这往往不足以实现真正的热重载。挑战主要来源于以下几个方面:
2.1 依赖链问题:父模块持有旧引用
这是最常见也是最棘手的问题。假设 main.js 依赖 moduleA.js,而 moduleA.js 又依赖 moduleB.js。如果 moduleB.js 发生了变化,我们只是简单地清除了 moduleB.js 的缓存并重新 require 它,moduleA.js 仍然会持有对旧的 moduleB.js 导出对象的引用。
示例代码:父模块持有旧引用
main.js:
// main.js
const path = require('path');
const moduleA = require('./moduleA');
function startApp() {
console.log('App started. moduleA says:', moduleA.getMessage());
}
startApp();
// 模拟文件变化后,清除 moduleB 缓存并重载
// 实际应用中,这会由文件监听器触发
setTimeout(() => {
console.log('n--- Simulating moduleB.js change & reload ---');
const moduleBPath = path.resolve(__dirname, 'moduleB.js');
if (require.cache[moduleBPath]) {
console.log(`Clearing cache for ${moduleBPath}`);
delete require.cache[moduleBPath];
}
// 重新加载 moduleB (但没人 require 它,所以它不会被重新执行)
// 甚至手动 require('./moduleB') 也会得到新版本,但 moduleA 仍然使用旧版本。
require('./moduleB'); // 这一步是无效的,因为 moduleA 已经加载
console.log('After reloading moduleB, moduleA still says:', moduleA.getMessage());
console.log('Expected: "Hello from B (new)", Actual: "Hello from B (old)"');
// 真正的解决方案是重新加载 moduleA 及其所有依赖它的模块
// 但这需要更复杂的逻辑
}, 2000);
moduleA.js:
// moduleA.js
console.log('moduleA.js is being loaded!');
const moduleB = require('./moduleB');
module.exports = {
getMessage: () => `Hello from A, and ${moduleB.getBMessage()}`
};
moduleB.js (初始版本):
// moduleB.js
console.log('moduleB.js is being loaded!');
module.exports = {
getBMessage: () => 'Hello from B (old)'
};
运行 node main.js。
在 setTimeout 内部,我们手动修改 moduleB.js 为:
// moduleB.js (新版本)
console.log('moduleB.js is being loaded! (NEW)');
module.exports = {
getBMessage: () => 'Hello from B (new)'
};
然后再次运行 node main.js。你会发现,尽管我们“清空”了 moduleB 的缓存,但 moduleA 仍然打印出旧的 moduleB 消息。这是因为 moduleA 在首次加载时已经捕获了 moduleB 的 exports 对象引用,这个引用并不会因为 require.cache 中 moduleB 的条目被删除而自动更新。
要解决这个问题,不仅要清除 moduleB 的缓存,还需要清除所有直接或间接依赖 moduleB 的模块的缓存(在这个例子中是 moduleA),然后重新加载它们。
2.2 副作用与全局状态
模块在首次执行时可能会产生各种副作用:
- 注册事件监听器:例如
process.on('SIGINT', ...)或emitter.on('data', ...)。如果模块被热重载,旧的监听器不会自动移除,新的监听器会被再次添加,可能导致事件重复触发或内存泄漏。 - 修改全局对象:如
global.config = { ... }。 - 创建单例实例:例如数据库连接池、日志器实例。重新加载模块不会替换已经创建的单例对象,除非模块本身有机制来处理这种情况。
- 启动服务器/服务:例如在一个模块中
http.createServer().listen(...)。热重载时,如果不关闭旧的服务器,将导致端口冲突。
这些副作用在模块重新加载时不会被“撤销”,这要求模块在设计时就考虑如何清理自身状态。
2.3 循环依赖
如果模块 A 依赖模块 B,同时模块 B 也依赖模块 A,就会形成循环依赖。Node.js 对循环依赖有特定的处理方式:当一个模块开始加载时,它会被放入缓存,但其 exports 对象可能尚未完全填充。如果此时另一个模块 require 了它,Node.js 会返回一个未完全填充的 exports 对象。
在热重载场景下,循环依赖会使缓存清除和重新加载的顺序变得更加复杂,甚至可能导致程序崩溃或进入无限循环。
2.4 其他模块的直接引用
除了 require.cache,其他模块可能直接持有对已加载模块内部函数或对象的引用。即使你清除了缓存并重新加载了模块,这些旧的引用仍然指向旧的函数或对象。这在 Node.js 中尤为常见,因为 JavaScript 是传引用语言。
// util.js
let counter = 0;
module.exports = {
increment: () => ++counter,
getCounter: () => counter
};
// app.js
const util = require('./util');
const incrementFn = util.increment; // app.js 持有了 util.increment 的直接引用
console.log(util.getCounter()); // 0
incrementFn();
console.log(util.getCounter()); // 1
// 假设 util.js 被修改并清除了缓存
// delete require.cache[resolvedUtilPath];
// const newUtil = require('./util'); // newUtil.getCounter() 将是 0
// 但 incrementFn() 仍然会操作旧的 counter 变量!
// incrementFn();
// console.log(newUtil.getCounter()); // 0 (旧的 counter 变量被 incrementFn 修改,但新的 util 实例中的 counter 仍然是 0)
这个例子展示了,即使模块被重新加载,通过直接引用捕获的函数或对象仍然指向旧的实例。这是 HMR 在 Node.js 中最深层的挑战之一,也是许多 HMR 库需要采用代理(Proxy)模式等高级技术来解决的原因。
第三章:Node.js 模块缓存清除策略
理解了挑战后,我们可以开始探讨各种清除 require 缓存的策略。这些策略各有优缺点,适用于不同的场景和复杂程度。
3.1 策略 A:直接删除目标模块缓存(Naive Approach)
这是最简单直接的方法,适用于模块没有父依赖,或者父模块也需要同时被重载的简单场景。
机制:
直接通过 delete require.cache[modulePath] 移除指定模块的缓存条目。当该模块再次被 require() 时,Node.js 会重新加载并执行它。
优点:
- 实现简单,代码量少。
- 对于独立、无外部依赖的模块(例如某些配置文件的加载函数),可能足够。
缺点:
- 不处理依赖问题:这是最大的缺陷,如第二章所述,父模块会继续使用旧的引用。
- 不处理副作用和全局状态。
代码示例:
targetModule.js:
// targetModule.js
console.log('targetModule.js loaded at:', new Date().toLocaleTimeString());
let value = 1;
module.exports = {
getValue: () => value++,
setValue: (v) => { value = v; }
};
main.js:
// main.js
const path = require('path');
const targetPath = path.resolve(__dirname, 'targetModule.js');
let moduleInstance = require('./targetModule');
console.log('Initial value:', moduleInstance.getValue()); // 1
console.log('n--- Simulating file change and naive reload ---');
setTimeout(() => {
// 假设 targetModule.js 被修改了
// 实际场景中,我们会重新 require
// 1. 清除缓存
if (require.cache[targetPath]) {
console.log(`Clearing cache for: ${targetPath}`);
delete require.cache[targetPath];
} else {
console.log(`Cache entry for ${targetPath} not found.`);
}
// 2. 重新 require 模块
moduleInstance = require('./targetModule'); // 重新加载
console.log('Value after naive reload:', moduleInstance.getValue()); // 1 (因为模块被重载,内部状态重置)
// 注意:如果 main.js 依赖 targetModule.js 的某个函数,
// 且 main.js 本身没有重载,它仍然会持有旧的函数引用。
// 在此示例中,我们直接更新了 moduleInstance 变量,所以看起来是新的。
// 但如果有一个 parentModule 引用了它,就会有问题。
}, 1000);
运行 node main.js。输出会显示模块被重新加载,并且 getValue() 返回 1,表明内部状态被重置。但请记住,这只是因为 main.js 直接重新赋值了 moduleInstance 变量。如果存在间接依赖,这种方法是不足的。
3.2 策略 B:递归清除模块及其子模块缓存
为了解决父模块持有旧引用导致的问题,我们至少需要清除被修改模块及其所有子模块的缓存。
机制:
通过遍历 module.children 属性,递归地删除一个模块及其所有直接或间接依赖的子模块的缓存。
算法:
- 获取目标模块的
Module实例。 - 递归函数
clearChildren(module):
a. 遍历module.children数组。
b. 对每个子模块,如果它存在于require.cache中,则递归调用clearChildren。
c. 删除当前子模块的缓存条目:delete require.cache[childModule.id]。 - 在调用
clearChildren后,删除目标模块自身的缓存条目。
优点:
- 解决了直接依赖的子模块无法更新的问题。
- 相对于完全重载整个应用,更为精细。
缺点:
- 仍未解决父模块问题:如果模块 A 依赖模块 B,模块 B 改变,该策略会清除 B 的缓存。但 A 仍然持有旧的 B 的引用,因为 A 没有被清除缓存。
- 不处理副作用和全局状态。
代码示例:
utils/clearCache.js:
// utils/clearCache.js
const path = require('path');
function clearModuleAndChildrenCache(modulePath) {
const resolvedPath = path.resolve(modulePath);
const module = require.cache[resolvedPath];
if (module) {
// 递归清除所有子模块的缓存
module.children.forEach(child => {
clearModuleAndChildrenCache(child.id); // 递归调用
});
// 删除父模块自身的缓存
console.log(` - Deleting cache for: ${resolvedPath}`);
delete require.cache[resolvedPath];
} else {
console.log(` - Cache entry not found for: ${resolvedPath}`);
}
}
// 辅助函数,用于获取模块的绝对路径
function resolveModulePath(relativePath) {
return path.resolve(process.cwd(), relativePath);
}
module.exports = {
clearModuleAndChildrenCache,
resolveModulePath
};
moduleB.js:
// moduleB.js
console.log('moduleB.js loaded at:', new Date().toLocaleTimeString());
module.exports = {
getBMessage: () => 'Hello from B (old)'
};
moduleA.js:
// moduleA.js
console.log('moduleA.js loaded at:', new Date().toLocaleTimeString());
const moduleB = require('./moduleB');
module.exports = {
getMessage: () => `Hello from A, and ${moduleB.getBMessage()}`
};
main.js:
// main.js
const { clearModuleAndChildrenCache, resolveModulePath } = require('./utils/clearCache');
let moduleA = require('./moduleA');
console.log('Initial message:', moduleA.getMessage());
console.log('n--- Simulating moduleB.js change and recursive child reload ---');
setTimeout(() => {
// 假设 moduleB.js 改变了,我们修改它的内容
// (手动修改 moduleB.js 为 'Hello from B (new)')
// 1. 清除 moduleB 的缓存及其子模块(如果有的话)
const moduleBPath = resolveModulePath('./moduleB.js');
console.log(`Attempting to clear cache for moduleB (${moduleBPath})...`);
clearModuleAndChildrenCache(moduleBPath);
// 2. 重新 require moduleA (moduleA 仍然持有旧的 moduleB 引用)
// moduleA = require('./moduleA'); // 这一行无法解决问题
console.log('Message after clearing moduleB cache:', moduleA.getMessage());
console.log('Expected: "Hello from B (new)", Actual: "Hello from B (old)"');
console.log('n--- Correcting: Also clearing moduleA cache and re-requiring both ---');
const moduleAPath = resolveModulePath('./moduleA.js');
clearModuleAndChildrenCache(moduleAPath); // 清除 moduleA 及其子模块 (moduleB 已经被清除)
moduleA = require('./moduleA'); // 重新加载 moduleA
console.log('Message after clearing moduleA and moduleB cache:', moduleA.getMessage());
// 如果你在 setTimeout 期间修改了 moduleB.js 的内容,这里会是新的消息。
}, 1000);
运行 node main.js。在 setTimeout 期间,手动将 moduleB.js 中的 getBMessage 改为 'Hello from B (new)'。
你会发现,即使 moduleB 的缓存被清除,moduleA 仍然打印旧消息。只有当 moduleA 的缓存也被清除并重新加载后,它才会获取到新的 moduleB 引用。这引出了下一种更复杂的策略。
3.3 策略 C:高级递归清除:清除目标模块及其所有依赖它的模块(Parents)
这是最接近 HMR 实际需求的策略,它旨在解决父模块持有旧引用的问题。它需要构建一个反向依赖图,即找到所有依赖于被修改模块的模块。
机制:
当一个模块文件发生变化时,不仅要清除该模块本身的缓存,还要递归地清除所有直接或间接引用了该模块的父模块的缓存。这通常通过跟踪 module.parent 属性来实现。
算法 (概念性):
- 构建依赖图 (或反向依赖图):Node.js 的
module.parent和module.children属性在运行时形成了模块依赖图。我们可以遍历require.cache来构建一个完整的依赖图,或者更有效率地,构建一个反向依赖图(即记录每个模块被哪些模块所依赖)。 - 文件变更检测:使用文件监听器(如
chokidar)检测到某个文件X.js发生变化。 - 识别受影响模块:找到
X.js对应的Module实例。 - 递归清除父模块:从
X.js开始,沿着module.parent链向上追溯,收集所有父模块。对于每个父模块,再递归地收集其所有父模块,直到根模块(通常是main.js或顶层启动文件)。 - 按序清除与重载:收集到的所有受影响模块,包括
X.js本身,都需要从require.cache中删除。删除顺序通常是从最顶层的父模块开始,向下到最底层的子模块,以确保重新require时能正确获取到最新版本。然后,重新require根模块或入口点,这将触发整个受影响链的重新加载。
优点:
- 有效地解决了父模块持有旧引用的问题,确保了整个依赖链都能获取到最新版本的模块。
- 更接近于真正的 HMR 体验。
缺点:
- 实现复杂:需要维护一个动态的依赖图,或在每次变更时遍历
require.cache构建。 - 性能开销:对于大型应用,每次变更可能导致大量模块被清除和重新加载。
- 副作用和单例问题仍未彻底解决:虽然模块被重新加载,但如果它们在执行时创建了全局状态、注册了事件监听器或产生了单例实例,这些旧的副作用可能仍然存在,甚至会与新的副作用叠加。模块需要有明确的清理机制。
代码示例 (简化版,侧重清除逻辑):
为了更好地演示,我们需要一个稍微复杂点的依赖链。
utils/hmrClearer.js:
// utils/hmrClearer.js
const path = require('path');
/**
* 递归查找所有依赖于给定模块的模块 (父模块)。
* @param {string} moduleId - 目标模块的绝对路径。
* @param {Set<string>} visited - 已访问过的模块 ID 集合,避免循环依赖导致无限递归。
* @param {Array<string>} dependents - 存储找到的依赖模块的 ID 列表。
*/
function findModuleDependents(moduleId, visited = new Set(), dependents = []) {
if (visited.has(moduleId)) {
return;
}
visited.add(moduleId);
// 遍历所有已加载的模块,寻找它们的子模块中是否包含 moduleId
for (const cachedModuleId in require.cache) {
if (cachedModuleId === moduleId) continue; // 跳过目标模块自身
const cachedModule = require.cache[cachedModuleId];
if (!cachedModule || !cachedModule.children) continue;
const isDependent = cachedModule.children.some(child => child.id === moduleId);
if (isDependent) {
if (!dependents.includes(cachedModuleId)) {
dependents.push(cachedModuleId);
}
// 递归查找这个父模块的父模块
findModuleDependents(cachedModuleId, visited, dependents);
}
}
return dependents;
}
/**
* 清除指定模块及其所有依赖它的模块的缓存。
* 这将确保当目标模块更新后,所有使用它的地方都能获取到新版本。
* @param {string} modulePath - 要清除缓存的模块的相对或绝对路径。
*/
function clearModuleAndDependentsCache(modulePath) {
const resolvedPath = path.resolve(modulePath);
const moduleToClear = require.cache[resolvedPath];
if (!moduleToClear) {
console.warn(`[HMR] Module not found in cache: ${resolvedPath}`);
return;
}
console.log(`[HMR] Identifying dependents for: ${resolvedPath}`);
const dependents = findModuleDependents(resolvedPath);
// 将目标模块也加入到待清除列表
if (!dependents.includes(resolvedPath)) {
dependents.push(resolvedPath);
}
// 从顶层依赖开始清除,这样在重新 require 时可以按正确顺序加载
// 这里简单地对收集到的依赖进行排序,通常是按路径层级
// 实际的 HMR 库会构建更精细的拓扑排序
dependents.sort((a, b) => {
const aDepth = a.split(path.sep).length;
const bDepth = b.split(path.sep).length;
return aDepth - bDepth; // 深度小的(更顶层)先清除,以便后续重载时能被其父模块正确 require
});
console.log(`[HMR] Modules to clear (and their dependents):`);
dependents.forEach(depId => {
console.log(` - ${depId}`);
delete require.cache[depId];
});
console.log(`[HMR] Cache cleared for ${dependents.length} modules.`);
}
// 辅助函数,用于获取模块的绝对路径
function resolveModulePath(relativePath) {
return path.resolve(process.cwd(), relativePath);
}
module.exports = {
clearModuleAndDependentsCache,
resolveModulePath
};
service/dataService.js:
// service/dataService.js
console.log('dataService.js loaded at:', new Date().toLocaleTimeString());
let data = { count: 0, status: 'initial' };
module.exports = {
getData: () => ({ ...data }), // 返回副本,避免外部直接修改
updateCount: () => {
data.count++;
data.status = `updated at ${new Date().toLocaleTimeString()}`;
return data.count;
}
};
api/userService.js:
// api/userService.js
console.log('userService.js loaded at:', new Date().toLocaleTimeString());
const dataService = require('../service/dataService');
module.exports = {
getUserInfo: () => `User info based on data: ${JSON.stringify(dataService.getData())}`,
processUserRequest: () => {
dataService.updateCount();
return `Request processed. New count: ${dataService.getData().count}`;
}
};
main.js:
// main.js - 模拟一个简单的 HTTP 服务器
const http = require('http');
const path = require('path');
const { clearModuleAndDependentsCache, resolveModulePath } = require('./utils/hmrClearer');
const chokidar = require('chokidar'); // 需要安装 npm install chokidar
let server;
let currentRouter = require('./router'); // 初始加载路由
function startServer() {
if (server) {
server.close(() => {
console.log('[HMR] Old server closed.');
_startNewServer();
});
} else {
_startNewServer();
}
}
function _startNewServer() {
server = http.createServer((req, res) => {
currentRouter(req, res); // 使用当前的路由处理函数
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(`[HMR] Server listening on port ${PORT}.`);
console.log(`[HMR] Try: curl http://localhost:${PORT}/user`);
console.log(`[HMR] Or: curl http://localhost:${PORT}/product`);
});
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.error(`[HMR] Port ${PORT} is already in use. Retrying...`);
setTimeout(() => {
server.close();
startServer();
}, 1000);
} else {
console.error(`[HMR] Server error: ${err.message}`);
}
});
}
// 路由模块
// router.js:
// const userService = require('./api/userService');
// const productService = require('./api/productService'); // 假设存在
// module.exports = (req, res) => {
// if (req.url === '/user') {
// res.end(userService.getUserInfo());
// } else if (req.url === '/product') {
// res.end('Product info (not implemented yet)');
// } else {
// res.statusCode = 404;
// res.end('Not Found');
// }
// };
// 启动服务器
startServer();
// 监听文件变化以实现热重载
const watcher = chokidar.watch(['./api/**/*.js', './service/**/*.js', './router.js'], {
ignored: /node_modules/,
persistent: true
});
watcher.on('change', (filePath) => {
console.log(`n[HMR] File changed: ${filePath}`);
const absolutePath = path.resolve(filePath);
// 清除模块及其所有父模块的缓存
clearModuleAndDependentsCache(absolutePath);
// 重新加载主路由模块,这将间接导致其依赖链被重新加载
console.log('[HMR] Re-requiring main router module...');
try {
currentRouter = require('./router');
console.log('[HMR] Router reloaded successfully. Restarting server...');
startServer(); // 重启服务器以使用新的路由逻辑
} catch (e) {
console.error('[HMR] Error during module reload:', e);
}
});
process.on('SIGINT', () => {
console.log('[HMR] Shutting down watcher and server...');
watcher.close();
if (server) {
server.close(() => {
console.log('[HMR] Server closed. Exiting.');
process.exit(0);
});
} else {
process.exit(0);
}
});
router.js:
// router.js
console.log('router.js loaded at:', new Date().toLocaleTimeString());
const userService = require('./api/userService');
module.exports = (req, res) => {
if (req.url === '/user') {
res.end(userService.getUserInfo());
} else if (req.url === '/test') { // 初始没有这个路由
res.end('This is a test route!');
}
else {
res.statusCode = 404;
res.end('Not Found');
}
};
运行步骤:
- 安装
chokidar:npm install chokidar - 创建
router.js、api/userService.js、service/dataService.js、utils/hmrClearer.js和main.js。 - 运行
node main.js。 - 在浏览器或命令行访问
http://localhost:3000/user,你会看到dataService的初始状态。 - 修改
service/dataService.js,例如将count初始值改为 100,或者修改updateCount逻辑。保存。 - 观察
main.js的控制台输出,会显示文件变更、缓存清除和服务器重启。 - 再次访问
http://localhost:3000/user,你会发现dataService的变化已经生效。 - 修改
router.js,添加一个新的路由/test,保存。 - 访问
http://localhost:3000/test,新路由应该已经生效。
这个示例展示了如何通过 chokidar 监听文件变化,然后使用 clearModuleAndDependentsCache 函数清除受影响模块的缓存,并重新加载入口点(这里是 router.js),最后重启 HTTP 服务器来应用这些变更。这是实现 Node.js HMR 的一个基本框架。
3.4 策略 D:清除所有非核心模块("Nuclear" Option)
如果对精确控制模块依赖关系感到厌烦,或者热重载的范围非常广,可以采取这种“一刀切”的方案。
机制:
遍历 require.cache,删除所有不是 Node.js 核心模块的条目。
优点:
- 实现简单。
- 确保所有用户态模块都能被重新加载,无需担心复杂的依赖链。
缺点:
- 效率低下:每次变更都会重新加载整个应用的所有模块,开销大。
- 副作用问题依然存在:所有模块都会重新执行,如果它们有副作用,这些副作用会再次发生,旧的副作用不会被清理。
- 核心模块不会被清除,
node_modules下的模块也可能不应该被频繁清除。
代码示例:
// utils/clearAllCache.js
const path = require('path');
function clearAllNonNativeModulesCache() {
console.log('[HMR] Clearing all non-native module caches...');
let clearedCount = 0;
for (const key in require.cache) {
// 排除 Node.