0依赖 100行代码,为项目添加 mock

2,884 阅读5分钟

写在前面的话

花 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 方案变得丝滑易用

常见的请求比如 axiosfetchwx.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

  1. 在 mock 文件夹下创建文件,内容如
// xx.js 文件
export default [
  { url: '/user/id', data: 10086, code: 0 },
  { url: '/user/name', data: 'wyf', code: 0 },
];
  1. 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 等

github推文地址-js实践