手写一个vite-mock-plugin插件(篇二)

前言

这里先给demo地址,可以直接看源码:demo地址

编写思路

  • 一开始干嘛?初始化。mock插件初始化什么,入口和能支持mock能力的devServer
  • 初始化完了,发现是不是有post,get等方法的路由请求,需不要给它分类?需要
  • 那怎么分类?考完重构一个对象的能力,是不是so easy.
  • 分为类是不是需要判断是否有效?有效就返回mock数据,无效执行下一个中间件
  • 前期这样想完全没有问题。

来一条华丽的分割线


后期发现问题:

  • 发现中间件的next方法和res.send方法同时执行或导致程序中断

解决这个问题我们需要判断路由是否有效,且不能在无效的时候去send和mock(假装自己知道就好了)

  • send函数在请求的时候需要给相应的信息,所以需要二次封装

初始化

我们从vite初始篇一开始了解vite插件的钩子,我们需要开启一个服务去支持mock能力,需要用到devServer,很明显我们需要在configureServer这个钩子里面写,且需要mock数据的路径

所以我们初始化的插件函数需要做两件事情:

  • 需要options参数入口,且为空的时候要给默认的入口
  • 在return的对象要用到configureServerserver.middlewares

server.middlewares相当于expressapp

import path from 'path';

export default function (options = {}) {
  // 获取mock文件入口,默认index
  options.entry = options.entry || './mock/index.js';

  // 转换为绝对路径
  if (!path.isAbsolute(options.entry)) {
    options.entry = path.resolve(process.cwd(), options.entry);
  }

  return {
    configureServer: function ({ middlewares: app }) {
        // 定义中间件:路由匹配
      const middleware = (req, res, next) => {

      };
      app.use(middleware);
    }
  };
}

再给我们的mock也初始下,下面看到我们是用的ES Moudle抛出方式,因为vite是不支持commonJS模块。所以我们需要用到export default,不能用到module.exports,本人亲测自己写一个支持require的插件也作用不到mock这个插件。(一个伤心的经历)

// /mock/index.js
export default [
  {
    url: '/api/users',
    type: 'get',
    response: function (req, res) {
      return res.send([
        {
          username: 'tom',
          age: 18,
        },
        {
          username: 'Tom',
          age: 19,
        },
      ]);
    },
  },
];

创建路由表

我们需要的路由表是这种结构,一个方法对应多个路由,所以我们需要创建路由表。

{
    "get": [
        {
            "url": "/api/1",
            "type": "get",
            "response": function 
        },
        {
            "url": "/api/2",
            "type": "get",
            "response": function 
        },
    ],
    "post": [
        {
            "url": "/api/1",
            "type": "get",
            "response": function 
        },
        {
            "url": "/api/2",
            "type": "get",
            "response": function 
        },
    ]
}

首先获取路由,我们不能用require,所以用import. 但import是异步的,那么我们需要加上async/await等待加载完模块,用解构的方式获取mockObj

...
return {
    configureServer: function ({ middlewares: app }) {
        // 定义路由表
        const mockObj = { ...(await import(options.entry)) }.default;
        // 创建路由表
        createRoute(mockObj);

        ....
         // 定义中间件:路由匹配
        const middleware = (req, res, next) => {

        };
        app.use(middleware)
    }
};
...

那么我们现在开始写createRoute函数,多尝试,也就一个简单的变量处理,下列写成函数如下:

function createRoute(mockConfList) {
  mockConfList.forEach((mockConf) => {
    let method = mockConf.type || 'get';
    let path = mockConf.url;
    let handler = mockConf.response;
    // 路由对象
    let route = { path, method: method.toLowerCase(), handler };
    // 如果没有改请求方法就初始化为空数组
    if (!mockRouteMap[method]) {
      mockRouteMap[method] = [];
    }
    console.log('create mock api: ', route.method, route.path);
    // 存入映射对象中
    mockRouteMap[method].push(route);
  });
}

创建完路由表,需要中间件里面的逻辑,也就是破路由和send数据到路由上面了。

执行匹配路由和自定义send

我们接下的逻辑可以这样写,先获取路由匹配的结果,如果匹配成功就send

...
return {
    configureServer: async function ({ middlewares: app }) {
      // 定义路由表
      const mockObj = { ...(await import(options.entry)) }.default;
      // 创建路由表
      createRoute(mockObj);

      // 定义中间件:路由匹配
      const middleware = (req, res, next) => {
        // 1. 执行匹配过程
        let route = matchRoute(req);

        // 2. 存在匹配,则这个是一个mock请求
        if (route) {
          console.log('mock request', route.method, route.path);
          // 自定义send
          res.send = send;
          // 执行路由的`response`回调函数,可以把代码往前翻
          route.handler(req, res);
        } else {
          next();
        }
      };
      app.use(middleware);
    },
};

...

匹配路由

根据我们浏览器请求的路由去匹配路由信息

function matchRoute(req) {
  let url = req.url;
  let method = req.method.toLowerCase();
  let routeList = mockRouteMap[method];

  return routeList && routeList.find((item) => item.path === url);
}

我们请求一个试下:

成功匹配:

image.png

自定义send

这里自定义send我们可以添加请求头字段,能够知道请求的东西的类型,长度等信息

function send(body) {
  let chunk = JSON.stringify(body);
  // Content-Length
  if (chunk) {
    chunk = Buffer.from(chunk, 'utf-8');
    this.setHeader('Content-Length', chunk.length);
  }
  // content-type
  this.setHeader('Content-Type', 'application/json');
  // status
  this.statusCode = 200;
  // response
  this.end(chunk, 'utf8');
}

总结

vite插件的掌握还是需要写些插件,看看别人的源码,了解为什么这么写,自己之后可以这样写。根据自己需求出去,应用到实践中去。

猜你喜欢

转载自juejin.im/post/7125745067758878756