Umi开发Chrome扩展记录

1,763 阅读4分钟

这次Chrome扩展我们会实现两个小功能:

  1. DPS骨架屏核心功能搬到Chrome上来,实现一键转换;
  2. 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.jsdps的核心

由于Chromium在国内网络下载不方便,且体积较大,我们可以换种思路,借助Chrome扩展运行evalDOM.js,然后将生成的代码转换成csstsx内容。

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.jsdps.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框里获取到的是一个字符串,如何解析带functionjson字符串,JSON.parse()对格式有严格的要求,我们不想写{"fn":"function(){}"}这种代码,这时候,可以用AST大法解析。

/** 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
}

使用扩展

Snipaste_2020-10-11_14-40-47.png

其他方案

在此推荐社区另外一个库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()这两个函数即可。

效果如下:

Snipaste_2020-10-11_15-12-42.png

对代码格式化

prettier提供了浏览器版本的api,可以很方便地对代码进行格式化,点击prettier文档地址

结尾

以上为开发扩展的记录,完~