【免费下载链接】service-mocker
🚀 Next generation frontend API mocking framework
在前端开发中,"API mock"(接口模拟)工具往往需要单独搭建一个 mock 服务器:写 JSON、开端口、配代理,流程繁琐。而Service Mocker是一个新一代的前端 API mocking 框架,它借助Service Worker的机制,直接在浏览器内部拦截请求并完成模拟,完全不需要任何真实服务器。本文完整拆解 Service Mocker 在 modern 模式下的 Service Worker 握手原理:客户端如何与服务工作者建立连接,又如何拿到"拦截请求"的权限。🚀
一图看懂整体架构
Service Mocker 的典型用法只有两个文件:
- server.js(服务器脚本):用 express 风格的路由定义模拟规则,它实际上是被注册为 Service Worker 运行的;
- app.js(客户端脚本):页面中唯一需要引入的脚本,负责与 Service Worker 完成握手。
// server.js —— 定义模拟规则 import { createServer } from 'service-mocker/server'; const { router } = createServer(); router.get('/greet', (req, res) => { res.send('Hello new world!'); });握手完成后,页面发起的任何fetch/XMLHttpRequest请求都会被 Service Worker 截获:命中路由规则就返回模拟数据,请求根本不会走到真实网络。这就是"无服务器拦截"的核心。⚡
模式判定:modern 还是 legacy?
Service Mocker 内部有两种工作模式,入口在 src/client/index.js 的createClient()中自动选择:
| 判定条件 | 结果 |
|---|---|
浏览器支持 Service Worker(serviceWorker in navigator) | 继续判定 |
页面运行在安全环境(HTTPS,或localhost/127.0.0.1) | ✅ 创建ModernClient(Service Worker 模式) |
| 任一条件不满足 | ⚠️ 控制台警告并降级为LegacyClient(monkey-patch 模式) |
另外也支持显式指定:createClient('server.js', { forceLegacy: true })可强制使用 legacy 模式。本文聚焦 modern 模式——这也是 Service Mocker 的"完全体"。
握手第一步:注册 Service Worker
ModernClient初始化时调用 src/client/modern/register.js 中的register(),核心只做两件事:
navigator.serviceWorker.register(scriptURL, { scope: location.pathname })—— 把你的 server.js 注册为 Service Worker,作用域限定在当前页面路径;- 注册成功后立即调用
connect()进入握手阶段。
这里有一个容易踩坑的细节:根据浏览器规范,Service Worker 的作用域默认不能超过脚本自身所在路径。如果你的 server.js 放在非根路径(如assets/js/server.js),需要在 dev server 上配置Service-Worker-Allowed响应头来解除限制,详见 docs/caveats.md。
握手核心:connect 与 handshake 逐帧解析
握手逻辑集中在 src/client/modern/connect.js,流程如下:
① 等待 Worker 就绪
先记录hasController(当前页面是否已被某个 Worker 控制),然后await navigator.serviceWorker.ready。注释里写得很清楚:Chrome 在页面重载后有时会"卡住",所有动作都要等 Worker 就绪后再执行。
② 获取(最新)注册
- 如果页面已有控制器且未跳过更新检查,会走 src/client/modern/get-newest-reg.js 的
getNewestReg():执行registration.update()探测新版本,发现新 Worker 后等待其激活(statechange到activated); - 否则直接
getRegistration()拿到当前注册对象。
③ 发送握手机器码
拿到注册对象后,handshake()执行两跳消息:
| 顺序 | 消息 | 发送条件 | 作用 |
|---|---|---|---|
| 1 | @mocker/request_claim | 页面尚无控制器(首次安装等"未受控"状态) | 请求 Worker 立即接管当前页面 |
| 2 | @mocker/ping | 总是发送 | 探活并确认通道连通,等待@mocker/pong回包 |
所有动作常量定义在 src/constants/action.js,并统一加上@mocker/前缀以避免与其他页面的消息冲突。握手成功后控制台会打印:
[mocker:modern] connection established消息是怎么传输的?
消息通道由 src/utils/send-message-request.js 实现:
- 每次通信创建一个
MessageChannel,通过port1/port2成对传递,收到回复后立即关闭端口释放内存; - 内置3 秒超时,超时会抛出
messaging timeout错误——所以如果握手长时间不完成,通常是 Worker 没有成功加载; - 回复中若带
error字段则以 reject 抛出,否则 resolve。
可以把它理解成一次轻量级的"TCP 三次握手":请求—应答—确认。
Worker 端的回应:claim 接管是关键
握手的服务端实现非常简洁,就在 src/server/server.js 的事件监听里:
- 收到
PING→ 回一个PONG; - 收到
REQUEST_CLAIM→ 先执行await self.clients.claim(),再回ESTABLISHED。
为什么要clients.claim()?因为按 Service Worker 默认行为,新安装的 Worker 只会接管之后"新打开"的页面,当前已存在的页面不会被控制。claim()让 Worker 主动接管所有已打开的客户端页面,这正是"无服务器拦截"能够立即生效的前提。
另外两个生命周期细节也值得注意:
self.addEventListener('install', (event) => { event.waitUntil(self.skipWaiting()); // 新版本不排队,直接激活 }); self.addEventListener('activate', (event) => { event.waitUntil(self.clients.claim()); // 激活时接管所有页面 });skipWaiting()保证路由规则一改就能生效,不用反复强刷;activate里的claim()则确保页面重载后依然处于受控状态。
握手完成后:请求是如何被拦截的
握手的意义在于"拿到控制权和确认连通性",真正的拦截发生在 Service Worker 的fetch事件上。同样在 src/server/server.js 中:
self.addEventListener('fetch', (event) => { MockerRouter.routers.some((router) => { return router._match(event); // 逐个路由表尝试匹配 }); });匹配逻辑在 src/server/router.js 的_match()中,依次校验:
- 请求 origin 与路由所属 origin 一致;
- 去掉 baseURL 前缀后,路径命中
path-to-regexp规则; - HTTP 方法匹配(或注册了
all)。
命中后同步构造MockerRequest和MockerResponse,先跑完所有中间件,再执行你写的回调(res.send(...)/res.json(...)等)。由于这一切都发生在 Service Worker 上下文内,返回的仍是标准Response对象——在浏览器 Network 面板里能看到"真实"的请求与响应头,调试体验与真接口几乎无差别。✅
容易忽略的三个细节
client.ready之前,任何请求都不会被拦截。这是官方文档(docs/api.md)明确强调的:所有初始化代码应放进client.ready.then(...)里执行,否则页面启动早期的请求会直接打到真实网络;- Worker 更新自动重连:src/client/modern/client.js 的
_autoSyncClient()监听controllerchange事件,Worker 升级后自动与新 Worker 重新握手(此时跳过更新检查),并在控制台提示 "mocker updated, reload your requests to take effect"; - 同一端口多项目会互相干扰:因为 Worker 按 origin + scope 拦截请求,切换到一个没有 mock 的项目时可能被"误拦截",此时可调用
client.unregister()或到浏览器开发者工具中手动注销 Worker,详见 docs/caveats.md。
快速上手清单 📋
- 安装(仅开发依赖即可):
npm install service-mocker --save-dev; - 编写 server.js 定义路由(参考上文示例,完整 API 见 docs/api.md);
- 页面中仅引入客户端脚本,
createClient('server.js')后在client.ready.then(...)中发请求; - 用
http://localhost:3000这类本地地址打开页面(localhost 免 HTTPS,满足安全环境要求); - 看到控制台输出
[mocker:modern] connection established,说明 Service Worker 握手成功,拦截已就绪。
写在最后
Service Mocker 的 modern 模式用一次"Service Worker 握手"替代了传统 mock 方案里整个服务器:register注册、claim接管、PING/PONG确认、fetch事件拦截——每一步都依托浏览器原生能力,零依赖、零端口、零代理。理解这套握手流程后,你就能准确判断 mock 为何"没生效":是没握手成功(等ready)、是作用域限制(scope 路径问题),还是 Worker 被残留的旧注册占用(unregister 即可)。🎉
更多使用细节可以查看仓库内的 README.md 与 docs/api.md。
【免费下载链接】service-mocker
🚀 Next generation frontend API mocking framework
相关推荐
Service Mocker源码阅读:MockerRouter与MockerResponse请求拦截流程全剖析
Service Mocker源码阅读:MockerRouter与MockerResponse请求拦截流程全剖析 Service Mocker 是一个基于 Ser
MSW(Mock Service Worker):前端请求模拟利器
MSW(Mock Service Worker):前端请求模拟利器 项目介绍 MSW 是一个强大的服务工作者库,专为 mocking API 请求而设计,允许开
测试开发工具前端Mock Service Worker:模拟API请求的开源利器
Mock Service Worker:模拟API请求的开源利器 Mock Service Worker(MSW)是一个用于浏览器和Node.js的开源库,主要
测试开发工具前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考