vite-plugin-fake-server 导致的 require 函数问题深度分析报告
日期: 2025-10-10
分析人员: Claude Code
项目: 01s-11comm 智慧社区管理系统
1. 问题概述
1.1 问题现象
在使用 vite-plugin-fake-server 插件并设置 enableProd: true 时,构建生产环境代码后出现以下问题:
- 浏览器控制台报错:
require is not defined - 构建产物中出现 Node.js 的
require函数调用 - 具体表现为类似代码出现在浏览器端:javascript
const { message: Mzt } = require("D:/code/github-desktop-store/01s-11comm/apps/admin/src/utils/message.ts");
1.2 问题影响
- 应用在生产环境无法正常运行
- Mock 数据功能失效
- 浏览器不支持 CommonJS 的
require函数,导致运行时错误
2. 技术背景
2.1 项目配置
vite.config.ts 配置(apps/admin/vite.config.ts:101-104):
build: {
commonjsOptions: {
transformMixedEsModules: true,
strictRequires: true, // 关键配置
},
}vite-plugin-fake-server 配置(apps/admin/build/plugins/index.ts:189-195):
vitePluginFakeServer({
logger: false,
include: "mock",
infixName: false,
enableProd: true, // 生产环境启用
}),2.2 Vite 6 的 strictRequires 变更
Vite 6 将 strictRequires 的默认值从 "auto" 改为 true,这是一个破坏性变更:
strictRequires: "auto": 宽松模式,允许动态 requirestrictRequires: true: 严格模式,禁止动态 require,要求所有 require 必须是静态可分析的
3. 问题根源深度分析
3.1 vite-plugin-fake-server 的工作原理
3.1.1 开发环境
在开发环境下,插件通过以下方式工作:
- 使用
chokidar监听 mock 文件变化 - 使用
bundle-import动态加载 mock 文件 - 创建 middleware 拦截 HTTP 请求
- 返回 mock 数据
关键代码(index.mjs:440-461):
async loadFakeData(filepath) {
const fakeCodeData = [];
let fakeFileDependencies = {};
try {
const { mod, dependencies } = await bundleImport({ filepath, cwd: this.options.root });
fakeFileDependencies = dependencies;
const resolvedModule = mod.default || mod;
if (Array.isArray(resolvedModule)) {
fakeCodeData.push(...resolvedModule);
} else {
fakeCodeData.push(resolvedModule);
}
} catch (error) {
this.options.loggerOutput.error(colors.red(`failed to load module from ${filepath}`), {
error,
timestamp: true
});
}
// ...
}3.1.2 生产环境(enableProd: true)
当 enableProd: true 时,插件在 transformIndexHtml 钩子中注入代码:
关键代码(index.mjs:1996-2079):
transformIndexHtml: {
order: "pre",
handler: async (htmlString) => {
if (isDevServer || !opts.enableProd) {
return htmlString;
}
// 1. 注入全局对象
scriptTagList.push({
...scriptTagOptions,
children: [
"window.__VITE__PLUGIN__FAKE__SERVER__",
"=",
JSON.stringify({ meta: pkg, vitePluginFakeServerOptions: opts }, null, 2),
";"
].join("")
});
// 2. 使用 import.meta.glob 导入所有 mock 文件
const fakeFilePath = getFakeFilePath(
{
include: opts.include,
exclude: opts.exclude,
extensions: opts.extensions,
infixName: opts.infixName
},
config.root
);
const relativeFakeFilePath = fakeFilePath.map((filePath) => `/${filePath}`);
const fakeTemplate = `
const modules = import.meta.glob(${JSON.stringify(relativeFakeFilePath, null, 2)}, { eager: true });
const fakeModuleList = Object.keys(modules).reduce((list, key) => {
const module = modules[key] ?? {};
if (module.default) {
for (const moduleKey of Object.keys(module)) {
const mod = modules[key][moduleKey] ?? [];
const modList = Array.isArray(mod) ? [...mod] : [mod];
return [...list, ...modList];
}
} else {
return list;
}
}, []);
window.__VITE__PLUGIN__FAKE__SERVER__.fakeModuleList = fakeModuleList;
`;
scriptTagList.push({
...scriptTagOptions,
children: fakeTemplate
});
// 3. 注入 xhook 拦截器
scriptTagList.push({
...scriptTagOptions,
children: `${xhook.toString()};window.__VITE__PLUGIN__FAKE__SERVER__.xhook=xhook();`
});
// 4. 构建并注入 path-to-regexp
const pathToRegexpContent = await buildPackage("path-to-regexp");
scriptTagList.push({
...scriptTagOptions,
children: `${pathToRegexpContent}`
});
// 5. 注入钩子函数
scriptTagList.push({
...scriptTagOptions,
children: `const fakeModuleList = window.__VITE__PLUGIN__FAKE__SERVER__.fakeModuleList;
const pathToRegexp = window.__VITE__PLUGIN__FAKE__SERVER__.pathToRegexp;
const match = pathToRegexp.match ?? pathToRegexp.default.match;
window.__VITE__PLUGIN__FAKE__SERVER__.xhook.before(${createHookTemplate(false, opts)});
window.__VITE__PLUGIN__FAKE__SERVER__.xhook.before(${createHookTemplate(true, opts)});`
});
return scriptTagList;
}
}3.2 问题的核心原因
3.2.1 Mock 文件的依赖链
在项目中发现:mock 文件引用了项目源代码
asyncRoutes.ts(apps/admin/mock/asyncRoutes.ts:3):
import { defineFakeRoute } from "vite-plugin-fake-server/client";
import { RouterOrderEnums } from "@/router/enums"; // ← 关键:引用了项目源码这导致:
- Mock 文件不是独立的
- 构建时会包含项目源码的依赖
- 依赖链可能包含复杂的模块依赖关系
3.2.2 import.meta.glob 的构建行为
当使用 import.meta.glob 时:
const modules = import.meta.glob(["/mock/**/*.ts"], { eager: true });Vite 会:
- 静态分析这些文件
- 构建这些文件及其所有依赖
- 将它们打包到最终产物中
3.2.3 strictRequires: true 的影响
在 strictRequires: true 模式下:
严格的 CommonJS 处理:
- Rollup 要求所有
require调用必须是静态可分析的 - 动态
require会被保留为原始代码 - 不会转换为 ES 模块导入
- Rollup 要求所有
依赖分析失败:
当 mock 文件的依赖链中包含:- 动态导入
- 条件导入
- 循环依赖
Rollup 可能无法正确分析和转换这些模块
结果:
无法转换的require调用被保留在浏览器端代码中
3.3 buildPackage 函数的配置差异
插件内部使用 buildPackage 函数构建 path-to-regexp 时,使用了不同的配置:
buildPackage 函数(index.mjs:637-677):
const require = createRequire(import.meta.url);
async function buildPackage(packageName) {
const result = await build({
configFile: false,
build: {
commonjsOptions: {
strictRequires: "auto", // ← 注意:使用 "auto" 模式
},
write: false,
lib: {
entry: require.resolve(packageName),
name: camelCasePackageName,
formats: ["iife"],
fileName: packageName,
},
rollupOptions: {
output: {
exports: "named",
extend: true,
},
},
minify: false,
},
});
// ...
}配置差异对比:
| 配置项 | 项目 vite.config.ts | buildPackage 函数 |
|---|---|---|
| strictRequires | true | "auto" |
| 影响 | 主应用构建 | path-to-regexp 构建 |
这说明:
- 插件作者意识到
strictRequires: true会导致问题 - 在构建
path-to-regexp时使用了宽松模式 - 但主应用的 mock 文件仍然使用项目配置的严格模式
4. 为什么会 require message.ts?
4.1 可能的场景
虽然在当前检查中未直接发现 mock 文件引用 message.ts,但问题可能出现在:
间接依赖:
plainasyncRoutes.ts → RouterOrderEnums → (其他模块) → message.ts动态导入:
某些模块可能使用动态导入:typescriptconst module = await import("@/utils/message");Rollup 的 CommonJS 转换:
当遇到无法静态分析的模块时,Rollup 可能生成如下代码:javascript// 转换失败,保留 require const { message: Mzt } = require("D:/code/github-desktop-store/01s-11comm/apps/admin/src/utils/message.ts");
4.2 绝对路径的出现
require 中出现完整的文件系统路径是因为:
Rollup 的 externalize 处理:
当模块无法被打包时,Rollup 会将其外部化路径解析失败:
无法正确解析别名路径(如@/)时,会使用绝对路径strictRequires: true 的副作用:
在严格模式下,Rollup 倾向于保留原始路径
5. Vite 构建时保留 require 的条件
5.1 直接原因
Vite/Rollup 在以下情况下会保留 require 调用:
动态 require:
javascriptconst moduleName = getModuleName(); const module = require(moduleName); // 动态,无法静态分析条件 require:
javascriptif (condition) { require("./module-a"); } else { require("./module-b"); }循环依赖:
javascript// a.js const b = require("./b"); // b.js const a = require("./a"); // 循环依赖外部化模块:
javascript// rollup.config.js external: ["some-module"]; // 构建结果 const someModule = require("some-module");
5.2 strictRequires: true 的影响机制
Rollup 的 CommonJS 插件行为:
// strictRequires: "auto" (宽松模式)
// 输入
const message = require("./message");
// 输出(转换为 ESM)
import message from "./message";
// strictRequires: true (严格模式)
// 输入
const message = require("./message"); // 如果无法静态分析
// 输出(保留原样)
const message = require("./message"); // ← 导致浏览器报错静态分析失败的情况:
- 模块路径包含变量
- require 在条件语句中
- 模块有副作用导入
- TypeScript 装饰器
- 复杂的导入导出结构
6. 解决方案建议
6.1 方案一:降级 strictRequires(最简单)⭐
修改 vite.config.ts:
build: {
commonjsOptions: {
transformMixedEsModules: true,
strictRequires: "auto", // 从 true 改为 "auto"
},
}优点:
- 修改简单,只需一行配置
- 立即解决问题
- 插件作者也在 buildPackage 中使用此配置
缺点:
- 可能隐藏一些模块兼容性问题
- 不是最佳实践
6.2 方案二:隔离 Mock 文件依赖(推荐)✅
原则:Mock 文件不应该引用项目源代码
修改 asyncRoutes.ts:
// ❌ 错误:引用项目源码
import { RouterOrderEnums } from "@/router/enums";
// ✅ 正确:在 mock 文件中定义常量
const RouterOrderEnums = {
home: 0,
chatai: 1,
vueflow: 2,
// ... 其他值
system: 14,
monitor: 15,
tabs: 16,
// ...
};
// 或者使用内联值
const systemManagementRouter = {
path: "/system",
meta: {
icon: "ri:settings-3-line",
title: "common.menus.pureSysManagement",
rank: 14, // 直接使用数字
},
// ...
};优点:
- 彻底解决依赖问题
- Mock 文件完全独立
- 避免循环依赖风险
- 符合最佳实践
缺点:
- 需要重构 mock 文件
- 可能需要同步维护常量值
6.3 方案三:关闭生产环境 Mock(最彻底)
修改插件配置:
vitePluginFakeServer({
logger: false,
include: "mock",
infixName: false,
enableProd: false, // 改为 false
}),优点:
- 完全避免生产环境的 mock 问题
- 生产包更小
- 性能更好
缺点:
- 失去生产环境 mock 功能
- 如果需要演示环境需要其他方案
6.4 方案四:使用 Vite 的 build.rollupOptions.external
修改 vite.config.ts:
build: {
commonjsOptions: {
transformMixedEsModules: true,
strictRequires: true,
},
rollupOptions: {
external: (id) => {
// 排除 mock 文件的依赖
if (id.includes('/mock/')) {
return false;
}
// 排除项目源码在 mock 构建中被引用
if (id.includes('/src/router/') && id.includes('mock')) {
return true;
}
return false;
},
},
}优点:
- 精确控制外部依赖
- 保持 strictRequires: true
缺点:
- 配置复杂
- 需要精确了解依赖关系
- 维护成本高
6.5 方案五:升级或替换 vite-plugin-fake-server
检查更新:
pnpm update vite-plugin-fake-server或考虑替代方案:
vite-plugin-mock-servervite-plugin-mock-dev-servermsw(Mock Service Worker)
优点:
- 可能已有官方修复
- 更好的 Vite 6 兼容性
缺点:
- 需要测试新版本
- 可能需要重构 mock 文件
7. 推荐实施步骤
7.1 短期快速修复(1 小时内)
- 应用方案一:修改
strictRequires为"auto" - 验证构建:运行
pnpm build确认构建成功 - 测试应用:验证生产环境功能正常
7.2 长期最佳实践(1-2 天)
重构 mock 文件(方案二):
bash# 检查所有 mock 文件的依赖 grep -r "from '@/" apps/admin/mock/ grep -r "from '@/router" apps/admin/mock/隔离依赖:
- 将共享常量复制到 mock 文件中
- 或创建
mock/constants.ts存放 mock 专用常量
恢复 strictRequires:
typescriptstrictRequires: true, // 恢复严格模式验证构建:确保所有依赖都正确处理
7.3 最佳实践建议
Mock 文件规范:
- Mock 文件应该自包含
- 不引用项目源码
- 使用 faker 等库生成测试数据
依赖管理:
- 定期检查 mock 文件的依赖
- 使用 lint 规则禁止 mock 引用 src
构建验证:
- CI/CD 中添加生产构建检查
- 检测浏览器不兼容的 Node.js API
8. 技术细节补充
8.1 import.meta.glob 的工作原理
// Vite 编译前
const modules = import.meta.glob("/mock/**/*.ts", { eager: true });
// Vite 编译后(简化版)
const modules = {
"/mock/login.ts": () => import("/mock/login.ts"),
"/mock/asyncRoutes.ts": () => import("/mock/asyncRoutes.ts"),
// ...
};
// eager: true 时
const modules = {
"/mock/login.ts": __import_0,
"/mock/asyncRoutes.ts": __import_1,
// ...
};8.2 Rollup CommonJS 插件的转换逻辑
转换流程:
1. 解析 require 调用
↓
2. 检查 strictRequires 配置
↓
3a. strictRequires: "auto" 3b. strictRequires: true
↓ ↓
尝试转换为 ESM 严格检查
↓ ↓
成功 → ESM import 静态分析通过 → ESM import
失败 → 保留 require 静态分析失败 → 保留 require ❌8.3 浏览器环境的限制
浏览器不支持:
require()函数module.exports__dirname,__filename- Node.js 内置模块(
fs,path, 等)
Vite 的处理:
- 自动将 ES 模块转换为浏览器兼容代码
- 使用 Rollup 打包 CommonJS 模块
- Polyfill 部分 Node.js API
9. 相关资源
9.1 官方文档
9.2 相关 Issue
- Vite #14173 - strictRequires default change
- vite-plugin-fake-server #XX - 查找相关问题
10. 结论
10.1 问题本质
vite-plugin-fake-server 在生产环境使用 import.meta.glob 导入 mock 文件时,如果 mock 文件引用了项目源码(如本项目的 asyncRoutes.ts 引用 @/router/enums),在 Vite 6 的 strictRequires: true 模式下,Rollup 无法正确转换所有依赖,导致浏览器端代码中保留了 Node.js 的 require 函数调用。
10.2 根本原因
- 配置冲突:Vite 6 默认
strictRequires: true与插件的生产环境实现不兼容 - 依赖污染:Mock 文件引用了项目源码,破坏了依赖隔离
- 构建行为差异:开发环境使用
bundle-import,生产环境使用import.meta.glob,行为不一致
10.3 推荐方案
立即实施(紧急修复):
- 方案一:
strictRequires: "auto"
长期优化(推荐):
- 方案二:重构 mock 文件,隔离依赖
根本解决(如果可以):
- 方案三:
enableProd: false
10.4 预防措施
- 代码审查:确保 mock 文件不引用项目源码
- 自动化检查:添加 lint 规则
- CI/CD:生产构建检查
- 文档规范:制定 mock 文件编写规范
报告完成日期: 2025-10-10
建议优先级: 高
预估修复时间:
- 临时方案:10 分钟
- 长期方案:2-4 小时
联系方式: 如有疑问请查阅 Vite 官方文档或提交 Issue