如何在Vite的TypeScript React Monorepo中实现HMR热模块替换
React Monorepo迁移Vite后HMR失效问题及解决方案需求
问题背景
我有一个基于TypeScript的React Monorepo,正在从Webpack迁移到Vite,但Vite里无法正常实现HMR(热模块替换)。我推测是因为我们单独构建各个包导致的。我需要找到可行的解决方案,但必须保留单独构建包的能力——这是为了保证Jest/ESLint的性能。
项目结构
\apps \main \packages \domainA \foo \package.json \build \src
当前包构建与配置
每个包通过tsc --project tsconfig.lib.json命令构建到build目录,package.json配置如下:
"name": "@ig/foo", "main": "./build/index.js", "types": "./build/index.d.ts", "files": [ "/build" ]
当前现象
启动主应用后,修改/packages/domainA/foo/src/index.ts时,包会自动构建(已配置监听器),但页面会完全重载,无法触发HMR。
约束与尝试
- 直接把包的入口改为
"main": "./src/index.ts"会降低Jest/ESLint的性能,不符合需求,但可以接受让Vite在开发环境直接指向源文件的方案。 - 尝试过配置Vite别名,但无效:
resolve: { alias: [{ find: '@ig/foo', replacement: '../packages/domainA/foo/src/index.ts', }] }
当前Vite配置
import react from '@vitejs/plugin-react'; import fs from 'fs'; import path, { resolve } from 'path'; import { defineConfig } from 'vite'; import mkcert from 'vite-plugin-mkcert'; import svgrPlugin from 'vite-plugin-svgr'; export default defineConfig({ // optimizeDeps: { // include: ['@infogrid/solution-views-occupancy'], // }, build: { outDir: 'build/public', sourcemap: true, rollupOptions: { input: { main: resolve(__dirname, 'index.html'), base: resolve(__dirname, 'index_base.html'), }, } }, server: { port: Number(process.env.PORT), // setting to true allows external ip host: true, }, plugins: [ react({ fastRefresh: true }), // Primarily used for HMR svgrPlugin({ svgrOptions: { icon: true } }), // Turns svgs into react components mkcert(), // Allows for HTTPS during local development ] });
解决方案
方案1:开发环境指向源文件,生产/工具链用构建产物
核心思路是在Vite开发模式下将包别名指向源文件,生产模式保留构建产物入口;同时通过package.json的exports字段区分环境,不影响Jest/ESLint的性能。
步骤1:修改包的package.json
新增exports字段,给不同环境指定入口:
"name": "@ig/foo", "main": "./build/index.js", "types": "./build/index.d.ts", "files": ["/build"], "exports": { ".": { "development": "./src/index.ts", "production": "./build/index.js" }, "./src/*": "./src/*" }
步骤2:调整Vite配置
区分开发/生产模式配置,确保开发时监听源文件、不预优化包:
export default defineConfig(({ mode }) => ({ // ...原有其他配置 resolve: { preserveSymlinks: true, alias: mode === 'development' ? [ { find: '@ig/foo', replacement: path.resolve(__dirname, '../packages/domainA/foo/src/index.ts'), } ] : [] }, optimizeDeps: { exclude: mode === 'development' ? ['@ig/foo'] : [] }, server: { // ...原有其他配置 watch: { include: ['../packages/**/*.{ts,tsx,js,jsx}'] } } }));
步骤3:调整包的TS配置
确保packages/domainA/foo/tsconfig.lib.json支持ES模块输出,让Vite能直接处理源文件:
{ "compilerOptions": { "module": "ESNext", "target": "ESNext", "jsx": "react-jsx", // ...其他原有配置 } }
方案2:用Monorepo插件简化配置
如果手动配置别名太繁琐,可使用vite-plugin-monorepo自动处理源文件指向:
- 安装插件:
npm install vite-plugin-monorepo -D - 修改Vite配置:
import monorepo from 'vite-plugin-monorepo'; export default defineConfig(({ mode }) => ({ // ...原有其他配置 plugins: [ monorepo({ packages: ['../packages/**'] }), // ...原有其他插件 ], optimizeDeps: { exclude: mode === 'development' ? ['@ig/foo'] : [] } }));
插件会自动将Monorepo包指向源文件,无需手动配置别名,同时不影响生产环境使用构建产物。
关键注意事项
- 开发模式下必须通过
optimizeDeps.exclude排除包,避免Vite预优化导致HMR失效。 - 开启
preserveSymlinks可避免Vite解析包时出现路径问题。 - 包的TS配置必须输出ES模块,否则Vite无法处理源文件。
内容的提问来源于stack exchange,提问作者Ian
相关产品推荐
相关产品推荐

