1. 项目概述与核心痛点最近在重构一个后台管理系统技术栈选的是 Vue3 Vite。开发阶段前端最头疼的莫过于后端接口还没好页面逻辑却得先跑起来。Mock 数据就成了刚需。之前用 Webpack 时代各种 Mock 方案都玩过像webpack-dev-server的before钩子或者直接用mockjs拦截XMLHttpRequest。但换到 Vite 后发现这套玩不转了因为 Vite 的开发服务器是基于原生 ESM 的很多老方法水土不服。于是我开始找 Vite 生态下的 Mock 方案目标很明确要能无缝集成到 Vite Dev Server 里支持热更新写法最好简单直观。搜了一圈vite-plugin-mock这个插件出现的频率最高看介绍也挺符合需求说是能“基于文件系统的 Mock支持热更新”。没多想直接npm i vite-plugin-mock -D就装上了。结果从安装到配置再到实际使用坑是一个接一个。官方文档写得比较简略很多细节没提社区里搜到的文章也是五花八门有些配置甚至已经过时了。我花了差不多一天时间才把各种报错和诡异行为给捋顺。这篇文章我就把自己踩过的这些坑以及最终的解决方案从头到尾给你盘清楚。如果你也在用 Vue3 Vite并且打算用vite-plugin-mock来模拟数据那这篇实操记录应该能帮你省下不少折腾的时间。2. 插件选型与核心原理拆解2.1 为什么是 vite-plugin-mock在 Vite 环境下做 Mock其实有好几条路可以走。最简单粗暴的就是在组件里直接写死一段json数据或者用mockjs的Mock.mock()在本地生成。但这方法太“原始”了无法模拟真实的网络请求过程对于测试请求加载状态、错误处理等场景很不友好。另一种常见思路是起一个本地的 Mock Server比如用json-server或者express自己写一个。这种方法功能强大可以模拟完整的 RESTful API但配置繁琐需要额外维护一个服务进程并且存在跨域问题需要处理增加了开发环境的复杂度。vite-plugin-mock的核心价值在于它把 Mock Server 的能力直接集成到了 Vite 的开发服务器内部。它本质上是一个 Vite 插件在插件内部创建了一个本地服务并劫持或者说代理了特定的 API 请求。当你的前端代码发起一个匹配规则的请求比如/api/user时这个请求并不会真正发往后端服务器而是被插件拦截并返回你在本地文件中定义的 Mock 数据。这样做有几个显著优势零配置网络无需处理跨域因为请求根本没出 Vite 服务器。开发体验无缝和开发真实接口的体验几乎一致都是发起fetch或axios请求。基于文件热更新Mock 数据写在.js或.ts文件里修改后能立刻生效无需重启服务。与构建流程解耦Mock 仅存在于开发环境生产构建时会自动剔除不会影响打包结果。2.2 插件工作原理与关键配置项理解它的工作原理对后面排查问题至关重要。插件在vite.config.ts中配置后会在 Vite 开发服务器启动时执行以下步骤扫描 Mock 文件根据你在配置中指定的mockPath默认是./mock目录插件会递归扫描该目录下所有的.js、.ts、.json文件。解析接口规则它会读取这些文件中导出的数组数组里每个对象定义了一个 Mock 接口。这个对象必须包含url接口路径、method请求方法、response返回数据或处理函数等关键属性。注册中间件插件在 Vite 的底层connect服务器上注册一个自定义的中间件。这个中间件会监听所有进来的 HTTP 请求。请求拦截与匹配当有请求进来时中间件会检查请求的 URL 和方法是否与某个 Mock 规则匹配。如果匹配则执行对应的response逻辑并直接返回结果请求到此为止。如果不匹配则放行请求会继续走 Vite 默认的处理流程或你配置的其他代理。这里有几个容易混淆但必须明确的配置项我踩的坑大多源于此enable: 这个最好理解控制 Mock 功能的总开关。通常我们会设置为process.env.NODE_ENV ‘development‘确保只在开发环境开启。mockPath: 指定 Mock 文件存放的目录。坑点一这个路径是相对于vite.config.ts文件所在位置的而不是项目根目录。如果你把vite.config.ts放在项目根目录那./mock没问题但如果你的配置文件在别处比如config/目录下这个路径就必须调整。ignore: 一个用于忽略某些文件的正则表达式。如果你在mockPath目录下放了不想被解析的文件比如README.md或一些工具函数文件需要用这个来过滤。watchFiles: 监听文件变化。默认会监听mockPath下的文件。但如果你在 Mock 文件里通过require或import引用了其他目录的模块并且希望这些模块变化也能触发 Mock 更新就需要在这里额外配置。logger: 是否在控制台打印日志。建议开发时开启能清楚地看到哪个请求被 Mock 拦截了返回了什么状态码对于调试非常有用。3. 环境搭建与配置实战3.1 项目初始化与插件安装首先确保你有一个现成的 Vue3 Vite 项目。如果没有可以用官方命令快速创建一个npm create vuelatest my-vue-app # 按照提示选择需要的特性即可 cd my-vue-app npm install然后安装vite-plugin-mock及其 peer dependencymockjs。注意mockjs是必须的因为插件内部依赖它来生成随机数据和拦截 XHR虽然我们主要用它的数据生成功能。npm install vite-plugin-mock mockjs -D # 或者使用 yarn/pnpm # yarn add vite-plugin-mock mockjs -D # pnpm add vite-plugin-mock mockjs -D注意这里有个版本兼容性的小坑。vite-plugin-mock的 2.x 版本和最新的 Vite 5 配合可能存在一些问题。我建议使用目前最稳定的组合vite-plugin-mock2.9.6和mockjs1.1.0。如果你项目中的 Vite 版本较低如 4.x可以尝试更新插件到最新版。安装时指定版本是个好习惯npm i vite-plugin-mock2.9.6 mockjs1.1.0 -D。3.2 Vite 配置的“天坑”与正确写法接下来是重头戏配置vite.config.ts。我直接把我踩坑后的最终正确配置贴出来再逐一解释每个部分。// vite.config.ts import { defineConfig } from ‘vite‘ import vue from ‘vitejs/plugin-vue‘ import { viteMockServe } from ‘vite-plugin-mock‘ // 注意导入方式 import path from ‘path‘ // 可能需要用到路径解析 export default defineConfig(({ command, mode }) { // 判断当前环境 const isDevelopment mode ‘development‘ return { plugins: [ vue(), // 配置 vite-plugin-mock viteMockServe({ // 坑点一enable 的赋值时机 enable: isDevelopment, // 仅在开发环境启用 // 坑点二mockPath 的路径基准 mockPath: ‘src/mock‘, // Mock 文件存放目录相对于项目根目录 // 坑点三ignore 的用法 ignore: /^_/, // 忽略以 _ 开头的文件比如 _utils.ts // 开启日志方便调试 logger: true, // 如果你需要支持动态导入import的 Mock 文件可能需要这个 watchFiles: true, // 坑点四localEnabled 与 prodEnabled 的区分 (v2.9.6版本) // localEnabled: command ‘serve‘, // 本地开发服务启用 // prodEnabled: command ‘build‘, // 生产构建时是否启用通常false // 注意v2.9.6 版本enable 优先级高于 localEnabled。如果设置了 enablelocalEnabled 可能失效。 }), ], // 可选的 resolve 配置如果你在 Mock 文件里用了别名 resolve: { alias: { ‘‘: path.resolve(__dirname, ‘src‘), }, }, } })关键坑点解析enablevslocalEnabled/prodEnabled这是最大的一个坑。在vite-plugin-mock的 2.9.6 版本中配置项有些混乱。文档和部分老教程会提到localEnabled和prodEnabled。但实测发现如果你设置了enable选项那么localEnabled和prodEnabled就会失效。插件会直接使用enable的值作为总开关。因此最稳妥的做法是只使用enable并根据mode或command来判断。像上面配置那样用mode ‘development‘是最清晰的。mockPath的路径问题这个路径的基准点是项目根目录即vite.config.ts通常所在的位置。我一开始设成了./mock但我的vite.config.ts在根目录所以它去找root/mock。后来我把 Mock 文件放在src/mock下这里就要改成src/mock。如果你放在别处比如./server/mock这里也要相应调整。绝对路径也是可以的用path.resolve(__dirname, ‘src/mock‘)更保险。ignore正则的坑这个值必须是一个正则表达式对象而不是字符串。写ignore: ‘^_‘是没用的必须写成ignore: /^_/。它的作用是忽略mockPath目录下文件名匹配该正则的文件。我习惯在 Mock 目录下放一个_utils.ts存放公共函数用这个配置就能避免它被当成接口定义文件解析而报错。3.3 Mock 文件目录结构与编写规范配置好 Vite 后我们在src目录下创建mock文件夹并在里面编写我们的接口模拟数据。src/ ├── mock/ │ ├── _utils.ts // 工具函数文件被ignore忽略 │ ├── user.ts // 用户相关接口 │ ├── article.ts // 文章相关接口 │ └── index.ts // 统一导出文件可选 ├── main.ts └── ...一个标准的 Mock 接口文件如user.ts应该这样写// src/mock/user.ts import { MockMethod } from ‘vite-plugin-mock‘ // 导入类型定义方便智能提示 import mockjs from ‘mockjs‘ // 定义一个用户列表的 Mock 数据生成函数 const getUserList () { return mockjs.mock({ ‘code‘: 200, ‘msg‘: ‘success‘, ‘data|10-20‘: [ // 生成10到20条数据 { ‘id|1‘: 1, // 自增ID ‘name‘: ‘cname‘, // 随机中文名 ‘age|18-60‘: 1, // 年龄18-60 ‘email‘: ‘email‘, ‘address‘: ‘county(true)‘, ‘createTime‘: ‘datetime‘, }, ], ‘total‘: 50, // 模拟总条数 }) } // 导出的数组每个对象是一个接口配置 const mockUserApis: MockMethod[] [ // 获取用户列表 GET /api/users { url: ‘/api/users‘, // 接口地址 method: ‘get‘, // 请求方法 timeout: 500, // 模拟网络延迟单位毫秒 response: ({ query }) { // response 可以是一个函数接收请求信息 console.log(‘查询参数‘, query) // 可以打印请求参数 // 模拟分页 const { page 1, size 10 } query const allData getUserList().data const start (page - 1) * size const end start size const pageData allData.slice(start, end) return { code: 200, msg: ‘success‘, data: pageData, total: allData.length, page, size, } }, }, // 获取单个用户信息 GET /api/user/:id { url: ‘/api/user/:id‘, // 支持动态参数 method: ‘get‘, response: ({ query, body, headers }) { // 可以解构出各种请求信息 const id query.id // 对于 GET参数在 query 里 // 实际开发中这里可以根据id去查找数据 return { code: 200, msg: ‘success‘, data: { id, name: ‘张三‘, age: 30, }, } }, }, // 创建用户 POST /api/user { url: ‘/api/user‘, method: ‘post‘, statusCode: 201, // 可以指定返回的 HTTP 状态码 response: ({ body }) { // POST 请求体在 body 中 console.log(‘创建用户请求体‘, body) return { code: 200, msg: ‘用户创建成功‘, data: { id: mockjs.Random.integer(1000, 9999), ...body, }, } }, }, // 模拟一个失败的接口 { url: ‘/api/error‘, method: ‘get‘, statusCode: 500, response: { code: 500, msg: ‘服务器内部错误‘, }, }, ] export default mockUserApis // 默认导出编写要点与避坑指南导出格式每个 Mock 文件必须导出一个MockMethod[]类型的数组。即使只有一个接口也要放在数组里。url匹配规则插件内部使用path-to-regexp进行匹配。支持静态路径/api/users、动态参数/api/user/:id、甚至通配符/api/*。注意路径是区分大小写的。response的类型response可以是一个直接的对象也可以是一个函数。强烈建议使用函数形式因为函数可以接收到请求的上下文对象{ url, query, body, headers }这样你才能根据不同的请求参数返回不同的数据模拟出真实的业务逻辑。timeout的妙用这个配置可以模拟网络延迟对于测试前端 loading 状态非常有用。但别设太大否则开发时体验会很差。状态码模拟通过statusCode可以模拟各种 HTTP 状态如 200成功、201创建成功、400参数错误、401未授权、404未找到、500服务器错误等方便测试前端的错误处理逻辑。使用mockjs语法在response函数返回的数据中可以嵌入mockjs的语法如‘data|10-20‘: [...]插件会自动解析并生成随机数据。这比手动写死数据灵活得多。最后如果你有多个 Mock 文件可以在mock目录下创建一个index.ts统一导出这样插件只需要扫描这一个入口文件性能更好但非必须插件会自动扫描所有文件。// src/mock/index.ts import user from ‘./user‘ import article from ‘./article‘ // 合并所有接口数组 export default [...user, ...article]4. 前端调用与联调实战4.1 发起请求的正确姿势Mock 配置好后在前端代码中调用接口就和调用真实接口完全一样。我们通常会用axios或fetch。首先安装并配置axiosnpm install axios创建一个简单的请求封装例如src/utils/request.tsimport axios from ‘axios‘ const service axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API, // 从环境变量读取开发环境通常是 ‘/api‘ timeout: 10000, }) // 请求拦截器 service.interceptors.request.use(...) // 响应拦截器 service.interceptors.response.use(...) export default service在环境变量文件.env.development中配置VITE_APP_BASE_API‘/api‘关键点这里的baseURL设置为/api。这意味着我们发起的请求都会以/api开头比如/api/users。这正是我们在 Mock 文件中定义的url的前缀。这是实现请求拦截匹配的关键。如果你的 Mock 接口url没带/api前缀或者你的请求baseURL没设置对两者就无法匹配Mock 就不会生效。4.2 在 Vue 组件中调用 Mock 接口现在在任何一个 Vue 组件中你都可以像调用真实后端接口一样发起请求了template div button click“fetchUserList“获取用户列表/button div v-if“loading“加载中.../div ul v-else li v-for“user in userList“ :key“user.id“ {{ user.name }} - {{ user.age }} /li /ul /div /template script setup lang“ts“ import { ref } from ‘vue‘ import request from ‘/utils/request‘ // 你的 axios 实例 interface User { id: number name: string age: number } const userList refUser[]([]) const loading ref(false) const fetchUserList async () { loading.value true try { // 发起请求路径是 ‘/api/users‘会被 Mock 拦截 const res await request.get(‘/api/users‘, { params: { page: 1, size: 5 } // 传递分页参数 }) if (res.data.code 200) { userList.value res.data.data } else { console.error(‘请求失败‘, res.data.msg) } } catch (error) { console.error(‘请求出错‘, error) } finally { loading.value false } } /script启动你的开发服务器 (npm run dev)点击按钮你应该能在浏览器网络面板中看到请求发往http://localhost:5173/api/users并且返回的是 Mock 生成的随机数据。同时Vite 终端控制台也会打印出类似[vite-plugin-mock] mock success: GET /api/users的日志说明 Mock 拦截成功。4.3 处理不同类型的接口带动态参数的 GET 请求// 前端调用 request.get(/api/user/${userId}) // Mock 文件中可以通过 query.id 或解析 url 拿到这个 userIdPOST 请求与请求体// 前端调用 request.post(‘/api/user‘, { name: ‘李四‘, age: 25 }) // Mock 文件的 response 函数中通过 body 可以拿到 { name: ‘李四‘, age: 25 }模拟错误情况 调用/api/error这个接口前端应该能收到状态码为 500 的响应从而触发你写在axios响应拦截器中的错误处理逻辑。这是测试前端健壮性的好方法。5. 深度踩坑记录与解决方案5.1 坑一Mock 接口不生效请求 404这是最常见的问题。打开浏览器控制台发现请求报 404或者请求直接发到了别的地方。排查步骤检查插件是否启用首先看 Vite 启动日志有没有[vite-plugin-mock]相关的日志输出。如果没有说明插件根本没加载成功。检查vite.config.ts中的enable或localEnabled配置确保在开发环境下其值为true。检查路径匹配这是最可能的原因。确认两点前端请求的 URL在浏览器 Network 面板中查看请求的完整 URL 是什么。比如是http://localhost:5173/api/users。Mock 文件中定义的url确保两者完全匹配。注意大小写、前缀/api、后缀。如果前端请求是/api/usersMock 里也必须是/api/users写成/users或/api/users/多一个斜杠都不会匹配。检查 Mock 文件导出确保你的.ts文件导出了一个正确的数组。可以尝试在vite.config.ts中临时将logger设为true并设置watchFiles: true然后修改 Mock 文件看控制台是否有重新加载的提示。检查文件扫描确认mockPath配置的目录是否正确并且目录下存在有效的 Mock 文件。可以尝试在mockPath目录下创建一个最简单的测试文件// test.ts export default [ { url: ‘/api/test‘, method: ‘get‘, response: { msg: ‘hello mock‘ }, }, ]然后访问/api/test看是否生效。端口与代理冲突如果你在vite.config.ts中还配置了server.proxy代理并且代理规则也匹配了/api那么请求可能会被代理规则优先转发到后端服务器而不是被 Mock 拦截。Mock 插件的优先级通常低于自定义代理。你需要调整代理规则或者确保后端服务器没开让代理失败从而回退到 Mock。5.2 坑二热更新HMR失效修改 Mock 文件不生效修改了 Mock 文件的数据结构或逻辑刷新页面后发现返回的还是老数据。确认watchFiles配置确保在vite.config.ts中watchFiles设置为true默认就是true。检查文件导入如果你的 Mock 文件里通过import或require引入了其他模块比如一个公共的data.json并且修改的是这个被引入的模块那么 Mock 文件本身没有变化热更新不会触发。此时需要在watchFiles中额外配置这些依赖文件的路径虽然文档支持但有时不灵。一个更稳妥的办法是将数据直接写在 Mock 文件内部或者使用动态导入。Vite 缓存问题尝试重启 Vite 开发服务器 (CtrlC然后npm run dev)。有时 Vite 的模块缓存会导致更新不及时。浏览器缓存在 Network 面板中勾选Disable cache或使用CtrlShiftR强制刷新页面。5.3 坑三TypeScript 类型报错在 Mock 文件中写response函数时可能会遇到类型错误比如说query是unknown。解决方案使用插件提供的MockMethod类型并从vite-plugin-mock导入Recordable等工具类型。import type { MockMethod, Recordable } from ‘vite-plugin-mock‘ const mockApi: MockMethod[] [{ url: ‘/api/test‘, method: ‘post‘, response: ({ body }: { body: Recordable }) { // 明确 body 类型 // 现在 body 有类型提示了 const { name } body return { ... } } }]如果还不行可以暂时使用// ts-ignore忽略单行错误或者自己断言类型({ body } as any)。但最好还是完善类型定义。5.4 坑四生产环境打包后 Mock 代码被打入包中这是非常危险的情况会导致线上代码尝试调用 Mock 接口。根本原因vite-plugin-mock的配置没有正确区分开发和生产环境。enable选项在构建时也被设置为true。解决方案确保你的vite.config.ts配置中enable的值是严格根据环境变量判断的。// 正确做法 viteMockServe({ enable: process.env.NODE_ENV ‘development‘, // 或 mode ‘development‘ // ...其他配置 })你可以在打包后检查生成的dist目录中的index.html和.js文件搜索mock或你定义的 Mock 接口 URL不应该出现任何相关代码。最保险的方法是在构建脚本中明确设置环境变量// package.json { “scripts“: { “build“: “NODE_ENVproduction vite build“, // 或者使用 cross-env 跨平台 “build:prod“: “cross-env NODE_ENVproduction vite build“ } }5.5 坑五与vitejs/plugin-legacy等插件冲突某些插件可能会修改 Vite 服务器的行为与vite-plugin-mock产生冲突。如果遇到奇怪的问题可以尝试调整插件在plugins数组中的顺序。通常将viteMockServe放在靠后的位置但在处理 HTML 的插件之前是一个好的尝试。6. 高级技巧与最佳实践6.1 模拟复杂的业务逻辑与状态Mock 不仅仅是返回静态数据。利用response函数你可以模拟出非常复杂的业务场景。示例模拟登录状态与权限// src/mock/auth.ts let currentUser: any null const token ‘mock-jwt-token‘ export default [ { url: ‘/api/login‘, method: ‘post‘, response: ({ body }) { const { username, password } body if (username ‘admin‘ password ‘123456‘) { currentUser { id: 1, username: ‘admin‘, role: ‘admin‘ } return { code: 200, msg: ‘登录成功‘, data: { user: currentUser, token }, } } else { return { code: 401, msg: ‘用户名或密码错误‘, } } }, }, { url: ‘/api/userInfo‘, method: ‘get‘, response: ({ headers }) { // 模拟检查 Token if (headers.authorization ! Bearer ${token}) { return { code: 401, msg: ‘未授权‘ } } return { code: 200, msg: ‘success‘, data: currentUser, } }, }, { url: ‘/api/logout‘, method: ‘post‘, response: () { currentUser null return { code: 200, msg: ‘已退出登录‘ } }, }, ]6.2 使用 Mock.js 语法生成更逼真的数据mockjs的语法非常强大可以生成各种随机但符合规则的数据让 Mock 数据更接近真实。import mockjs from ‘mockjs‘ const productList mockjs.mock({ ‘data|50‘: [ { ‘id|1‘: 1000, ‘name‘: ‘ctitle(5, 10)‘, // 随机中文标题 ‘price|10-1000.2‘: 1, // 10-1000之间的价格保留两位小数 ‘status|1‘: [‘on_sale‘, ‘sold_out‘, ‘draft‘], // 随机取一个状态 ‘cover‘: “image(‘200x100‘, ‘#4A7BF7‘, ‘Product‘)“, // 生成图片占位符 ‘createdAt‘: ‘datetime‘, ‘category‘: { ‘id|1-10‘: 1, ‘name‘: ‘cword(2,4)‘, }, }, ], }) // 然后在 response 中返回 productList.data6.3 如何平滑切换到真实后端接口当后端接口开发完成后我们需要从 Mock 无缝切换到真实接口避免大量修改前端代码。环境变量控制这是最推荐的方式。将后端 API 的基础地址通过环境变量管理。.env.development:VITE_APP_API_BASE/api(指向 Mock).env.production:VITE_APP_API_BASEhttps://real-api.example.com在axios配置中使用baseURL: import.meta.env.VITE_APP_API_BASE。这样开发环境请求被 Mock 拦截生产环境则发往真实服务器。条件编译不推荐在 Mock 文件中可以通过环境变量判断是否启用某个 Mock 接口。但这样会使 Mock 文件变得复杂。彻底移除插件当所有接口都就绪后直接在vite.config.ts中注释掉viteMockServe插件的配置并删除src/mock目录即可。由于你的请求代码是基于环境变量的所以切换过程是无感的。6.4 组织大型项目的 Mock 文件对于接口很多的项目良好的文件组织至关重要。按业务模块分文件如user.ts,order.ts,product.ts。使用索引文件在mock目录下创建index.ts统一导入并导出所有模块。提取公共类型和工具函数创建_types.ts定义共享的接口类型创建_utils.ts存放数据生成函数、分页逻辑等。模拟数据库状态对于需要跨接口共享状态的情况如购物车可以在一个单独的state.ts文件中用变量模拟数据库所有 Mock 文件都操作这个共享状态。经过这一整套从安装、配置、编写到调试和优化的流程走下来vite-plugin-mock这个插件基本上就能在你的 Vue3 Vite 项目中稳定、高效地工作了。它确实能极大提升前后端并行开发的效率关键是要理解其原理并避开我上面提到的那些坑。最后再强调一次务必确保生产构建时 Mock 功能被完全禁用这是上线前的最后一道安全检查。