对于这个问题,第一反应是在 vue.config.js 中配置 devServer,通过 proxy 代理让流量流向 mockServer。
于是第一时间查看了 vue.config.js,devServer 配置如下:
devServer: {
port: port,
open: true,
overlay: {
warnings: false,
errors: true
},
before: require('./mock/mock-server.js')
}
出乎意料的是,并没有 proxy。
线索到这儿断了,那么先去看看 .env 是如何配置的吧:
# .env.stage
VUE_APP_BASE_API = '/stage-api'
# .env.development
VUE_APP_BASE_API = '/dev-api'
# .env.production
VUE_APP_BASE_API = '/prod-api'
可见,这里连打包之后的地址都没有指向服务器,那么这个项目打包之后,是怎么找到服务器的呢?
怀着这个问题,在全局搜索 VUE_APP_BASE_API,找到了下一条线索:
显然,关键在这个 mock-server.js 中,这个时候,再仔细看看 vue.config.js,发现这里其实用到了 mock-server.js:
devServer: {
/* ... */
before: require('./mock/mock-server.js')
}
那么我们再回到 mock-server.js,这里我们关注点来到 module.exports 的方法:
module.exports = app => {
app.use(bodyParser.json())
app.use(
bodyParser.urlencoded({
extended: true
})
)
const mockRoutes = registerRoutes(app)
var mockRoutesLength = mockRoutes.mockRoutesLength
var mockStartIndex = mockRoutes.mockStartIndex
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))
}
}
})
}
大致分析一下,不难看出,它总共做了三件事:
- 通过
bodyParser解析请求 - 通过
registerRoutes注册了mockRoutes - 通过
chokidar监听 mock 目录,从而实现 mock-server 的热更新
那么核心自然是 registerRoutes,我们看看它又是怎么做的:
function registerRoutes(app) {
let mockLastIndex
const { mocks } = require('./index.js')
const mocksForServer = mocks.map(route => {
return responseFake(route.url, route.type, route.response)
})
for (const mock of mocksForServer) {
app[mock.type](mock.url, mock.response)
mockLastIndex = app._router.stack.length
}
const mockRoutesLength = Object.keys(mocksForServer).length
return {
mockRoutesLength: mockRoutesLength,
mockStartIndex: mockLastIndex - mockRoutesLength
}
}
可以看到,这里逻辑如下:
- 首先从
mock/index.js中引入了mocks - 然后遍历
mocks,依次通过responseFake获取返回 - 随后遍历
mockForServer,通过app[mock.type](mock.url, mock.response)进行注册
我们先去看看 mocks 长什么样:
/* index.js */
const user = require('./user')
const role = require('./role')
const article = require('./article')
const search = require('./remote-search')
const mocks = [...user, ...role, ...article, ...search]
/* user.js */
const tokens = {
admin: {
token: 'admin-token'
},
editor: {
token: 'editor-token'
}
}
const users = {
'admin-token': {
roles: ['admin'],
introduction: 'I am a super administrator',
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
name: 'Super Admin'
},
'editor-token': {
roles: ['editor'],
introduction: 'I am an editor',
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
name: 'Normal Editor'
}
}
module.exports = [
// user login
{
url: '/vue-element-admin/user/login',
type: 'post',
response: config => {
const { username } = config.body
const token = tokens[username]
// mock error
if (!token) {
return {
code: 60204,
message: 'Account and password are incorrect.'
}
}
return {
code: 20000,
data: token
}
}
},
// get user info
{
url: '/vue-element-admin/user/info\.*',
type: 'get',
response: config => {
const { token } = config.query
const info = users[token]
// mock error
if (!info) {
return {
code: 50008,
message: 'Login failed, unable to get user details.'
}
}
return {
code: 20000,
data: info
}
}
},
// user logout
{
url: '/vue-element-admin/user/logout',
type: 'post',
response: _ => {
return {
code: 20000,
data: 'success'
}
}
}
]
于是我们得到 mocks 的大致结构:
mocks = [
{
url: string,
type: string,
response: function | object
}
]
再次回到 mock-server 中,我们看看 responseFake 又做了什么处理:
const responseFake = (url, type, respond) => {
return {
url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
type: type || 'get',
response(req, res) {
console.log('request invoke:' + req.path)
res.json(
Mock.mock(respond instanceof Function ? respond(req, res) : respond)
)
}
}
}
显然,这个函数是对 mock 接口的处理,最后我们回到注册:
for (const mock of mocksForServer) {
app[mock.type](mock.url, mock.response)
mockLastIndex = app._router.stack.length
}
这是怎么进行注册的呢?
看看 webpack 的文档就明白了:
最后补上整体分析的脑图:
结语
通过梳理,我们已经明白了 vue-element-admin 是如何实现 mock-server 的,不过还存有一个问题:.env.prodcution 和 .env.stage 中并没有配置远程服务器的地址,它是怎么在打包后找到服务器的呢?
不过这个问题暂不在本章讨论的范畴之内,后面的打包篇章会另行分析的。