通过webpackDevServer实现mock服务的那些事

1,144 阅读4分钟

「这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战

上一篇文章中,我们通过查看mockjs源码以及查看XMLHttpRequest接口说明,了解了如何通过模拟XMLHttpRequest去实现一个mock服务,详细可以前往如下文章了解:

通过模拟XMLHttpRequest实现Mock服务

这篇讲下如何通过借助webpack的能力去注册mock服务。

了解WebpackDevServer

webpack 通过webpack-dev-server提供了一个简单的web服务器,并且能够实时重新加载。它通过配置contentBase参数告知webpack-dev-server启动的服务以哪个目录文件作为可访问文件,也就是通常所说的服务root路径。同时devServer还暴露了一个before勾子函数,它的作用是提供在服务内部优先于所有其他中间件之前执行的自定义中间件的能力,因此便可以在before函数内定义mock处理函数,匹配路由,拦截请求,返回mock数据。

那么webpack启动的devServer是什么呢?通过查看源码我们知道,它就是一个express实例。

Express框架

Express是一个基于 Node.js 平台,快速、开放、极简的 Web 开发框架。

这里我们先看下Express的基本用法,这样方便我们写mock服务,它是什么。

1. 安装

通过npm init初始化一个项目,然后执行命名:

npm install express --save

2. Hello World应用

var express = require('express')
// 1. 创建express实例
var app = express()

// 3. 创建个get服务
app.get('/', function (req, res) {
  res.send('hello world')
})

// 2. 监听3000端口
app.listen(3000)

当你发起个路径为/get请求,此时就会返回hello world。就是这么简单。

3. 开发个log中间件

中间件函数是可以访问请求对象 ( req)、响应对象( res) 和next应用程序请求-响应循环中的函数的函数。该next函数是 Express 路由器中的一个函数,当被调用时,它会在当前中间件之后执行中间件。

var express = require('express')
var app = express()

// log中间
var myLogger = function (req, res, next) {
  console.log('LOGGED')
  next()
}

// 注册中间件
app.use(myLogger)

app.get('/', function (req, res) {
  res.send('Hello World!')
})

app.listen(3000)

每次应用程序收到请求时,它都会向终端打印消息“LOGGED”。

4. express的api

这里只列举我们需要用到的方法几个方法api。

  1. app.get:get请求
  2. app.post:post请求
  3. app.delete:delete请求
  4. app.put:put请求

Mock服务实现

通过上面我们知道了,webpackDevServer本身就是express,并且它暴露before勾子函数且它运行在所有中间件之前,同时在before函数中它暴露了appexpress实例。因此我们遍可以通过在app上进行注册服务。

1. 基本实现

devServer: {
  before: (app)=>{
    app.get('/app/test',(req,res)=>{
       res.send('Hello World!')
    })
  }
}

至此便是注册了一个路由为/app/test返回Hello World的mock服务。那怎么访问呢?这个是挂载在webpackDevServer上,所以直接通过启动的devServer的ip+端口即可访问。项目可直接axios.get('/app/test')访问。

2. 进阶:动态注册

实际项目中,并不可能每次在before函数中写,写了再重新启动。一方面,会导致webpackDevServer代码臃肿,不好维护;另一方面,更改要重新启动webpack,效率低下。因此强烈建议创建个文件夹专门写mock服务,并且在devServer内进行文件夹的监听,监听到变化,进行自动注册。

对应代码如下:不做过多描述,可自行动动试试。技术关键点是:

  1. 通过chokidar监听文件的变化
  2. 要懂得commonJs的实现机制。 附:commonJs实现传送门:你真的掌握commonJs了吗?来看看它底层实现
// mock-server.js

const chokidar = require('chokidar')
const bodyParser = require('body-parser')
const chalk = require('chalk')
const path = require('path')

const mockDir = path.join(process.cwd(), 'mock')

// 注册路由
function registerRoutes(app) {
  let mockLastIndex
  const { default: mocks } = require('./index.js')
  for (const mock of mocks) {
    // 注册mock
    app[mock.type](mock.url, mock.response)
    mockLastIndex = app._router.stack.length
  }
  const mockRoutesLength = Object.keys(mocks).length
  return {
    mockRoutesLength: mockRoutesLength,
    mockStartIndex: mockLastIndex - mockRoutesLength
  }
}

function unregisterRoutes() {
 /*
  * require.cache
  * 多处引用同一个模块,最终只会产生一次模块执行和一次导出。所以,会在运行时(runtime)中会保存一份缓存。删除此缓存,会产生新的模块执行和新的导出。
  */ 
  Object.keys(require.cache).forEach(i => {
    if (i.includes(mockDir)) {
      delete require.cache[require.resolve(i)]
    }
  })
}

module.exports = app => {
  // es6 polyfill
  require('@babel/register')

  // 解析 app.body
  // https://expressjs.com/en/4x/api.html#req.body
  app.use(bodyParser.json())
  app.use(bodyParser.urlencoded({
    extended: true
  }))

  const mockRoutes = registerRoutes(app)
  var mockRoutesLength = mockRoutes.mockRoutesLength
  var mockStartIndex = mockRoutes.mockStartIndex

  // 监听文件,热加载mock-server
  chokidar.watch(mockDir, {
    ignored: /mock-server/,
    ignoreInitial: true
  }).on('all', (event, path) => {
    if (event === 'change' || event === 'add') {
      try {
        // 删除模拟路由堆栈
        app._router.stack.splice(mockStartIndex, mockRoutesLength)
        // 删除路由缓存
        unregisterRoutes()
        // 注册路由
        const mockRoutes = registerRoutes(app)
        mockRoutesLength = mockRoutes.mockRoutesLength
        mockStartIndex = mockRoutes.mockStartIndex

        console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed  ${path}`))
      } catch (error) {
        console.log(chalk.redBright(error))
      }
    }
  })
}

总结

mock服务注册不难,消耗时间的是mock数据的构造,那我们有办法在这块进行提效吗?进行自动生成mock服务。答案是:可以的。有空再写篇文章详解。