场景:Node.js 配置热更新完整方案

Node.js 配置热更新完整方案(CommonJS / ESM 两套实操)

前言

在 Node.js 开发中,日常会使用 config.js 作为项目配置文件,业务需求通常为:修改配置无需重启程序、实时读取最新配置,常用于文件监听、接口上传、自动化脚本等场景。

绝大多数开发者会踩坑:明明修改了配置文件,程序却一直读取旧配置。根本原因是 Node.js 模块缓存机制

目前 Node.js 存在 CommonJS、ESM 两套模块规范,二者缓存逻辑不同,因此热更新实现方案完全不一样。本文抛开基础理论,直接给两套可落地、生产可用的配置热更新方案,附带原理、完整代码、避坑要点。


一、核心前置知识(仅热更新必备)

1. CommonJS 缓存规则

使用 require\(\) 加载配置文件后,模块会存入 require.cache 缓存,二次加载直接读取内存,不会读取本地硬盘;该规范开放缓存权限,支持手动删除缓存实现热更新。

2. ESM 缓存规则

使用 import / import\(\) 加载模块,强制永久缓存;官方屏蔽所有缓存操作API,开发者无法清除缓存,只能绕开模块系统实现热更新。


二、方案一:CommonJS 配置热更新(最简方案)

1. 实现原理

利用 CommonJS 开放的缓存API,每次读取配置前,手动删除该配置文件的缓存,重新加载文件,强制读取硬盘最新数据。

2. 项目文件结构

├── config.js       # 业务配置文件
├── getConfig.js    # 热更新工具函数
└── index.js        # 主程序

3. 完整代码实现

① 配置文件 config.js

module.exports = {
  // 监听目录
  watchDir: "./uploadFile",
  // 接口上传地址
  uploadUrl: "http://127.0.0.1:3000/api/upload",
  // 超时时间
  timeout: 15000,
  // 重试配置
  retry: {
    retries: 3,
    delay: 3000
  }
}

② 热更新工具函数 getConfig.js

const path = require('path');
// 拼接配置文件绝对路径
const configPath = path.resolve(__dirname, './config.js');

function getConfig() {
  // 核心:删除配置文件缓存,清除内存旧数据
  delete require.cache[require.resolve(configPath)];
  // 重新读取硬盘最新配置
  return require(configPath);
}

module.exports = { getConfig };

③ 主程序(搭配chokidar)index.js

const chokidar = require('chokidar');
const { getConfig } = require('./getConfig.js');

let watcher;
// 初始化监听函数
function initWatcher() {
  // 获取最新配置
  const config = getConfig();
  // 存在旧监听器则关闭,解决监听目录更新失效问题
  if (watcher) watcher.close();
  // 新建文件监听器
  watcher = chokidar.watch(config.watchDir, { persistent: true });
  console.log("✅ 当前监听目录:", config.watchDir);

  // 监听文件新增事件
  watcher.on('add', (filePath) => {
    // 每次触发事件,实时获取最新配置
    const latestConfig = getConfig();
    console.log("📁 检测到新文件:", filePath);
    console.log("🚀 当前上传接口:", latestConfig.uploadUrl);
  });
}

// 程序启动初始化
initWatcher();

// 监听配置文件变化,自动重启监听器
chokidar.watch('./config.js').on('change', () => {
  console.log("\n🔁 配置文件已修改,重新加载监听...");
  initWatcher();
});

4. 关键注意事项

  • 适用场景:项目无 \&\#34;type\&\#34;:\&\#34;module\&\#34;、CommonJS 规范项目

  • chokidar 版本限制:v4.0 以上为纯ESM模块,CommonJS 需安装chokidar@3\.5\.3

  • 优势:代码极简、无第三方依赖、性能高、无兼容bug


三、方案二:ESM 配置热更新(通用方案)

1. 实现原理

ESM 禁止清除缓存、无 require API,因此彻底绕开模块系统:使用 fs 读取配置文件原始文本,通过 vm 模块创建沙箱执行代码,每次读取均为硬盘最新文件,规避缓存问题。

2. 项目文件结构

├── config.js       # 业务配置文件
├── loadConfig.js   # 热更新工具函数
├── package.json    # 标记ESM规范
└── index.js        # 主程序

3. 完整代码实现

① package.json(必须配置)

{
  "type": "module"
}

② 配置文件 config.js

export default {
  watchDir: "./uploadFile",
  uploadUrl: "http://127.0.0.1:3000/api/upload",
  timeout: 15000,
  retry: {
    retries: 3,
    delay: 3000
  }
}

③ 热更新工具函数 loadConfig.js(极简易懂版)

import fs from 'fs/promises';
import path from 'path';
import vm from 'vm';
import { fileURLToPath } from 'url';

// ESM手动拼接文件路径,替代__dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const cfgPath = path.resolve(__dirname, './config.js');

// 实时加载最新配置
export async function loadConfig() {
  // 1. 以文本形式读取配置文件,绕开模块缓存
  const code = await fs.readFile(cfgPath, 'utf8');
  // 2. 创建干净沙箱环境
  const sandbox = {};
  // 3. 沙箱内执行JS代码
  vm.runInNewContext(code, sandbox);
  // 4. 返回最新配置
  return sandbox.default;
}

④ 主程序(搭配chokidar)index.js

import chokidar from 'chokidar';
import { loadConfig } from './loadConfig.js';

let watcher;
// 初始化监听函数
async function initWatcher() {
  const config = await loadConfig();
  if (watcher) await watcher.close();
  watcher = chokidar.watch(config.watchDir, { persistent: true });
  console.log("✅ 当前监听目录:", config.watchDir);

  watcher.on('add', async (filePath) => {
    const latestConfig = await loadConfig();
    console.log("📁 检测到新文件:", filePath);
    console.log("🚀 当前上传接口:", latestConfig.uploadUrl);
  });
}

// 程序启动初始化
await initWatcher();

// 监听配置文件变更
chokidar.watch('./config.js').on('change', async () => {
  console.log("\n🔁 配置文件已修改,重新加载监听...");
  await initWatcher();
});

4. 关键注意事项

  • 适用场景:带有 \&\#34;type\&\#34;:\&\#34;module\&\#34; 的 ESM 项目

  • 路径问题:ESM 无原生 \_\_dirname,需手动拼接路径

  • chokidar 版本:直接使用最新版即可,无需降级

  • 核心逻辑:不使用 import 导入配置,全程文本读取+沙箱执行


四、两套方案对比 & 选型建议

对比项 CommonJS 方案 ESM 方案
核心原理 删除 require.cache 缓存 fs读文本 + vm沙箱执行
代码难度 简单易懂 稍复杂,需处理路径
缓存问题 手动清除缓存 彻底绕开缓存
适用项目 老旧Node项目、无type标识 新项目、现代规范项目
chokidar版本 需降级至3.5.3 任意最新版本

五、通用踩坑总结

  1. 监听目录不更新:修改配置后,必须关闭旧 chokidar 监听器,再根据新目录重启监听,不能直接修改配置生效。

  2. chokidar 报错 require() of ES Module:CommonJS 项目安装过高版本 chokidar,降级至 3\.5\.3 即可。

  3. ESM 无法使用__dirname:固定模板手动拼接路径,不要直接使用原生变量。

  4. 配置文件无法热更新:禁止静态导入配置,业务读取配置必须调用封装的工具函数。

(注:文档部分内容可能由 AI 生成)

posted @ 2026-05-14 18:49  蜗牛般庄  阅读(54)  评论(0)    收藏  举报
Title
页脚 HTML 代码