开发 Chrome 插件 + Mockjs 自动生成服务器,解决前端手动 mock 的痛苦
1. 背景
最近笔者开发一个新项目,需求评审后准备开发,后端拆解了产品需求并提供了接口文档,然后笔者在开始时要自己在代码中
mock
数据或者借助构建工具提供的相关插件去mock
数据很不爽,因为还要自己去写mock
的返回值并copy
后端定义的返回值
,降低了开发效率,不就相当于减少了moyu 时间么
不用现有的开源
mock
平台是因为安全
,如果要进行私有化部署
会收费,所以综合考虑下笔者就花了几天时间开发了此款chrome 插件
2. 效果图
3. 插件功能流程
为了尽可能的让其他的前端小伙伴使用无负担,整体功能流程就是
打开
后端提供的接口文档地址右键
打开控制台选择
笔者开发的插件名称Kg
生成
服务器代码- 打开新标签页,测试后端提供的接口地址
4. chrome 插件开发-前端代码
网上有很多的
chrome 插件
开发教程,这里笔者就不一一赘述了,有兴趣的小伙伴可以自行搜索相关资料,这里只列举一下chrome 插件
所需的相关文件
4.1 manifest.json
{
"manifest_version": 3, // v3 版本
"name": "auto-mock", // 插件名称
"version": "1.0.0", // 插件版本
"description": "读取 confluence 中的接口,自动写入本地nodejs服务器,生成接口,供前端模拟", // 插件描述
"action": {
"default_icon": "icon.png", // chrome 插件浏览器右上角插件图标
"default_title": "解放双手" // 输入移入插件时的提示
},
"content_scripts": [
{
"matches": ["*://*/*"], // 匹配任意网站
"js": ["./content-script.js"], // 网站打开时运行的 js 文件
"css": ["./css/index.css"], // 依赖的 css 文件
"run_at": "document_end" // 网站打开后什么时候注入 js 文件,这里是 文档加载完成后注入
}
],
"devtools_page": "./devtool.html", // 控制台插件打开时的 html
"permissions": [
// chrome 插件所需要的相关权限
"tabs",
"notifications",
"activeTab",
"downloads",
"history",
"storage"
]
}
4.2 devtool.js
// 创建一个Panel
chrome.devtools.panels.create("Kg", "./icon.png", "./index.html");
4.3 index.html
笔者是用
vue3 + typescript + naive-ui
开发的,最后会被打包到一个单独的js 文件中
,在html 文件中引用该文件即可
4.4 爬取页面数据格式化成 json
- 监听
devtool页面
发来的消息
// 当首次进入此页面时,默认会向 content-scripts 文件发送一个消息,content-scripts 文件中会监听事件,代码如下
chrome.runtime.onMessage.addListener((action, sender, sendResponse) => {
try {
const response = handleMatchPageData(params); // 爬取页面数据
return sendResponse(response);
} catch (error) {
console.log(error, "error");
return sendResponse(error);
}
});
- 爬取页面数据
// 这里其实就是通过获取dom,然后用正则提取相关字段信息进行组装了,以下代码仅供参考,实际不同的接口文档爬取的规则是不一样的
const handleMatchPageData = (params) => {
const rules = {
defaultApiName: "",
defaultApiURI: "",
defaultApiMethod: "",
defaultApiQuery: {},
defaultApiBody: {},
defaultReponse: {},
// 匹配开始的dom
matchStartDoms() {
const list = document.querySelectorAll("h4");
return list ? Array.from(list) : [];
},
// 匹配 Api name
matchApiName(parentNodeHTML) {
const matched = parentNodeHTML.match(/\>\d+(?:、|\.)(.*?)\</);
return matched ? matched[1] : rules.defaultApiName;
},
// 匹配 Api uri
matchApiURI(parentNodeHTML) {
const matched = parentNodeHTML.match(/uri(?:\:|:)(?: )?(.*?)\</i);
return matched ? matched[1] : rules.defaultApiURI;
},
// 匹配 Api method
matchApiMethod(parentNodeHTML) {
const matched = parentNodeHTML.match(
/method(?:\s)?(?:\:|:)(?:\s+)?(.*?)\</i
);
return matched ? matched[1].toLowerCase() : rules.defaultApiMethod;
},
// 匹配get请求的query参数
matchApiQueryByGet(method, index) {
if (!method || method !== "get") return rules.defaultApiQuery;
return matchApiParams(index);
},
// 匹配post请求的body参数
matchApiBodyByPost(method, index) {
if (!method || method !== "post") return rules.defaultApiBody;
return matchApiParams(index);
},
// 匹配返回值,格式化json数据,因为后端写的json可能会有很多错,这里是把后端写的json格式化为尽可能正确的json
matchApiResponse(element) {
const table = element.querySelector(".code table");
if (!table) return rules.defaultReponse;
const text = table.innerText;
const response = JSON.parse(
text
.replace(/\/\/.*?\n/g, "") // 干掉注释
.replace(/\'/g, '"') // 替换 ' 为 "
.replace(/\'/g, '"') // 替换 ' 为 "
.replace(/\“/g, '"') // 替换 “ 为 "
.replace(/\”/g, '"') // 替换 ” 为 "
.replace(/,/g, ",") // 替换 ,为 ,
.replace(/:/g, ":") // 替换 :为 :
.replace(/\n/g, "") // 干掉换行符
.replace(/\s/g, "") // 干掉空格
.replace(/\,(?=\})/g, "") // 干掉不符合json规范的逗号
.replace(/\,(?=\])/g, "") // 干掉不符合json规范的逗号
);
return response;
},
};
const startDoms = matchStartDoms();
return startDoms.reduce((p, startDom, index) => {
let element = startDom;
let HTML = "";
while (element.outerHTML.match(/div class="code/) === null) {
HTML += element.outerHTML;
element = element.nextElementSibling;
}
HTML += element.outerHTML;
const name = matchApiName(HTML);
const uri = matchApiURI(HTML);
const method = matchApiMethod(HTML);
const response = matchApiResponse(element);
p.push({
name,
uri,
method,
response,
});
return p;
}, []);
};
-
拿到数据后展示成表格即可
-
生成服务器代码
// 前端将爬取到的数据通过请求全部统一发送给后端
function request(params, url = "/gen-api") {
// 这里的 params 就是爬取到的数据进行了整合
const baseUrl = "http://localhost:3004"; // 后端服务器接口地址
const requestUrl = `${baseUrl}${url}`;
const instance = new Request(requestUrl, {
method: "post",
headers: {
"Content-Type": "application/json;charset=utf-8",
},
body: JSON.stringify(params),
});
}
5. chrome 插件开发-后端代码
后端是通过
express框架
搭建的
- 接口中生成路由模板
function genExpressRouter({ method, uri, response }) {
method = method.toLowerCase();
return `
app.${method.replace(/ /g, "").replace(/\s+/, "")}('${uri
.replace(/ /g, "")
.replace(/\s+/, "")}',async (req,res) =>{
try{
return res.send(Mock.mock(${JSON.stringify(response)}))
}catch(e){
console.log(e,'e');
return res.send({
code:201,
codeMsg:e,
success:false
})
}
})
`;
}
app.post("/gen-api", (req, res) => {
try {
// mock = 前端匹配到的数据
const { mock } = req.body;
...
const expressRoutes = mock.reduce((p, info) => {
p += genExpressRouter(info);
return p;
}, "");
...
} catch (error) {
return res.send({
code: 201,
message: "生成代码失败",
});
}
});
- 将生成后的模板写入到单独的
js 文件
fs.writeFileSync("auto-gen-server.js", template, "utf-8");
- 使用
pm2
启动服务器
// package.json 文件
{
"name": "auto-mock-server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"del:mock-server": "pm2 del mock-server",
"start:mock-server": "pm2 start auto-gen-server.js --name mock-server"
},
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.1",
"mockjs": "^1.1.0"
}
}
const { execSync } = require("child_process");
app.post("/gen-api", (req, res) => {
try {
...
try {
execSync("npm run del:mock-server");
execSync("npm run start:mock-server");
} catch (error) {
execSync("npm run start:mock-server");
}
} catch (error) {
return res.send({
code: 201,
message: "生成代码失败",
});
}
});
6. 实现思路总结
- 控制面板打开后,
devtool
页面发送一个消息给content-scripts
文件 content-scripts
文件中接受到消息,利用正则匹配页面中的内容content-scripts
中匹配页面内容后,在发送消息给devtool
页面devtool
文件中接受到消息,将数据展示成表格生成服务器代码
功能会发送一个请求给express 服务器
express 服务器
接受到请求,会生成对应的模板代码- 将
模板代码
写入到一个单独的文件中 - 利用
execSync + pm2
启动这个单独的文件 - 完成