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

Vite插件热更新问题:moduleGraph为空无法重载虚拟模块

Vite虚拟模块热重载问题:moduleGraph为空无法获取模块

问题详情

我正在开发一个基于配置文件生成虚拟模块的Vite插件,生成功能已正常运行。接下来希望在底层配置文件esphome-web.json变更时热重载生成的虚拟模块,原本打算使用ctx.server.reloadModule触发模块重载,但遇到问题:ctx.server.moduleGraph中的所有Map均为空,导致ctx.server.moduleGraph.getModuleById无法获取到任何模块,无论修改哪个文件都是如此。确定自己可能存在理解误区或低级错误,但搜索后未找到相关解决方案。

当前handleHotUpdate实现

handleHotUpdate(ctx) {
  if (ctx.file.endsWith(esphomeWebConfigFile)) {
    // Not sure if I need to use module ID or resolved module ID here
    const mod = ctx.server.moduleGraph.getModuleById(cssModuleId);
    // Bot doesn't really matter anyway, because this here turns up with an
    // empty Map!
    console.log(ctx.server.moduleGraph.idToModuleMap);

    // ctx.server.reloadModule(mod);
    return [];
  }
},

完整插件代码

function esphomeWebConfigVirtualModules() {
  const cssModuleId = 'virtual:custom.css';
  const moduleIds = [cssModuleId];
  function resolveId(id) {
    return `\0${id}`;
  }

  const esphomeWebConfigFile = 'esphome-web.json';
  const esphomeWebConfigPath = esphomeWebConfigFile;
  let esphomeWebConfig = {};
  if (fs.existsSync(esphomeWebConfigPath)) {
    esphomeWebConfig = JSON.parse(fs.readFileSync(esphomeWebConfigPath))
  }
  function generateCssModule() {
    const o = esphomeWebConfig.css || {};
    return `body { \n${Object.keys(o).map(k => `  ${k}: ${o[k]};\n`).join('')}}`;
  }

  return {
    name: 'esphome-web-config-virtual-modules-plugin',
    resolveId(id) {
      if (moduleIds.includes(id)) {
        return resolveId(id);
      }
    },
    handleHotUpdate(ctx) {
      if (ctx.file.endsWith(esphomeWebConfigFile)) {
        // Not sure if I need to use module ID or resolved module ID here
        const mod = ctx.server.moduleGraph.getModuleById(cssModuleId);
        // Bot doesn't really matter anyway, because this here turns up with an
        // empty Map!
        console.log(ctx.server.moduleGraph.idToModuleMap);

        // ctx.server.reloadModule(mod);
        return [];
      }
    },
    load(id) {
      if (id === resolveId(cssModule)) {
        return generateCssModule();
      }
    },
  };
}

问题分析与修复方案

核心问题点

  1. load函数变量错误:load方法里判断的是resolveId(cssModule),但cssModule是未定义变量,应该用cssModuleId。这会导致虚拟模块根本没被正确加载,所以moduleGraph中没有该模块的记录。
  2. 模块ID使用错误:moduleGraph中存储的是resolved ID(即带\0前缀的ID),而非原始的cssModuleId,所以用原始ID无法找到模块。
  3. 配置未实时更新:配置文件变更时没有重新读取,就算触发重载,生成的模块内容还是旧的。
  4. HMR处理逻辑问题:返回空数组会阻止默认HMR,但需要正确找到模块并触发重载。

修改后的完整代码

const fs = require('fs'); // 确保引入fs模块

function esphomeWebConfigVirtualModules() {
  const cssModuleId = 'virtual:custom.css';
  const resolvedCssModuleId = `\0${cssModuleId}`; // 直接定义resolved ID
  const moduleIds = [cssModuleId];

  const esphomeWebConfigFile = 'esphome-web.json';
  const esphomeWebConfigPath = esphomeWebConfigFile;
  let esphomeWebConfig = {};
  
  // 封装读取配置的函数,方便HMR时调用
  function readConfig() {
    if (fs.existsSync(esphomeWebConfigPath)) {
      esphomeWebConfig = JSON.parse(fs.readFileSync(esphomeWebConfigPath, 'utf8'));
    } else {
      esphomeWebConfig = {};
    }
  }
  
  // 初始化读取配置
  readConfig();

  function generateCssModule() {
    const o = esphomeWebConfig.css || {};
    return `body { \n${Object.keys(o).map(k => `  ${k}: ${o[k]};\n`).join('')}}`;
  }

  return {
    name: 'esphome-web-config-virtual-modules-plugin',
    resolveId(id) {
      if (moduleIds.includes(id)) {
        return resolvedCssModuleId;
      }
    },
    handleHotUpdate(ctx) {
      if (ctx.file.endsWith(esphomeWebConfigFile)) {
        // 重新读取配置
        readConfig();
        // 使用resolved ID查找模块
        const mod = ctx.server.moduleGraph.getModuleById(resolvedCssModuleId);
        if (mod) {
          // 重载模块
          ctx.server.reloadModule(mod);
        }
        // 返回空数组阻止默认处理,因为我们已经手动触发重载
        return [];
      }
    },
    load(id) {
      // 修正变量错误,使用resolvedCssModuleId
      if (id === resolvedCssModuleId) {
        return generateCssModule();
      }
    },
  };
}

关键修改说明

  • 把resolveId的结果提前定义为resolvedCssModuleId,避免重复计算,同时确保ID一致。
  • 封装readConfig函数,在初始化和配置文件变更时调用,保证配置是最新的。
  • 修正load函数中的变量错误,确保虚拟模块能被正确加载,这样moduleGraph才会有记录。
  • 在handleHotUpdate中使用resolvedCssModuleId查找模块,找到后调用reloadModule触发重载。

内容的提问来源于stack exchange,提问作者Daniel Baulig

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 12:25:08