Skip to content

Commit

Permalink
feat(mock): 增加mock数据功能
Browse files Browse the repository at this point in the history
  • Loading branch information
luoxue committed Dec 17, 2019
1 parent d6dec9a commit f0fed9e
Show file tree
Hide file tree
Showing 12 changed files with 655 additions and 254 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
feat | aaaaa | [73abf42](https://github.com/luoxue-victor/learn_webpack/commit/73abf42)
feat | asdada | [64090c7](https://github.com/luoxue-victor/learn_webpack/commit/64090c7)
global-style | 设置全局样式 | [ddfca57](https://github.com/luoxue-victor/learn_webpack/commit/ddfca57)
graphql | 增加graphql配置 | [d6dec9a](https://github.com/luoxue-victor/learn_webpack/commit/d6dec9a)
init | 项目初始化 | [d7835fb](https://github.com/luoxue-victor/learn_webpack/commit/d7835fb)
lint-staged | 添加 lint-staged,并修改课题10文档 | [a84dff0](https://github.com/luoxue-victor/learn_webpack/commit/a84dff0)
logo | 添加logo | [32ff923](https://github.com/luoxue-victor/learn_webpack/commit/32ff923)
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
- [课时 19:添加 webpack 配置检查命令](./docs/课时-19.md)
- [课时 20:添加 prefetch + preload](./docs/课时-20.md)
- [课时 21:增加 GraphQL Server](./docs/课时-21.md)
- [课时 21:开启 mock](./docs/课时-22.md)

</details>

Expand Down Expand Up @@ -134,6 +135,7 @@ webpack-box server:gql # graphql-server
- [dashboard 增加仪表盘配置](./config/dashboard.js)
- [eslint-loader 配置](./config/eslintLoader.js)
- [提取 manifest](./config/manifest.js)
- [mock](./config/mock.js)
- [optimization 优化配置](./config/optimization.js)
- [样式表配置](./config/style.js)
- [stylelint 配置](./config/styleLintPlugin.js)
Expand Down Expand Up @@ -182,6 +184,7 @@ module.exports = function (config) {
output: 'dist',
publicPath: '/common/',
port: 8888,
mock: true,
env: {
MY_ENV: 'my-env'
},
Expand Down
1 change: 1 addition & 0 deletions box.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module.exports = function (config) {
output: 'dist',
publicPath: '/common/',
port: 8888,
mock: true,
env: {
MY_ENV: 'my-env'
},
Expand Down
17 changes: 15 additions & 2 deletions build/dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = function (options) {
const WebpackDevServer = require('webpack-dev-server')
const port = options.port || 8080
const publicPath = options.publicPath || '/'
// const createMockMiddleware = require('../lib/createMockMiddleware')

config.devServer
.quiet(true)
Expand All @@ -13,6 +14,15 @@ module.exports = function (options) {
.disableHostCheck(true)
.publicPath(publicPath)
.clientLogLevel('none')
// .before(app => {
// // try {
// // app.use(createMockMiddleware())
// // } catch (error) {
// // console.error(error)
// // process.exit()
// // }
// // require('../server/start')
// })

if (typeof options.chainWebpack === 'function') {
options.chainWebpack(config)
Expand All @@ -39,8 +49,11 @@ module.exports = function (options) {
new Promise(() => {
compiler.hooks.done.tap('dev', stats => {
const empty = ' '
const common = `App running at:
- Local: http://127.0.0.1:${port}${publicPath}\n`
const common = `
App running at:
- dev at: http://localhost:${port}${publicPath}
${options.mock ? ` - mock at: http://localhost:${port}/api/users/12` : ''}
`
console.log(chalk.cyan('\n' + empty + common))
})
})
Expand Down
11 changes: 11 additions & 0 deletions config/mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// [mock]
module.exports = ({
config,
options
}) => {
return () => {
if (!options.mock) return
const createMockMiddleware = require('../lib/createMockMiddleware')
config.devServer.before(app => app.use(createMockMiddleware()))
}
}
2 changes: 2 additions & 0 deletions docs/课时-21.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
```bash
webpack-box server:gql
```

文档后续在补充,还需要调整,目前可以使用脚手架命令尝试使用
62 changes: 62 additions & 0 deletions docs/课时-22.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
## 课时 21:开启 mock

box.config.js

```js
{
mock: true;
}
```

```js
└──── mock // mock目录
│── index.js
└── mock.js
```

### 使用

mock/index.js

```js
module.exports = {
// 支持值为 Object 和 Array
"GET /api/users": { users: [1, 2] }, // http://localhost:8888/api/users
// GET POST 可省略
"/api/users/1": { id: 2 }, // http://localhost:8888/api/users/1
"/api/users/2": (req, res) => {
// http://localhost:8888/api/users/2
res.json({
success: true
});
},
"/api/users/3"(req, res) {
// http://127.0.0.1:8888/api/users/3
res.json({
success: true
});
}
};
```

使用 mockjs

mock/mock.js

```js
const Mock = require("mockjs");

module.exports = {
// 支持值为 Object 和 Array
// http://localhost:8888/api/users/12
"/api/users/12": Mock.mock({
// 属性 list 的值是一个数组,其中含有 1 到 10 个元素
"list|1-30": [
{
// 属性 id 是一个自增数,起始值为 1,每次增 1
"id|+1": 1
}
]
})
};
```
194 changes: 194 additions & 0 deletions lib/createMockMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
const { existsSync } = require('fs')
const { join } = require('path')
const bodyParser = require('body-parser')
const glob = require('glob')
const assert = require('assert')
const chokidar = require('chokidar')
const pathToRegexp = require('path-to-regexp')

const VALID_METHODS = ['get', 'post', 'put', 'patch', 'delete']
const BODY_PARSED_METHODS = ['post', 'put', 'patch']

module.exports = function getMockMiddleware(/* api */) {
const api = {
debug: require('debug'),
service: {
cwd: process.cwd()
}
}
const { debug } = api
const { cwd } = api.service
const absMockPath = join(cwd, 'mock')
const absConfigPath = join(cwd, 'mock.config.js')

let mockData = getConfig()
watch()

function watch() {
if (process.env.WATCH_FILES === 'none') return
const watcher = chokidar.watch([absConfigPath, absMockPath], {
ignoreInitial: true
})
watcher.on('all', (event, file) => {
debug(`[${event}] ${file}, reload mock data`)
mockData = getConfig()
})
}

function getConfig() {
cleanRequireCache()
let ret = null
if (existsSync(absConfigPath)) {
debug(`load mock data from ${absConfigPath}`)
ret = require(absConfigPath)
} else {
const mockFiles = glob.sync('**/*.{js,ts}', {
cwd: absMockPath
})
debug(
`load mock data from ${absMockPath}, including files ${JSON.stringify(
mockFiles
)}`
)
ret = mockFiles.reduce((memo, mockFile) => {
memo = {
...memo,
...require(join(absMockPath, mockFile))
}
return memo
}, {})
}
return normalizeConfig(ret)
}

function parseKey(key) {
let method = 'get'
let path = key
if (key.indexOf(' ') > -1) {
const splited = key.split(' ')
method = splited[0].toLowerCase()
path = splited[1]
}
assert(
VALID_METHODS.includes(method),
`Invalid method ${method} for path ${path}, please check your mock files.`
)
return {
method,
path
}
}

function createHandler(method, path, handler) {
return function(req, res, next) {
if (BODY_PARSED_METHODS.includes(method)) {
bodyParser.json({ limit: '5mb', strict: false })(req, res, () => {
bodyParser.urlencoded({ limit: '5mb', extended: true })(
req,
res,
() => {
sendData()
}
)
})
} else {
sendData()
}

function sendData() {
if (typeof handler === 'function') {
handler(req, res, next)
} else {
res.json(handler)
}
}
}
}

function normalizeConfig(config) {
return Object.keys(config).reduce((memo, key) => {
const handler = config[key]
const type = typeof handler
assert(
type === 'function' || type === 'object',
`mock value of ${key} should be function or object, but got ${type}`
)
const { method, path } = parseKey(key)
const keys = []
const re = pathToRegexp(path, keys)
memo.push({
method,
path,
re,
keys,
handler: createHandler(method, path, handler)
})
return memo
}, [])
}

function cleanRequireCache() {
Object.keys(require.cache).forEach(file => {
if (file === absConfigPath || file.indexOf(absMockPath) > -1) {
delete require.cache[file]
}
})
}

function matchMock(req) {
const { path: exceptPath } = req
const exceptMethod = req.method.toLowerCase()
for (const mock of mockData) {
const { method, re, keys } = mock
if (method === exceptMethod) {
const match = re.exec(req.path)
if (match) {
const params = {}

for (let i = 1; i < match.length; i = i + 1) {
const key = keys[i - 1]
const prop = key.name
const val = decodeParam(match[i])

if (val !== undefined || !hasOwnProperty.call(params, prop)) {
params[prop] = val
}
}
req.params = params
return mock
}
}
}

function decodeParam(val) {
if (typeof val !== 'string' || val.length === 0) {
return val
}

try {
return decodeURIComponent(val)
} catch (err) {
if (err instanceof URIError) {
err.message = `Failed to decode param ' ${val} '`
err.status = err.statusCode = 400
}

throw err
}
}

return mockData.filter(({ method, re }) => {
return method === exceptMethod && re.test(exceptPath)
})[0]
}

return (req, res, next) => {
const match = matchMock(req)
if (match) {
debug(`mock matched: [${match.method}] ${match.path}`)
return match.handler(req, res, next)
} else {
return next()
}
}
}
16 changes: 16 additions & 0 deletions mock/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = {
// 支持值为 Object 和 Array
'GET /api/users': { users: [1, 2] }, // http://localhost:8888/api/users
// GET POST 可省略
'/api/users/1': { id: 2 }, // http://localhost:8888/api/users/1
'/api/users/2': (req, res) => { // http://localhost:8888/api/users/2
res.json({
success: true
})
},
'/api/users/3' (req, res) { // http://127.0.0.1:8888/api/users/3
res.json({
success: true
})
}
}
13 changes: 13 additions & 0 deletions mock/mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const Mock = require('mockjs')

module.exports = {
// 支持值为 Object 和 Array
// http://localhost:8888/api/users/12
'/api/users/12': Mock.mock({
// 属性 list 的值是一个数组,其中含有 1 到 10 个元素
'list|1-30': [{
// 属性 id 是一个自增数,起始值为 1,每次增 1
'id|+1': 1
}]
})
}
Loading

0 comments on commit f0fed9e

Please sign in to comment.