You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js应用热更新函数问题:文件未更新及目录遍历困难求助

Node.js热重载实现问题与优化方案

问题描述

我编写了refreshFolders函数,期望实现Node.js应用更新时无需重启服务器,但目前文件修改后并未生效,推测可能是消息堆积导致,但无法确定;同时遍历所有文件与文件夹的实现过于繁琐,想寻求更优的实现方式。

现有代码

refreshFolders() {
        try {
          const folders = fs.readdirSync(path.join(__dirname, "..", ".."));

          for (const folder of folders) {
            fs.readdirSync(path.resolve(__dirname, "..", ".."), { withFileTypes: true })
              .filter((type) => {
                if (type.isFile()) {
                  fs.watchFile(path.resolve(__dirname, "..", "..", type.name), (curr, prev) => {
                    delete require.cache[path.resolve(__dirname, "..", "..", type.name)];
                    utils.logger("info", `${type.name} updating...`);
                  });
                }

                if (type.isDirectory()) {
                  fs.watch(path.resolve(__dirname, "..", "..", type.name), (event_name, file_name) => {
                    delete require.cache[path.resolve(__dirname, "..", "..", type.name, file_name)];
                    utils.logger("info", `${file_name} updating...`);
                  });

                  fs.readdirSync(path.resolve(__dirname, "..", "..", type.name), { withFileTypes: true })
                    .filter((secondType) => {
                      if (secondType.isFile()) {
                        fs.watchFile(path.resolve(__dirname, "..", "..", type.name, secondType.name), (curr, prev) => {
                          delete require.cache[path.resolve(__dirname, "..", "..", type.name, secondType.name)];
                          utils.logger("info", `${secondType.name} updating...`);
                        });
                      }

                      if (secondType.isDirectory()) {
                        fs.watch(path.resolve(__dirname, "..", "..", type.name, secondType.name), (second_event_name, second_file_name) => {

                          delete require.cache[path.resolve(__dirname, "..", "..", type.name, secondType.name, second_file_name)];
                          utils.logger("info", `${second_file_name} updating...`);
                        });

                        fs.readdirSync(path.resolve(__dirname, "..", "..", type.name, secondType.name), { withFileTypes: true })
                          .filter((threeType) => {
                            if (threeType.isDirectory()) {
                              fs.watch(path.resolve(__dirname, "..", "..", type.name, secondType.name, threeType.name), (three_event_name, three_file_name) => {

                                delete require.cache[path.resolve(__dirname, "..", "..", type.name, threeType.name, three_file_name)];
                                utils.logger("info", `${three_file_name} updating...`);
                              });
                            }
                            
                            if (threeType.isFile()) {
                              fs.watchFile(path.resolve(__dirname, "..", "..", type.name, secondType.name, threeType.name), (curr, prev) => {
                                delete require.cache[path.resolve(__dirname, "..", "..", type.name, secondType.name, threeType.name)];
                                utils.logger("info", `${threeType.name} updating...`);
                              })
                            }
                          })
                      }
                    })
                }
              })
          }
        } catch (error) {
          utils.logger("error", error.stack);
        }
      }

现有代码的核心问题

  • 层级硬编码:仅处理到三级目录,更深层级的子目录完全未覆盖,导致部分文件修改无法被监听
  • API混用且低效:fs.watchFile采用轮询机制性能差,与fs.watch混用易触发重复事件;未处理fs.watch的event_name,文件重命名/删除时会出现file_name为空的情况
  • 缓存删除路径错误:监听目录时直接拼接file_name,若文件在子目录中,路径拼接会出错,导致缓存未正确删除
  • 冗余遍历:外层已读取根目录内容,内层重复读取同一目录,完全没必要

优化实现方案

采用递归遍历处理任意层级目录,统一使用fs.watch监听,同时正确处理缓存删除与模块重新加载逻辑:

const fs = require('fs');
const path = require('path');

function watchDirectory(dirPath) {
  // 监听当前目录
  fs.watch(dirPath, { recursive: false }, (eventType, fileName) => {
    if (!fileName) return; // 忽略无文件名的事件
    const fullPath = path.resolve(dirPath, fileName);

    fs.stat(fullPath, (err, stats) => {
      if (err) return;

      if (stats.isFile()) {
        // 删除对应模块缓存
        if (require.cache[fullPath]) {
          delete require.cache[fullPath];
          utils.logger("info", `${fileName} 缓存已清除,下次加载将更新`);
        }
      } else if (stats.isDirectory()) {
        // 递归监听新创建的子目录
        watchDirectory(fullPath);
      }
    });
  });

  // 遍历当前目录内容,初始化监听
  fs.readdirSync(dirPath, { withFileTypes: true }).forEach(item => {
    const fullPath = path.resolve(dirPath, item.name);
    if (item.isDirectory()) {
      watchDirectory(fullPath);
    } else if (item.isFile()) {
      // 初始化文件监听
      fs.watch(fullPath, () => {
        if (require.cache[fullPath]) {
          delete require.cache[fullPath];
          utils.logger("info", `${item.name} 缓存已清除,下次加载将更新`);
        }
      });
    }
  });
}

// 调用方式:监听目标根目录
function refreshFolders() {
  try {
    const rootDir = path.join(__dirname, "..", "..");
    watchDirectory(rootDir);
    utils.logger("info", "文件监听已启动");
  } catch (error) {
    utils.logger("error", error.stack);
  }
}

额外说明

  1. 文件未更新的可能原因:

    • 缓存删除后未重新require模块:删除缓存仅让下次require时重新加载文件,若当前模块已在内存运行,需确保后续逻辑会重新引入模块
    • 监听路径错误:确认__dirname层级是否正确,避免路径拼接错误导致监听目录不对
    • 事件堆积:fs.watchFile会频繁触发事件,改用fs.watch可减少重复事件
  2. 注意事项:

    • 避免监听node_modules目录,否则会触发大量无效事件
    • 动态加载的模块需在使用前重新require,才能获取最新内容
    • 生产环境不建议使用这种热重载方式,推荐用PM2等工具实现进程重启

内容的提问来源于stack exchange,提问作者João Paulo moura

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 18:26:17