上週四下午,我正在調樣式,突然電腦風扇開始咆哮。
打開任務管理器:內存佔用 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 請求持續優化中:性能、匹配規則和用戶體驗會不斷迭代,歡迎反饋與建議在網頁加載前,偷偷替換掉瀏覽器原生的 fetch 和 XMLHttpRequest ,讓所有網絡請求先經過我們的"檢查站",符合規則的就返回假數據,不符合的就放行。
正常情況:
網頁代碼 → fetch('/api/user') → 瀏覽器發送真實請求 → 伺服器
插件介入後:
網頁代碼 → fetch('/api/user')
↓
我們的假 fetch(檢查是否需要 Mock)
↓
需要 Mock?
├─ 是 → 直接返回假數據 ✅
└─ 否 → 調用真正的 fetch 發送請求 → 伺服器
// 1. 保存原始方法
const 真正的fetch = window.fetch;
// 2. 替換成我們的方法
window.fetch = function(url) {
// 3. 檢查是否需要 Mock
if (url.includes('/api/user')) {
console.log('攔截成功!返回假數據');
return Promise.resolve({
json: () => ({ name: 'Mock User' })
});
}
// 4. 不需要 Mock,調用原始方法
return 真正的fetch(url);
};
關鍵點:
open 和 send 方法quick-mock/
├── manifest.json # 插件配置文件
├── popup.html # 彈窗頁面
├── popup.css # 彈窗樣式
├── popup.js # 彈窗邏輯
├── content.js # 內容腳本
└── injected.js # 注入腳本
chrome://extensions/🔗 文件源代碼鏈接:
🔗 文件源代碼鏈接:
作用:處理用戶配置mock操作
功能:
🔗 文件源代碼鏈接:
作用:連接插件和網頁的橋梁
能力:
職責:
🔗 文件源代碼鏈接:
作用:真正執行攔截的代碼
能力:
可以訪問網頁的 JavaScript 環境(window.fetch)可以重寫 fetch 和 XMLHttpRequest不能訪問 Chrome API(chrome.storage)職責:
🔗 文件源代碼鏈接:
🔗 文件源代碼鏈接:
chrome://extensions/┌─────────────┐
│ 用戶操作 │ 在 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 Script 運行在隔離的沙箱中,無法訪問網頁的 window.fetch。
// content.js 中這樣做是無效的!
window.fetch = function() {
console.log('攔截失敗!'); // 網頁看不到這個修改
};
不行! 因為 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)通信
客戶發起請求 → 大堂經理攔截 → 對講機問管理員"要不要放行"
→ 管理員查保險箱 → 回覆"不放行,給假鈔" → 大堂經理返回假鈔
run_at: "document_start" ?// manifest.json
"run_at": "document_start" // 在 HTML 解析前運行
原因: 如果網頁在插件加載前就執行了 fetch('/api/data'),我們就攔截不到了。
web_accessible_resources ?// manifest.json
"web_accessible_resources": [
{
"resources": ["injected.js"],
"matches": ["<all_urls>"]
}
]
原因: 默認情況下,網頁無法加載插件內部的文件(跨域限制)。這個配置相當於給 injected.js 開了"綠色通道"。
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 這個"傳送門"通信。
const originalFetch = window.fetch; // 必須先保存
window.fetch = async function(url) {
if (needMock) {
return mockResponse;
}
return originalFetch(url); // 不 Mock 時調用原始方法
};
原因: 如果不保存,所有請求都會被攔截,無法發送真實請求。
window.fetch 和 XMLHttpRequest,在網頁代碼執行前劫持請求postMessage(唯一方式)document_start(越早越好)如果這個插件幫到了你,歡迎:
這個插件會持續優化,支持更豐富的配置項、更好的互動等
如果你有好的想法,歡迎在評論區或 GitHub Issue 提出!
如果覺得對您有幫助,歡迎點贊 👍 收藏 ⭐ 關注 🔔 支持一下!
往期實戰推薦:
- Vue3 後台分頁寫膩了?我用 1 個 Hook 刪掉 90% 重複代碼(附源碼)
- ⚡ 一個Vue自定義指令搞定絲滑拖拽列表,告別複雜組件封裝
- 🔥 這才是 Vue 驅動的 Chrome 插件工程化正確打開方式
- 老闆問我:AI真能一鍵畫廣州旅遊路線圖?我用 MCP 現場開圖
- 【前端效率工具】:告別右鍵另存,不到 50 行代碼一鍵批量下載網頁圖片
- 女朋友炸了:剛打開的網頁怎麼又沒了?我反手甩出一鍵恢復按鈕!
- 你家孩子又偷玩網頁遊戲? 試試這個防沉迷工具
- 她說想要浪漫,我把瀏覽器滑鼠換成了柴犬,點一下就有煙花(附源碼)
- 女朋友被鏈接折磨瘋了,我寫了個工具一鍵解救
- 上班摸魚看掘金,老闆突然出現在身後...