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(); } }, }; }
问题分析与修复方案
核心问题点
- load函数变量错误:
load方法里判断的是resolveId(cssModule),但cssModule是未定义变量,应该用cssModuleId。这会导致虚拟模块根本没被正确加载,所以moduleGraph中没有该模块的记录。 - 模块ID使用错误:
moduleGraph中存储的是resolved ID(即带\0前缀的ID),而非原始的cssModuleId,所以用原始ID无法找到模块。 - 配置未实时更新:配置文件变更时没有重新读取,就算触发重载,生成的模块内容还是旧的。
- 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
相关产品推荐
相关产品推荐

