写在前面的话
花 10 分钟,插入 100 行代码,0 额外依赖,让你项目支持 mock,支持任意 webpack 工程,简单易用
目前已将公司传统 PC 项目和 React-Native 项目进行了改造,效果不错,特意分享出来
写了一个小 demo github.com/imaoda/gene… 供体验
本方案的优势
常规 mock 的手段有:
- 利用 webpack 的 devSever
- 启动 node 层的服务
- 利用 charles 代理
- 利用 chrome 插件去代理到 mock 服务
- 拦截请求 (本方案)
这些原因阻碍了我们 mock 的脚步:
- 需要各种配置,用起来太麻烦
- 学习成本高,配置不对不生效
- React-Native、小程序 等环境用不了
- 移动端真机调试 mock 不了
- 对项目新人有学习和配置成本
本方案的优势:
- 无复杂配置
- 0 依赖
- 傻瓜式的使用体验
- 基于老项目改造很容易 (我用了不到10分钟)
- mock 数据在项目集中管理维护
- 即配置即生效
- 支持所有类型的请求 (xhr fetch wx.request ...)
- 不影响打包体积
- 生产环境不受影响
方案原理
本质上还是请求拦截,并非新花样,重点在后面的细节的处理技巧,会让这个 mock 方案变得丝滑易用
常见的请求比如 axios
、fetch
、wx.request
,并不是所有请求都有拦截器方法,况且拦截器也不是万能的,通常我们的项目都会进行 请求再封装,做诸如以下的事情:
- 根据环境填充 baseURL
- cookie / storage 搬运
- 异常情况弹窗
- 移动端维护请求队列显示 loading 菊花
- fetch 提供 timeout
- 今天的主角 mock
比如我们封装 fetch,让请求支持 mock
import mockList from '../../mock'; // 引入写好的 mock 数据
// 封装 fetch,如果 url 命中 url 则直接返回 mock 数据,否则走正常请求
export default async function request(url) {
const resArr = mockList.filter(item => item.url === url);
return resArr[0] || (await fetch(url).then(i => i.json()));
}
处理技巧
如何批量引入 mock 数据?
我们会将整个 mock 文件夹下文件引入,作为 mock 数据集,比如,我们在 mock/index.js
下引入所有其他文件,并合并导出,如下图:
麻烦的事来了,一旦目录结构发生变化,比如新增,删除,批量调整,嵌套文件夹等,我们都需要频繁的修改 mock/index.js
文件,引入这些数据,并合并,再导出
那么怎么才能方便的全量引入呢?
这时,有同学可能会说:用 fs.readdirSync
读取整个 mock 文件夹呀!
思路是对的,但是无法成行;因为毕竟这是前端工程,而非 node 工程,fs.readdirSync
是运行时处理的函数,而前端工程的运行时已经在浏览器端了,浏览器端何来的 fs
?
我们可以利用 require.context 来解决,该 API 既非 commonjs 语法,也不是 ES module 语法,而是由 webpack
提供的
webpack
在编译时,词法解析到该 api,会将这段代码放入 node 运行时去 执行
,并将结果拼接到打包好的 module
中
使用 require.context
进行批量引入
前面啰嗦了半天理论,接下来使用 require.context 来引入。该套路比较固定,因此,不用刻意去理解
let routes = [] // 收集所有 mock 数据
// 遍历目录下 mock,开启递归遍历,匹配 (js|ts|jsx|tsx) 结尾的文件
const ctx = require.context('../../mock', true, /(js|ts|jsx|tsx)$/);
// 对命中的文件进行引入,用 cjs 语法引入 esm 导出的,需加 .default
ctx.keys().forEach((file) => {
const content = ctx(file).default;
if (content instanceof Array) {
routes = [...routes, ...content];
} else console.warn(`mock 文件${file}格式不是数组`);
});
// 导出所有
export default routes;
开启 mock 的技巧
如何开启、关闭 mock,方式有很多中,总体来说,最佳实践就是在 package.json 的 script 里新增一个命令,比如
"script": {
"build": "...",
"dev": "...",
"mock": "xxx dev --mock"
}
通过 webpack 的 definePlugin 为代码注入变量,告知是否走的 yarn mock
命令启动的调试:
// webpack 配置中,plugins 里增加:
new webpack.DefinePlugin({ __IS_MOCK__, process.argv.includes('--mock') }) // 简单起见就不用 minimist 解析参数了
此时,业务代码里已经注入了常量 __IS_MOCK__
最后,我们再小改动一下之前封装的 request
export default async function request(url) {
// 如果未开启 mock 直接返回
if(!__IS_MOCK__) return await fetch(url).then(i => i.json())
const resArr = mockList.filter(item => item.url === url);
return resArr[0] || (await fetch(url).then(i => i.json()));
}
配置好了,开始使用!
提交到 git 仓库上,任意组员拉下来,只需 2 步,开启 mock
- 在 mock 文件夹下创建文件,内容如
// xx.js 文件
export default [
{ url: '/user/id', data: 10086, code: 0 },
{ url: '/user/name', data: 'wyf', code: 0 },
];
yarn mock
(具体根据你刚才配置的命令
其他深入的探讨点
至此,主要内容已经完成,如果还想在项目中继续优化探索,可以继续看下面的部分
是否 gitignore mock 文件夹
ignore 之后,mock 文件夹不会被 git 仓库收录,也就是多人合作开发的时候,大家的 mock 文件互不干扰。
我个人理解:
- 适合 git 收录的场景:你的项目过于依赖 mock,比如后端环境经常挂,或者在浏览器环境没有宿主提供的鉴权信息(如 app 会给 webview 带一些登陆 token 等),导致无法获取数据
- 不适合 git 收录的场景:比如你的项目只在新需求新增几个接口,供后端没有 ready 时 mock,其他接口正常走请求
值得注意的是,如果 ignore 掉了 mock 文件夹,require.context
会报错,此时加上 try catch,webpack 就会在找不到 mock 文件夹时跳过不处理,如:
let routes = [] // 收集所有 mock 数据
try {
const ctx = require.context('../../mock', true, /(js|ts|jsx|tsx)$/);
/** 略 **/
} catch (e) {}
// 导出所有
export default routes;
是否影响打包的包大小
如果工程 gitignore 了 mock 文件夹,上传的到 docker 构建的时候,是没有该文件夹的,因此不会影响到打包的体积
如果没有 ignore,且不做任何处理,很不幸的是,你的最终 bundle 会带上这些 mock 数据。解决方案就是在执行 require.context
前加入 if 条件,仅在 mock 打开的时候打包,如:
let routes = [] // 收集所有 mock 数据
try {
if(__IS_MOCK__) {
const ctx = require.context('../../mock', true, /(js|ts|jsx|tsx)$/);
/** 略 **/
}
} catch (e) {}
// 导出所有
export default routes;
是否支持随机生成 mock 数据等进阶需求
本方案是一个非常基础的工程方案,同时由于它没有过多依赖,所以非常灵活,你完全可以在拦截阶段,对获取的数据进行处理,比如自定义一些模板语法
本方案的优势,在于灵活,不仅仅对 XHR 请求进行拦截,任意环境,比如小程序、fetch 等