Skip to content

vite-plugin-fake-server require 函数问题深度分析报告

报告生成时间: 2025-10-10
分析工具: Claude Code
插件版本: vite-plugin-fake-server@2.2.0
项目: vue-pure-admin@6.1.0


问题概述

在使用 vue-pure-admin 作为二次开发模板时,当 vite-plugin-fake-server 的配置项 enableProd: true 时,生产环境构建打包后的应用会出现 require is not defined 错误,导致应用无法正常运行。

环境信息

  • 项目模板: vue-pure-admin v6.1.0
  • 插件版本: vite-plugin-fake-server v2.2.0
  • Vite 版本: v7.1.9
  • Node 版本要求: ^20.19.0 || >=22.13.0
  • 问题配置: enableProd: true

问题现象

构建产物检测结果

apps/dist/static/js/index-DbKXK31u.js 中发现以下关键内容:

  1. 存在 __VITE__PLUGIN__FAKE__SERVER__ 对象

    • 包含插件元数据
    • 包含配置选项(enableProd: true
  2. 存在 xhook 相关代码

    • window.__VITE__PLUGIN__FAKE__SERVER__.xhook
  3. 存在未转换的 require 函数调用

    • 在浏览器环境中直接使用了 Node.js 的 require 函数
    • 导致运行时错误:ReferenceError: require is not defined

问题根源分析

1. 插件工作机制

enableProd: true 时,vite-plugin-fake-server 在生产环境构建过程中会执行以下操作:

源码位置:node_modules/vite-plugin-fake-server/dist/index.mjs

第 637-677 行:buildPackage 函数

javascript
const require = createRequire(import.meta.url); // Node.js 环境的 require

async function buildPackage(packageName) {
	const camelCasePackageName = packageName.replace(/-\w/g, (str) => str[1].toUpperCase());
	const result = await build({
		configFile: false,
		build: {
			commonjsOptions: {
				strictRequires: "auto", // 尝试自动处理 CommonJS
			},
			write: false,
			lib: {
				entry: require.resolve(packageName), // 使用 Node.js 的 require.resolve
				name: camelCasePackageName,
				formats: ["iife"], // 输出为 IIFE 格式
				fileName: packageName,
			},
			rollupOptions: {
				output: {
					exports: "named",
					extend: true,
				},
			},
			minify: false,
		},
	});

	const _result = Array.isArray(result) ? result[0] : result;
	if (!("output" in _result)) {
		return;
	}

	// 将构建结果包装后返回
	return `window.__VITE__PLUGIN__FAKE__SERVER__.${camelCasePackageName} = (function() { ${_result.output[0].code} return this.${camelCasePackageName}; }).apply({});`;
}

第 1996-2079 行:transformIndexHtml hook

javascript
transformIndexHtml: {
  order: "pre",
  handler: async (htmlString) => {
    if (isDevServer || !opts.enableProd) {
      return htmlString;  // 开发环境或 enableProd=false 时不处理
    }

    // ... 其他代码 ...

    // 关键:构建 path-to-regexp 包并注入到页面
    const pathToRegexpContent = await buildPackage("path-to-regexp");
    scriptTagList.push({
      ...scriptTagOptions,
      children: `${pathToRegexpContent}`  // 直接注入构建结果
    });

    // ... 其他代码 ...
  }
}

2. 问题发生的具体流程

plain
┌─────────────────────────────────────────────────────────────────┐
│ 1. Vite 开始生产环境构建                                          │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 2. vite-plugin-fake-server 检测到 enableProd: true              │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 3. 在 transformIndexHtml hook 中调用 buildPackage()             │
│    - 目标:将 path-to-regexp 打包成浏览器可用的代码              │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 4. buildPackage 使用 Vite build API 构建 path-to-regexp         │
│    - 入口:require.resolve("path-to-regexp")                     │
│    - 格式:iife (立即执行函数)                                    │
│    - CommonJS 配置:strictRequires: "auto"                       │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 5. 【问题点】path-to-regexp 包含 CommonJS require 调用           │
│    - Vite 的 strictRequires: "auto" 未能完全转换                │
│    - 构建产物中仍然保留了 require() 函数调用                      │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 6. 将构建结果注入到 index.html 的 <script> 标签中                │
│    window.__VITE__PLUGIN__FAKE__SERVER__.pathToRegexp = ...     │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 7. 浏览器加载页面并执行 JavaScript                               │
│    ✗ require is not defined (浏览器环境无 require 函数)         │
└─────────────────────────────────────────────────────────────────┘

3. 为什么会出现 require 函数

原因 A:path-to-regexp 依赖问题

path-to-regexp@8.2.0 可能包含或依赖了使用 CommonJS 格式的代码:

javascript
// 可能存在类似这样的代码
const someModule = require("some-dependency");

原因 B:Vite 构建配置不完善

javascript
commonjsOptions: {
	strictRequires: "auto"; // "auto" 模式在某些情况下无法处理所有 require
}

Vite 6 将 strictRequires 的默认值从 "auto" 改为 true,插件使用了 "auto" 配置,这在某些边缘情况下无法正确转换所有的 CommonJS require 调用。

原因 C:缺少 polyfill

插件没有为浏览器环境提供 require 函数的 polyfill,直接假设构建产物可以在浏览器中运行。

4. 当前项目配置

位置:apps/build/plugins.ts:55-60

typescript
vitePluginFakeServer({
  logger: false,
  include: "mock",
  infixName: false,
  enableProd: true  // ← 问题的触发点
}),

解决方案

方案 1:禁用生产环境 Mock 功能(强烈推荐)⭐⭐⭐⭐⭐

Mock 功能通常只在开发环境中使用,生产环境不应该包含 mock 数据。

修改配置

typescript
// apps/build/plugins.ts
vitePluginFakeServer({
  logger: false,
  include: "mock",
  infixName: false,
  enableProd: false,  // ← 改为 false
  enableDev: true     // 保持开发环境可用
}),

优点

  • 彻底解决问题:不会再有 require 函数问题
  • 减少打包体积:移除不必要的 mock 相关代码
  • 符合最佳实践:生产环境不应包含测试/开发用的 mock 数据
  • 提高安全性:避免 API 接口结构泄露
  • 提升性能:减少运行时开销

验证修复

bash
cd apps
pnpm build

检查构建产物:

bash
# 检查是否还有 require 函数
grep -r "require" dist/static/js/*.js

# 检查是否还有 __VITE__PLUGIN__FAKE__SERVER__
grep -r "__VITE__PLUGIN__FAKE__SERVER__" dist/static/js/*.js

如果都没有输出(或只在注释中),说明问题已解决。


方案 2:使用条件构建 ⭐⭐⭐

如果确实需要在某些特殊场景下启用生产环境 mock(如测试环境),可以通过环境变量控制。

修改配置

typescript
// apps/build/plugins.ts
vitePluginFakeServer({
  logger: false,
  include: "mock",
  infixName: false,
  enableProd: process.env.ENABLE_MOCK_IN_PROD === 'true',
  enableDev: true
}),

使用方式

bash
# 正常生产构建(不启用 mock)
cd apps
pnpm build

# 带 mock 的生产构建(仅用于测试环境)
ENABLE_MOCK_IN_PROD=true pnpm build

优点

  • ✅ 灵活性高
  • ✅ 默认情况下安全
  • ✅ 可用于特殊测试场景

缺点

  • ⚠️ 容易误操作(忘记设置环境变量)
  • ⚠️ 仍然存在潜在的构建问题

方案 3:升级或降级插件版本 ⭐⭐

检查是否有新版本修复了此问题。

检查更新

bash
cd apps
pnpm outdated vite-plugin-fake-server

尝试更新

bash
pnpm update vite-plugin-fake-server@latest

或指定版本

bash
pnpm add -D vite-plugin-fake-server@2.1.0  # 尝试其他版本

注意事项

  • 检查插件的 GitHub Issues
  • 查看 Changelog 是否有相关修复
  • 测试新版本是否引入其他问题

方案 4:Patch 插件(高级方案)⭐

如果必须在生产环境使用 mock 且其他方案都不可行,可以使用 patch-package 修改插件源码。

安装 patch-package

bash
cd apps
pnpm add -D patch-package

修改 package.json

json
{
	"scripts": {
		"postinstall": "patch-package"
	}
}

修改插件源码

修改 node_modules/vite-plugin-fake-server/dist/index.mjs

javascript
// 第 644-646 行,修改 strictRequires
commonjsOptions: {
	strictRequires: true; // 改为 true,强制转换所有 require
}

创建 patch

bash
pnpm patch-package vite-plugin-fake-server

缺点

  • ⚠️ 维护成本高
  • ⚠️ 升级插件时需要重新创建 patch
  • ⚠️ 可能不完全解决问题

替代方案

如果确实需要在生产环境模拟 API,建议使用更成熟的方案:

1. Mock Service Worker (MSW) ⭐⭐⭐⭐⭐

bash
pnpm add -D msw

MSW 使用 Service Worker 拦截请求,更适合生产环境测试。

优点

  • ✅ 真正的网络层拦截
  • ✅ 不修改业务代码
  • ✅ 支持浏览器和 Node.js
  • ✅ 社区活跃,文档完善

2. 独立的 Mock 服务器

搭建独立的 Mock API 服务器(如使用 json-server、mockoon 等)。

优点

  • ✅ 完全隔离
  • ✅ 更接近真实场景
  • ✅ 可以被多个项目共享

3. 后端提供测试环境

让后端团队提供专门的测试环境,而不是在前端使用 mock。

优点

  • ✅ 最真实的测试场景
  • ✅ 测试前后端集成
  • ✅ 前端无需维护 mock 数据

技术细节深入分析

Vite 的 CommonJS 处理

Vite 使用 @rollup/plugin-commonjs 来处理 CommonJS 模块。strictRequires 选项控制如何处理动态 require:

javascript
commonjsOptions: {
	// "auto": 自动检测(可能遗漏某些情况)
	// true: 严格模式,所有可能的 require 都转换
	// false: 宽松模式,只转换明确的 require
	strictRequires: "auto";
}

Vite 6 改变了默认值:

  • Vite 5: 默认 "auto"
  • Vite 6: 默认 true

插件使用了 "auto",这在 Vite 6+ 中可能导致某些 require 调用未被转换。

path-to-regexp 的模块格式

path-to-regexp@8.2.0 的 package.json:

json
{
	"exports": {
		".": {
			"import": "./dist/index.js",
			"require": "./dist/index.cjs"
		}
	}
}

该包同时提供 ESM 和 CommonJS 版本,但在某些构建场景下,Vite 可能会引入 CommonJS 版本的依赖。

createRequire 的作用

javascript
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);

这是 Node.js ESM 中使用 CommonJS require 的官方方式,但这个 require 只能在 Node.js 环境中使用,不能在浏览器中运行。

最佳实践建议

开发环境配置

typescript
vitePluginFakeServer({
  logger: true,          // 开发时显示日志
  include: "mock",
  infixName: false,
  enableDev: true,       // 开发环境启用
  enableProd: false,     // 生产环境禁用
  watch: true            // 监听 mock 文件变化
}),

生产环境策略

  1. 完全移除 mock(推荐)
  2. 连接测试后端:配置真实的测试环境 API
  3. 使用环境变量区分
typescript
// vite.config.ts
export default defineConfig(({ mode }) => {
	const isMockEnabled = mode === "staging"; // 只在 staging 模式启用

	return {
		plugins: [
			vitePluginFakeServer({
				enableProd: isMockEnabled,
				enableDev: true,
			}),
		],
	};
});

构建命令:

bash
# 生产环境(无 mock)
pnpm build

# 测试环境(有 mock)
pnpm build:staging  // vite build --mode staging

项目结构建议

plain
project/
├── apps/
│   ├── mock/          # Mock 数据
│   │   ├── user.ts
│   │   └── api.ts
│   ├── src/
│   │   ├── api/       # API 调用
│   │   └── config/    # 配置文件
│   └── vite.config.ts
└── docs/
    └── api-mock.md    # Mock 使用文档

相关资源

总结

问题本质

vite-plugin-fake-server 在生产环境构建时,尝试将 Node.js 的 path-to-regexp 包动态构建并注入到浏览器代码中,但由于 CommonJS 模块转换不完全,导致最终产物包含浏览器不支持的 require 函数。

推荐方案

强烈建议使用方案 1:将 enableProd 设置为 false

这是最简单、最安全、最符合最佳实践的解决方案,可以:

  • 彻底避免此问题
  • 减少生产环境风险
  • 提升应用性能
  • 减小打包体积

关键配置

typescript
// apps/build/plugins.ts
vitePluginFakeServer({
  logger: false,
  include: "mock",
  infixName: false,
  enableProd: false,  // ← 关键修改
  enableDev: true
}),

贡献者

The avatar of contributor named as ruan-cat ruan-cat

页面历史

最近更新