「这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战」
上一篇文章中,我们通过查看mockjs
源码以及查看XMLHttpRequest
接口说明,了解了如何通过模拟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。
- app.get:get请求
- app.post:post请求
- app.delete:delete请求
- app.put:put请求
Mock服务实现
通过上面我们知道了,webpackDevServer本身就是express,并且它暴露before
勾子函数且它运行在所有中间件之前,同时在before
函数中它暴露了app
express实例。因此我们遍可以通过在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内进行文件夹的监听,监听到变化,进行自动注册。
对应代码如下:不做过多描述,可自行动动试试。技术关键点是:
- 通过
chokidar
监听文件的变化 - 要懂得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服务。答案是:可以的。有空再写篇文章详解。