Node.js 中的模块缓存:如何清空 require 缓存以实现模块热重载

各位同仁,下午好!

今天,我们将深入探讨 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 内部会执行以下几个主要步骤:

  1. 路径解析 (Resolution):根据 request 字符串(可以是相对路径、绝对路径、模块名),Node.js 会解析出模块的完整文件路径。例如,require('./myModule') 会被解析为 /path/to/current/directory/myModule.js
  2. 缓存检查 (Loading from Cache):Node.js 会检查其内部的 require.cache 对象。如果解析出的完整文件路径已经作为键存在于 require.cache 中,并且对应的模块对象已经标记为 loaded: true,那么 Node.js 会直接返回该缓存的模块的 exports 对象,而不会重新加载和执行文件。这是性能优化的核心所在。
  3. 加载与封装 (Loading and Wrapping):如果模块不在缓存中,Node.js 将从文件系统读取模块的源代码。然后,它会将模块代码封装在一个函数中,这个函数提供了模块特有的局部变量,如 exportsmodulerequire__filename__dirname
    // 模块代码实际上被包裹成类似这样:
    (function (exports, require, module, __filename, __dirname) {
        // 你的模块代码在这里
        // 例如:module.exports = { a: 1 };
    })(exports, require, module, __filename, __dirname);
  4. 执行 (Execution):封装后的模块代码会被执行。在执行过程中,模块可能会通过 module.exportsexports 对象导出其公共接口。
  5. 缓存 (Caching):模块执行完毕后,其 module 对象(包含 exports)会被存储到 require.cache 对象中,以其完整文件路径作为键。同时,module.loaded 属性会被设置为 true
  6. 返回 (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() 的核心在于其缓存行为。让我们通过一个更详细的步骤分解来精确地定位缓存介入的时机。

  1. 标准化路径与模块 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

  2. 缓存命中检查 (Cache Lookup)
    Node.js 会检查全局对象 require.cacherequire.cache 的键就是这些标准化的模块 ID。如果模块 ID 存在于 require.cache 中,并且对应的 Module 对象已经被完全加载(module.loaded === true),则直接返回 require.cache[id].exports。这是最常见的缓存命中情况。

  3. 模块加载与创建 (Module Loading & Creation)
    如果缓存未命中,Node.js 会创建一个新的 Module 实例。这个实例会关联到该模块 ID,并被放入 require.cache 中,但此时 module.loaded 仍为 false

    • 文件读取: 从文件系统读取模块的源代码。
    • 编译: 根据文件扩展名(.js, .json, .node 等)或文件头部的 #! 解释器指令,对源代码进行编译。例如,.js 文件会被编译成 JavaScript 函数。
    • 封装: 将模块代码封装在一个函数中,提供 exports, require, module, __filename, __dirname 等局部变量。
  4. 模块执行 (Module Execution)
    封装后的函数被调用执行。在这个阶段,模块中的所有代码都会运行。模块内部通过 module.exportsexports 对象定义其对外接口。任何在此阶段产生的副作用(如连接数据库、启动定时器、注册全局事件监听器)都会发生。

  5. 更新缓存状态 (Cache State Update)
    模块代码执行完毕后,module.loaded 属性被设置为 true。此时,模块的最终 exports 对象已经确定,并存储在 require.cache[id].exports 中。

  6. 返回模块导出 (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.jsmyModule.jsanotherModule.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 实例。

通过 parentchildren 属性,我们可以构建出一个模块依赖关系图,这对于实现智能热重载至关重要。

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 在首次加载时已经捕获了 moduleBexports 对象引用,这个引用并不会因为 require.cachemoduleB 的条目被删除而自动更新。

要解决这个问题,不仅要清除 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 属性,递归地删除一个模块及其所有直接或间接依赖的子模块的缓存。

算法:

  1. 获取目标模块的 Module 实例。
  2. 递归函数 clearChildren(module):
    a. 遍历 module.children 数组。
    b. 对每个子模块,如果它存在于 require.cache 中,则递归调用 clearChildren
    c. 删除当前子模块的缓存条目:delete require.cache[childModule.id]
  3. 在调用 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 属性来实现。

算法 (概念性):

  1. 构建依赖图 (或反向依赖图):Node.js 的 module.parentmodule.children 属性在运行时形成了模块依赖图。我们可以遍历 require.cache 来构建一个完整的依赖图,或者更有效率地,构建一个反向依赖图(即记录每个模块被哪些模块所依赖)。
  2. 文件变更检测:使用文件监听器(如 chokidar)检测到某个文件 X.js 发生变化。
  3. 识别受影响模块:找到 X.js 对应的 Module 实例。
  4. 递归清除父模块:从 X.js 开始,沿着 module.parent 链向上追溯,收集所有父模块。对于每个父模块,再递归地收集其所有父模块,直到根模块(通常是 main.js 或顶层启动文件)。
  5. 按序清除与重载:收集到的所有受影响模块,包括 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');
    }
};

运行步骤:

  1. 安装 chokidar: npm install chokidar
  2. 创建 router.jsapi/userService.jsservice/dataService.jsutils/hmrClearer.jsmain.js
  3. 运行 node main.js
  4. 在浏览器或命令行访问 http://localhost:3000/user,你会看到 dataService 的初始状态。
  5. 修改 service/dataService.js,例如将 count 初始值改为 100,或者修改 updateCount 逻辑。保存。
  6. 观察 main.js 的控制台输出,会显示文件变更、缓存清除和服务器重启。
  7. 再次访问 http://localhost:3000/user,你会发现 dataService 的变化已经生效。
  8. 修改 router.js,添加一个新的路由 /test,保存。
  9. 访问 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.

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注