这次Chrome扩展我们会实现两个小功能:
- DPS骨架屏核心功能搬到Chrome上来,实现一键转换;
- YAPI接口文档转换为请求函数;
前置知识
脚手架准备
1 安装 Scss
安装插件
yarn add @umijs/plugin-sass --dev
另外在项目根目录typings.d.ts加上Scss模块说明
declare module '*.scss';
2 设置hash路由,publicPath
为了我们的页面打开之后可以直接访问,需要设置
history:{ type: 'hash' },
publicPath:'./',
3 设置Chrome扩展
这次写的是Chrome扩展,在开发调试的时候需要把js和css暴露在硬盘上;
设置:
devServer:{
writeToDisk:true,
}
package.json: 加上HTML=none不输出html文件
"build": "HTML=none umi build",
同时,在public目录新建index.html,这个index.html是在umi build产物copy的,这个html文件在构建的时候会放到dist目录下,去引用我们上面说的暴露在硬盘上的js和css文件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
<link rel="stylesheet" href="./umi.css" />
<script>
window.routerBase = "/";
</script>
<script>
//! umi version: 3.2.22
</script>
</head>
<body>
<div id="root"></div>
<script src="./umi.js"></script>
</body>
</html>
然后,我们在public新建manifest.json声明这是一个Chrome扩展。
{
"name": "FE chrome扩展",
"manifest_version": 2,
"version": "1.0.0",
"description": "这是我的第一个chrome扩展",
"icons": {
"128": "img/icon.png"
},
"permissions": ["activeTab"],
"content_scripts": [
],
"browser_action": {
"default_icon": "img/icon.png",
"default_popup": "index.html"
}
}
4 目录参考
.
├── README.md
├── dist // chrome扩展目录,安装也是在这个文件夹
│ ├── content_scripts
│ ├── img
│ ├── index.html
│ ├── manifest.json
│ ├── umi.css
│ └── umi.js
├── package.json
├── public // public目录的文件会原样copy到dist目录
│ ├── content_scripts
│ ├── img
│ ├── index.html
│ └── manifest.json
├── src // src目录生成的文件会放到dist/umi.js和dist/umi.css文件给index.html引用
│ ├── app.scss
│ ├── app.tsx
│ ├── components
│ ├── pages
│ └── utils
├── tsconfig.json
├── typings.d.ts
└── yarn.lock
DPS骨架屏扩展开发
dps骨架屏简单介绍
dps是基于 DOM 操作生成颜色块拼成骨架屏的方案,它通过单纯的 DOM 操作,遍历页面上的节点,根据制定的规则生成相应区域的颜色块,最终形成页面的骨架屏。
然后,通过Puppeteer 在无头浏览器运行evalDOM.js,生成骨架屏代码,注入到html页面中。没错,evalDOM.js是dps的核心。
由于Chromium在国内网络下载不方便,且体积较大,我们可以换种思路,借助Chrome扩展运行evalDOM.js,然后将生成的代码转换成css和tsx内容。
Chrome扩展开发
入口文件 manifest.json
{
// ...
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content_scripts/evalDOM.js", "content_scripts/dps.js"],
"run_at": "document_start"
}
],
// ...
}
这里我们需要借助content-scripts(Chrome插件中向页面注入脚本的一种形式) 和 popup(点击browser_action或者page_action图标时打开的一个小窗口网页) 这两种能力。
我们向网页注入了evalDOM.js和dps.js,dps.js用于接收popup窗口传递过来的事件去调用evalDOM()方法生成骨架屏代码并返回内容。
dps.js
chrome.runtime.onMessage.addListener(function (request) {
if (request.cmd == "dps") {
// window.evalDOM方法由evalDOM.js提供
window.evalDOM(request.value).then(skeletonHTML => {
document.body.innerHTML=skeletonHTML // 当前页面替换为骨架屏代码
chrome.runtime.sendMessage(skeletonHTML); // 将骨架屏代码内容发送给popup.html
}).catch(e => {
console.error(e)
})
}
});
popup窗口
popup窗口获取当前配置输入的内容,点击转换按钮将配置发送到dps.js
// 向content-script发送信息
$btn.onclick = ()=>{
const value = parse($config.value); // AST解析字符串,生成带function字符的json
sendMessageToContentScript( { cmd: "dps", value} );
}
// 监听来自content-script的消息
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
const content = transform(request)
document.querySelector("#style").value=content.style
document.querySelector("#html").value=content.html
});
全程代码都比较简单,原生几个js文件搞定~
如何向配置注入function函数
由于evalDOM接收参数有init()和includeElement(node, draw)两个函数,我们将配置写在textarea框里获取到的是一个字符串,如何解析带function的json字符串,JSON.parse()对格式有严格的要求,我们不想写{"fn":"function(){}"}这种代码,这时候,可以用AST大法解析。
- esprima:解析AST代码的库
- estraverse: 遍历更新AST
- escodegen: 得到转译后的代码
/** json字符串转换成对象 */
function parse(str){
const esprima = window.esprima // esprima库
const traverse = window.traverse // estraverse库
const escodegen = window.escodegen // escodegen 库
const ast = esprima.parseScript('var a='+str); // 解析成AST
const re = {};
traverse(ast, {
// 遍历
enter: function (node) {
if (node.type === "Property") {
if (node.value.type === "Literal") {
let key = node.key.name || node.key.value;
let value = node.value.value;
re[key] = value;
}
if (node.value.type === "FunctionExpression") {
let key = node.key.name || node.key.value;
let value = escodegen.generate(node.value); // 得到"function(){}"函数字符串
// value = eval(`(function(){return ${value}})()`); // 可以通过eval转成函数
re[key] = value;
}
this.skip();
}
}
});
return re
}
使用扩展
其他方案
在此推荐社区另外一个库react-content-loader,可以在线自定义生成SVG代码,可以尝试一下。
YAPI转请求函数
仔细观察YAPI接口网页地址,上面都是
http://*.com/project/${项目id}/interface/api/${接口id},拿到这个接口id,我们再去network找对应的请求接口数据即可。
入口文件 manifest.json
{
// ...
"content_scripts": [
{
"matches": ["http://*yapi*/**/*"],
"js": ["content_scripts/yapiToFuntion.js"],
"run_at": "document_start"
}
],
// ...
}
这里注入了content_scripts yapiToFuntion.js,做的事情很简单,接收到popup窗口事件后,发起请求得到数据,再返回给popup窗口处理
fetch('http://**/api/interface/get?id=' + id, {
referrerPolicy: 'strict-origin-when-cross-origin',
body: null,
method: 'GET',
mode: 'cors',
credentials: 'include',
}).then(async (res) => {
if (res.ok) {
chrome.runtime.sendMessage({
cmd: 'yapiToFuntion',
data: await res.json(),
});
}
});
popup窗口处理转换数据
我们实现了一个工具类,用于转换请求数据为想要的api函数jsbin地址,如果想改造生成函数,也可以在里面修改,只需要修改fetchWithJSON()和fetchWithForm()这两个函数即可。
效果如下:
对代码格式化
prettier提供了浏览器版本的api,可以很方便地对代码进行格式化,点击prettier文档地址
结尾
以上为开发扩展的记录,完~