【前端效率工具】再也不用 APIfox 联调!零侵入 Mock,全程不改代码、不开代理
上周四下午,我正在调样式,突然电脑风扇开始咆哮。
打开任务管理器:「内存占用 15.5G / 16G」。
我人傻了,16G 内存开发个前端项目还能卡?
仔细一看:
「VSCode」:2.1G(跑着项目,正常) 「Chrome 30 个标签页」:4.5G(掘金、Stack Overflow、GitHub、文档...能理解) 「APIfox」:1.5G(???) 「微信 + 企业微信」:1.1G(没办法,工作要用) 「其他若干进程…」:...
「APIfox这一个工具,占了 1.5G?」
关键是,我只是想 Mock 几个接口而已,为什么要:
装一个 500M+ 的客户端 开代理(还得记得关) 来回切窗口(浏览器 → APIfox → VSCode) 占用 1.5G 内存
我试过其他方案:
「在代码里写死 Mock 数据」 → 有时候忘记删掉,上线差点带着假数据冲进生产 「用 Mock.js」 → 配置太麻烦,还要改代码
我想要的是:不装客户端、不改代码、不开代理、不耗内存、一键切换 Mock 开关 。 于是,我花了一个周末,做了一个chrome浏览器插件。
「现在mock不用 APIfox,内存占用降了不少,电脑顺多了,浏览器里一键 Mock,调试又快又省心。」
想知道怎么实现的吗?接下来将从零开始,和你一起实现这个mock小工具
效果演示: 下面是插件在拦截 fetch/ajax 请求并返回 Mock 数据时的演示:
插件功能预览
拦截页面的 fetch、ajax请求规则支持模糊匹配和完全匹配 支持按 HTTP 方法过滤(GET/POST/PUT/DELETE) 自定义返回 JSON 数据 持续优化中:性能、匹配规则和用户体验会不断迭代,欢迎反馈与建议
根本原理:劫持浏览器的网络请求方法
一句话总结
「在网页加载前,偷偷替换掉浏览器原生的」「fetch」「和」「XMLHttpRequest」「,让所有网络请求先经过我们的"检查站",符合规则的就返回假数据,不符合的就放行。」
原理:
正常情况:网页代码 → fetch('/api/user') → 浏览器发送真实请求 → 服务器插件介入后:网页代码 → fetch('/api/user')↓我们的假 fetch(检查是否需要 Mock)↓需要 Mock?├─ 是 → 直接返回假数据 ✅└─ 否 → 调用真正的 fetch 发送请求 → 服务器
具体实现(简化版)
// 1. 保存原始方法const 真正的fetch = window.fetch;// 2. 替换成我们的方法window.fetch = function(url) {// 3. 检查是否需要 Mockif (url.includes('/api/user')) {console.log('拦截成功!返回假数据');return Promise.resolve({json: () => ({ name: 'Mock User' })});}// 4. 不需要 Mock,调用原始方法return 真正的fetch(url);};
「关键点:」
必须先保存原始方法,否则无法发送真实请求 必须在网页加载前执行,否则拦截不到早期请求 XMLHttpRequest 同理,重写 open和send方法
项目结构
quick-mock/├── manifest.json # 插件配置文件├── popup.html # 弹窗页面├── popup.css # 弹窗样式├── popup.js # 弹窗逻辑├── content.js # 内容脚本└── injected.js # 注入脚本
🚀 快速开始
获取代码
「GitHub」:https://github.com/Teernage/quick-mock ⭐如果对您有帮助欢迎Star 「Gitee」:https://gitee.com/xuzhenxin110/quick-mock
一键安装
打开Chrome浏览器 地址栏输入: chrome://extensions/打开右上角"开发者模式" 点击"加载已解压的扩展程序" 选择quick-mock文件夹 搞定!扩展安装完成!
深入解析:从零开始理解插件实现
界面层(用户交互)
1. 「popup.html」 - 插件的"脸面"- 作用:用户点击插件图标看到的弹窗界面
包含:输入框、按钮、规则列表
❝🔗 「文件源码链接」:
❞
GitHub: https://github.com/Teernage/quick-mock/blob/main/popup.htmlGitee: https://gitee.com/xuzhenxin110/quick-mock/blob/main/popup.html
2. 「popup.css」 - 界面样式
❝🔗 「文件源码链接」:
❞
GitHub: https://github.com/Teernage/quick-mock/blob/main/popup.cssGitee: https://gitee.com/xuzhenxin110/quick-mock/blob/main/popup.css
3. 「popup.js」 - 界面逻辑
作用:处理用户配置mock操作
功能:
点击"添加规则" → 保存到 chrome.storage 点击"删除" → 从 chrome.storage 移除 渲染规则列表
❝🔗 「文件源码链接」:
❞
GitHub: https://github.com/Teernage/quick-mock/blob/main/popup.jsGitee: https://gitee.com/xuzhenxin110/quick-mock/blob/main/popup.js
核心层(拦截逻辑)
4. 「content.js」 - "中间人"
作用:连接插件和网页的桥梁
能力:
可以访问 Chrome API(读取 chrome.storage) 可以访问网页 DOM 不能访问网页的 JavaScript 环境(window.fetch)
职责:
把 injected.js 注入到网页 读取用户配置的 Mock 规则 通过 postMessage 与 injected.js 通信
❝🔗 「文件源码链接」:
❞
GitHub: https://github.com/Teernage/quick-mock/blob/main/content.jsGitee: https://gitee.com/xuzhenxin110/quick-mock/blob/main/content.js
5. 「injected.js」 - "劫匪"
作用:真正执行拦截的代码
能力:
可以访问网页的 JavaScript 环境(window.fetch)可以重写 fetch 和 XMLHttpRequest不能访问 Chrome API(chrome.storage)
职责:
重写 window.fetch 重写 XMLHttpRequest.prototype.open/send 拦截请求,询问 content.js 是否需要 Mock 根据回复决定返回假数据还是真实请求
❝🔗 「文件源码链接」:
❞
GitHub: https://github.com/Teernage/quick-mock/blob/main/injected.jsGitee:https://gitee.com/xuzhenxin110/quick-mock/blob/main/injected.js
💡 深入理解脚本通信机制
如果你想深入了解 background/service worker、content script、injected script、popup 之间真实的运行上下文与消息链路,强烈推荐阅读: 「〈大部分人都错了!这才是 Chrome 插件多脚本通信的正确姿势〉」 这篇文章用原理+示例拆解了常见误区与正确做法,能帮你更透彻地理解本插件的通信机制。
配置层
6. 「manifest.json」 - 插件的"身份证"
❝🔗 「文件源码链接」:
❞
GitHub: https://github.com/Teernage/quick-mock/blob/main/manifest.jsonGitee:https://gitee.com/xuzhenxin110/quick-mock/blob/main/manifest.json
安装扩展
打开Chrome浏览器 地址栏输入: chrome://extensions/打开右上角"开发者模式" 点击"加载已解压的扩展程序" 选择quick-mock文件夹 搞定!扩展安装完成!
文件协作流程(完整链路)
第一阶段:用户配置规则
┌─────────────┐│ 用户操作 │ 在 popup.html 输入 Mock 规则└──────┬──────┘│↓┌─────────────┐│ popup.js │ 点击"添加"按钮└──────┬──────┘││ chrome.storage.local.set({ mockRules: [...] })↓┌─────────────┐│Chrome Storage│ 持久化存储规则(关闭浏览器也不丢失)└─────────────┘
第二阶段:页面加载时注入脚本
用户打开网页(如 https://example.com)↓┌─────────────┐│manifest.json│ 检测到匹配的 URL└──────┬──────┘││ 自动注入↓┌─────────────┐│ content.js │ 在网页上下文运行(但在隔离沙箱)└──────┬──────┘││ 创建 <script> 标签│ script.src = chrome.runtime.getURL('injected.js')│ document.head.appendChild(script)↓┌─────────────┐│ injected.js │ 在网页真实环境运行└──────┬──────┘││ window.fetch = 我们的假fetch;↓拦截就绪!
第三阶段:拦截请求(实时通信)
网页代码执行:fetch('/api/user')↓┌─────────────────────┐│ injected.js│ 我们的假 fetch 被调用└──────┬──────────────┘││ window.postMessage({│ type: 'MOCK_REQUEST',│ url: '/api/user',│ method: 'GET'│ })↓┌─────────────────────┐│ content.js │ 监听 message 事件└──────┬──────────────┘││ 1. 读取 chrome.storage.local│ 2. 遍历规则,检查是否匹配│ 3. 找到匹配规则↓│ window.postMessage({│ type: 'MOCK_RESPONSE',│ shouldMock: true,│ mockData: { name: 'Mock User' }│ })↓┌─────────────────────┐│ injected.js │ 收到回复└──────┬──────────────┘││ if(shouldMock) {│ return new Response(JSON.stringify(mockData));│ } else {│ return 真fetch(url); // 调用原始方法│ }↓网页代码收到响应:{ name: 'Mock User' }
为什么要分 content.js 和 injected.js?
只用 Content Script 行不行?
「不行!」 因为 Content Script 运行在隔离的沙箱中,无法访问网页的 window.fetch。
// content.js 中这样做是无效的!window.fetch = function() {console.log('拦截失败!'); // 网页看不到这个修改}
只用 Injected Script 行不行?
「不行!」 因为 Injected Script 无法访问 chrome.storage 等 Chrome API,无法读取用户配置的 Mock 规则。
### 正确方案:两者配合用户配置 Mock 规则↓存储到 chrome.storage (Popup)↓读取规则 (Content Script) ← 可以访问 Chrome API↓通过 postMessage 通信↓拦截 fetch (Injected Script) ← 可以修改 window.fetch
通俗比喻
content.js = 银行金库管理员- 有钥匙(Chrome API 权限)- 能读取保险箱(chrome.storage)- 但不能直接接触客户(网页 JavaScript)injected.js = 银行大堂经理- 直接面对客户(网页代码)- 能拦截客户请求(重写 fetch)- 但没有金库钥匙(无法访问 chrome.storage)解决方案:两人用对讲机(postMessage)通信客户发起请求 → 大堂经理拦截 → 对讲机问管理员"要不要放行"→ 管理员查保险箱 → 回复"不放行,给假钞" → 大堂经理返回假钞
关键技术点总结
1. 「为什么要用」「run_at: "document_start"」「?」
// manifest.json"run_at": "document_start" // 在 HTML 解析前运行
「原因:」 如果网页在插件加载前就执行了 fetch('/api/data'),我们就拦截不到了。
2. 「为什么要用」「web_accessible_resources」「?」
// manifest.json"web_accessible_resources": [{"resources": ["injected.js"],"matches": ["<all_urls>"]}]
「原因:」 默认情况下,网页无法加载插件内部的文件(跨域限制)。这个配置相当于给 injected.js 开了"绿色通道"。
3. 「为什么用」「postMessage」「而不是全局变量?」
// ❌ 错误做法window.mockRules = [...]; // content.js 设置console.log(window.mockRules); // injected.js 读取(读不到!)// ✅ 正确做法window.postMessage({ type: 'MOCK_REQUEST' }, '*'); // injected.js 发送window.addEventListener('message', (e) => { ... }); // content.js 接收
「原因:」 content.js 和 injected.js 虽然在同一个网页,但 JavaScript 环境是隔离的,就像两个平行世界,只能通过 postMessage 这个"传送门"通信。
4. 「为什么要保存原始 fetch、xhr?」
const originalFetch = window.fetch; // 必须先保存window.fetch = async function(url) {if (needMock) {return mockResponse;}return originalFetch(url); // 不 Mock 时调用原始方法};
「原因:」 如果不保存,所有请求都会被拦截,无法发送真实请求。
总结
核心要点
「根本原理」:重写 window.fetch和XMLHttpRequest,在网页代码执行前劫持请求「为什么分两个脚本」:content.js 能读插件配置,injected.js 能拦截请求 「怎么通信」: postMessage(唯一方式)「什么时候注入」: document_start(越早越好)「为什么能拦截」:在网页代码执行前就替换了原生方法
适用场景
前端开发时,后端接口还没好 调试线上 Bug,想临时改返回数据 演示 Demo,不想依赖真实服务器 自动化测试,需要稳定的 Mock 数据 接口文档不完善,想自己造数据测试
最后
如果这个插件帮到了你,欢迎:
如果觉得对您有帮助,欢迎点赞 👍 收藏 ⭐ 关注 🔔 支持一下! ⭐ 「GitHub Star 支持」https://github.com/Teernage/quick-mock 💬 「评论区聊聊你的使用场景」
这个插件会持续优化,支持更丰富的配置项、更好的交互等