rspack-plugin-virtual-module 热更新失效问题及解决方案

222 阅读3分钟

问题描述

在使用 rspack-plugin-virtual-module 插件时,发现虚拟模块的动态更新功能(writeModule 方法)在开发环境下不会触发热更新,页面内容不会自动刷新。

问题复现

环境信息

  • Rsbuild: v1.4.0
  • rspack-plugin-virtual-module: v1.0.1
  • Vue: v3.5.17

复现步骤

  1. 安装并配置 rspack-plugin-virtual-module
  2. 在配置中使用 writeModule 动态更新虚拟模块内容
  3. 启动开发服务器
  4. 观察到虚拟模块内容更新后,页面没有热更新

示例代码

// rsbuild.config.mjs
import { defineConfig } from '@rsbuild/core';
import { pluginVue } from '@rsbuild/plugin-vue';
import VirtualModulePlugin from 'rspack-plugin-virtual-module';

function rsbuildPlugin() {
  return {
    name: 'rsbuild-plugin',
    setup(api) {
      let virtualModulePluginInstance;
      const virtualId = 'virtual-module';

      api.modifyRspackConfig((rspackConfig) => {
        virtualModulePluginInstance = new VirtualModulePlugin({
          [virtualId]: 'export default "hello world";',
        });

        rspackConfig.plugins.push(virtualModulePluginInstance);
      });

      api.onAfterStartDevServer(() => {
        setTimeout(() => {
          if (virtualModulePluginInstance) {
            // 这个更新不会触发热更新
            virtualModulePluginInstance.writeModule(virtualId, 'export default "hello world2";');
          }
        }, 2000);
      });
    },
  };
}

export default defineConfig({
  plugins: [pluginVue(), rsbuildPlugin()],
});

问题原因分析

经过调试发现,rspack-plugin-virtual-module 插件会在 node_modules 目录下创建一个临时文件夹(格式为 rspack-virtual-module-xxxxxx),用于存储虚拟模块的实际文件。

核心问题:Rspack 默认会忽略 node_modules 目录下的所有文件变化,不会监听这些文件的修改,因此当虚拟模块内容更新时,不会触发重新编译和热更新。

问题机制

  1. rspack-plugin-virtual-module 创建临时目录:node_modules/rspack-virtual-module-020dbd51/
  2. 虚拟模块文件存储在该目录下
  3. writeModule 方法修改该目录下的文件
  4. Rspack 的文件监听忽略了 node_modules/** 路径
  5. 文件变化未被检测到,热更新失效

解决方案

方案一:修改 watchOptions.ignored 配置(推荐)

在 Rspack 配置中,通过修改 watchOptions.ignored 来排除虚拟模块目录,使其能被文件监听系统检测到。

// rsbuild.config.mjs
import { defineConfig } from '@rsbuild/core';
import { pluginVue } from '@rsbuild/plugin-vue';
import VirtualModulePlugin from 'rspack-plugin-virtual-module';

function rsbuildPlugin() {
  return {
    name: 'rsbuild-plugin',
    setup(api) {
      let virtualModulePluginInstance;
      const virtualId = 'virtual-module';

      api.modifyRspackConfig((rspackConfig) => {
        virtualModulePluginInstance = new VirtualModulePlugin({
          [virtualId]: 'export default "hello world";',
        });

        rspackConfig.plugins.push(virtualModulePluginInstance);
        
        // 🔑 关键配置:排除虚拟模块文件夹,使其能被监听
        rspackConfig.watchOptions = {
          ...rspackConfig.watchOptions,
          ignored: [
            ...(rspackConfig.watchOptions?.ignored || []),
            '!**/node_modules/rspack-virtual-module-*/**', // 不忽略虚拟模块文件夹
          ],
        };
      });

      api.onAfterStartDevServer(() => {
        setTimeout(() => {
          if (virtualModulePluginInstance) {
            // 现在这个更新会触发热更新了!
            virtualModulePluginInstance.writeModule(virtualId, 'export default "hello world2";');
          }
        }, 2000);
      });
    },
  };
}

export default defineConfig({
  plugins: [pluginVue(), rsbuildPlugin()],
});

方案二:使用 snapshot 配置

// 在 rspackConfig 中添加
rspackConfig.snapshot = {
  ...rspackConfig.snapshot,
  managedPaths: [], // 清空默认的 node_modules 管理路径
  immutablePaths: [], // 清空不可变路径
};

验证步骤

  1. 应用上述配置
  2. 启动开发服务器:npm run dev
  3. 在浏览器中打开应用
  4. 观察控制台输出和页面内容
  5. 等待 2 秒后,应该能看到:
    • 虚拟模块内容从 "hello world" 更新为 "hello world2"
    • 页面自动热更新,显示新内容
    • 无需手动刷新页面

完整示例

项目结构

project/
├── src/
│   ├── App.vue
│   └── index.js
├── package.json
└── rsbuild.config.mjs

App.vue

<template>
  <div class="app">
    <h1>虚拟模块测试</h1>
    <p>虚拟模块内容: {{ virtualContent }}</p>
  </div>
</template>

<script>
import virtualModule from 'virtual-module'

export default {
  name: 'App',
  data() {
    return {
      virtualContent: virtualModule
    }
  }
}
</script>

package.json

{
  "dependencies": {
    "rspack-plugin-virtual-module": "^1.0.1",
    "vue": "^3.5.17"
  },
  "devDependencies": {
    "@rsbuild/core": "^1.3.22",
    "@rsbuild/plugin-vue": "^1.0.7"
  }
}

注意事项

  1. 通配符匹配:使用 rspack-virtual-module-* 通配符来匹配所有可能的临时目录名称
  2. 感叹号语法! 表示排除规则,即"不忽略"这些路径
  3. 性能影响:此配置可能会轻微影响构建性能,因为增加了对特定 node_modules 目录的监听
  4. 版本兼容性:此解决方案在 Rsbuild v1.4.0 和 rspack-plugin-virtual-module v1.0.1 下测试有效

相关链接

总结

这个问题的根本原因是 Rspack 默认忽略 node_modules 目录,而 rspack-plugin-virtual-module 在该目录下创建临时文件。通过配置 watchOptions.ignored 来排除虚拟模块目录,可以有效解决热更新失效的问题。

希望这个解决方案能帮助到遇到同样问题的开发者!