Open dkvirus opened 5 years ago
src/dev.js
$ roadhog dev
启动项目时,如果项目根目录下有.roadhog.mock.js
文件,就会启动 Mock 功能。
入口文件 bin/roadhog.js
=> src/roadhog.js
=> src/scripts/dev.js
,开启一个子进程 => src/scripts/realDev.js
=> src/dev.js
文件。运行 $ roadhog dev
核心处理代码就是这个文件。
src/dev.js
import dev from 'af-webpack/dev';
import { applyMock } from './utils/mock';
export default function runDev(opts = {}) {
const { cwd = process.cwd(), entry } = opts;
// .......
dev({
webpackConfig,
proxy: config.proxy || {},
beforeServer(devServer) {
try {
applyMock(devServer);
} catch (e) {
console.log(e);
}
},
afterServer(devServer) {
returnedWatchConfig(devServer);
},
openBrowser: true,
});
}
对外暴露一个接口 runDev,接口内部处理一些参数,最后调用 dev 方法,dev 是 af-webpack 底层库对于 webpack 的封装,其中 af 指的是 ali-financial(阿里金融)。dev 的 beforeServer 参数字面意思是启动服务前的一个钩子函数,可以看到就是这里加载的 Mock 相关功能。
src/utils/mock.js
这个文件由7个方法组成,一个个看。
applyMock()
入口方法。但不处理核心逻辑,核心逻辑交给 realApplyMock()
处理,这一点和 scripts 目录下的 dev.js 和 realDev.js 颇为相似,看来是大佬的个人编码习惯~~
export function applyMock(devServer) {
try {
realApplyMock(devServer);
error = null;
} catch (e) {
error = e;
outputError();
// 加载 Mock 失败,可能原因配置文件或者 mock 目录写的有问题,监听这两个文件的变化,
// 只要有修改就重启 Mock 功能。chokidar.watch 功能和 fs.watch 类似,监听文件变化,但更稳定
// ....
const watcher = chokidar.watch([configFile, mockDir], {});
watcher.on('change', path => {
applyMock(devServer);
});
}
}
在看 realApplyMock()
方法之前,先看几个工具类方法。
getConfig()
:加载 .roadhog.mock.js 配置文件,获取 mock 目录下 mock 数据,返回一个对象;parseKey()
: 解析键值,获取请求方法(e.g. GET)和请求地址(e.g. /api/v1/users);outputError()
:解析过程出现错误,打印这部分内容提示用户;createMockHandler()
:Roadhog-mock 中描述 mock 即可以写成函数形式,也可以直接写一个对象字面量,如下图,createMockHandler()
方法实现该功能;上图为 Roadhog-Mock 写法,其中键值除了对象自面量就是函数,压根没有字符串的示例。代理中只有键值为字符串,才会调用 createProxy 方法,对于这个方法不觉明历??而且 Mock 为什么会涉及到代理的东东??奇怪。
补充:在 Roadhog PR 里找到答案了,roadhog#795 提出加代理需求,个人感觉一团糟就是个挖坑的过程,没想到 PR 还被通过了。mock 功能很清晰,就是本地造假数据,脱离真实服务器也可以启动项目进行项目演示,代理完全可以在 .webpackrc.js
中添加,功能分离,更优雅。
配置:['/api/v1/(.*)']: 'localhost:8008'
,键值可以是字符串,Roadhog 官方文档未更新相关特性。
createProxy()
:创建代理。http-proxy-middleware#context-matching 最后一小点 custom matching
介绍配置方法。/**
* 引入 .roadhog.mock.js,该文件导出对象
* {
* [`GET /users`] (req, res) { res.json({ }) },
* [`GET /roles`] (req, res) { res.json({ }) },
* }
*/
function getConfig() {
// require.cache 第一次看到,了解一波:https://cnodejs.org/topic/5993c0d54e3c4e5a7021b112
}
/**
* 解析键值,返回请求方法和请求地址
* For Example:[`GET /api/v1/users`] (req, res) { res.json({ }) }
* key 就是键值,上述列子中:key = `GET /api/v1/users`
* return { method: 'GET', path: '/api/v1/users' }
*/
function parseKey(key) {
}
/**
* 启动 Mock 功能报错处理方法
*/
export function outputError() {
console.log('xxxxx');
}
/**
* 创建 Mock 处理函数
* https://github.com/sorrycc/roadhog#mock => roadhog 文档有介绍可以返回一个方法,
* 也可以直接返回一个对象字面量,createMockHandler() 实现这一功能。
*/
function createMockHandler(method, path, value) {
if (typeof value === 'function') {
value(...args);
} else {
res.json(value);
}
}
/**
* 创建代理
*/
function createProxy(method, pathPattern, target) {
return proxy(filter, { target: realTarget, pathRewrite });
}
realApplyMock()
处理核心逻辑。
function realApplyMock(devServer) {
// Step1
const config = getConfig(); // 获取 mock 造的假数据对象
const { app } = devServer;
// Step2:键值是字符串的放到 proxyRules,其它类型的键值放到 mockRules 中
const proxyRules = [], mockRules = [];
Object.keys(config).forEach(key => {
const keyParsed = parseKey(key); // 解析获得请求方法和请求地址
if (typeof config[key] === 'string') {
proxyRules.push({ path, method: keyParsed.method, target: config[key] });
} else {
mockRules.push({ path: keyParsed.path, method: keyParsed.method, target: config[key] });
}
});
// Step3:question?what is app apis.
proxyRules.forEach(proxy => {
app.use(proxy.path, createProxy(proxy.method, proxy.path, proxy.target));
});
/**
* body-parser must be placed after http-proxy-middleware
* https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/modify-post.md
*/
devServer.use(bodyParser.json({ limit: '5mb', strict: false }));
devServer.use(bodyParser.urlencoded({
extended: true,
limit: '5mb',
}));
mockRules.forEach(mock => {
app[mock.method](
mock.path,
createMockHandler(mock.method, mock.path, mock.target),
);
});
// Step4:调整 stack,把 historyApiFallback 放到最后,question??
let lastIndex = null;
app._router.stack.forEach((item, index) => {
// .......
});
// Step5:监听 mock 目录和 .roadhog.mock.js 文件变化,有变化,自动重启
const watcher = chokidar.watch([configFile, mockDir], {});
watcher.on('change', path => {
applyMock(devServer);
});
}
Mock 核心代码读下来还算 ok,只是 realApplyMock 方法中 devServer 这东东是 af-webpack 里封装的对象,api 还不熟悉。Step3 中注册 mock 数据,涉及到 devServer 参数,这是 af-webpack 封装的。Step4 historyApiFallback 是 webpack 中的一个配置。