【Vue-Element-Admin 分析】- 01 Mock 是怎么实现的?

1,277 阅读2分钟

对于这个问题,第一反应是在 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,找到了下一条线索:

图片1

显然,关键在这个 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 的文档就明白了:

图片2

最后补上整体分析的脑图:

图片3

结语

通过梳理,我们已经明白了 vue-element-admin 是如何实现 mock-server 的,不过还存有一个问题:.env.prodcution.env.stage 中并没有配置远程服务器的地址,它是怎么在打包后找到服务器的呢?

不过这个问题暂不在本章讨论的范畴之内,后面的打包篇章会另行分析的。