10

San DevTools 技术解析(下)

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzUxMzk2ODI1NQ%3D%3D&%3Bmid=2247484206&%3Bidx=1&%3Bsn=e17b9d66e20264871c099fdfa7f73f1e
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

我们已经连续分享了《San DevTools 技术解析》上篇与中篇,讲了其中四大模块: BackendFrontendMessage ChannelDebugging Protocol ,基本完成了 San DevTools 整体架构与核心技术讲解,除这些技术外,我们还有哪些有意思的技术?今天来讲下: DevTools 扩展开发 和项目中其他比较有价值的技术点。

DevTools 扩展开发

简介

概念:我们正在说的应该叫Chrome扩展( Chrome Extension ),真正意义上的Chrome插件是浏览器底层的功能扩展,需要在了解浏览器底层技术的基础上利用 C++ 去开发。鉴于Chrome插件的叫法已经习惯,本文所说的插件也是Chrome扩展。

Chrome插件是一个用Web技术开发、用来增强浏览器功能的软件,它其实就是一个由HTML、CSS、JS、图片等资源组成的一个.crx后缀的压缩包。

DevTools 扩展

什么是DevTools扩展?它是为 Chrome DevTools 添加新功能的插件,功能上可新增加Panel UI面板和侧边栏,与被检查的页面交互,获取类似网络请求、Dom等信息。与其他扩展类似:有Backgroud、Content Script和其他项。DevTools 扩展都有一个 DevTools 页面 ,可以访问Chrome提供的 DevTools Api:

  • devtools.inspectedWindow:获取被审查窗口的有关信息

  • devtools.network:获取有关网络请求的信息

  • devtools.panels:面板相关

大家可以对比一下官方提供的DevTools扩展架构图与中篇中提供的图,结合两者,加深一下理解。

QNfAFv.png!mobile

q263Mz6.png!mobile

DevTools 扩展实例

实现扩展可通过简单的三步实现:

1.Manifest.json:指向HTML文件

   {
"devtools_page": "devtools.html"
}

2.HTML只引入JS文件

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>DevTools</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
</head>
<body>
<script src="/js/devtools.js"></script>
</body>
</html>

3.调用 API 创建自定义面板,同一个插件可以创建多个自定义面板

chrome.devtools.panels.create(
'San',
'/icons/logo128.png',
'panel.html'
);

关键技术:

  • 至少提供两个HTML,分别为devtools和panel

  • 入口:manifest 的 devtools_page

  • 核心API :chrome.devtools.panels.create

DevTools 扩展调试方式

因扩展需要多个页面与JS,调试时各模块方式也不同,这里做个汇总:

  1. Content Script 调试:F12或右键->检查,包括Inject Script

  2. Devtools Panel 调试:panel 面板里右键 -> 检查

    MRZVbyZ.png!mobile

  3. Backgroud 调试:chrome://extensions/ -> 背景页

    jI3amay.png!mobile

总结:

  1. Backgroud 是常驻的,与浏览器生命周期相同

  2. Content Script 是页面级的,与页面生命周期相同

  3. DevTools 与调试工具生命周期相同

其他技术

Monorepo 项目管理

Monorepo 是管理项目代码的一种方式,指在一个项目仓库(repo)中管理多个模块/包(packages),不同于常见的每个模块建立一个repo。适合于大型前端项目的代码管理。 San DevTools 采用这种代码组织方式,通过 yarn workspace 管理依赖和运行项目。比传统 git-submodule 的多个repo管理上方便很多。

san-devtools
├── packages
│ ├─ shared
│ │ ├─ src
│ │ └─ package.json
│ └─ backend
│ ├─ src
│ └─ package.json
├── tsconfig.json # 配置文件,对整个项目生效
├── .eslintrc # 配置文件,对整个项目生效
├── node_modules # 整个项目只有一个外层 node_modules
└── package.json # 包含整个项目所有依赖


// package.json
{
...
"scripts": { # workspace 彼此独立运行
"start": "yarn workspace san-devtools start",
"build:standalone": "yarn workspace san-devtools build",
"start:extensions": "yarn workspace extensions start",
"build:extensions": "yarn workspace extensions build"
},
"workspaces": [ # Yarn 命令使用的工作空间
"packages/*"
],
"files": [
"packages" # 配置项目包含的文件名数组
],
...
}

模块解析

代码里经常可以看到如下的引入:

import Bridge from '@shared/Bridge';
import {install, DevToolsHook} from '@backend/hook';

@shared@backend 的路径是如何解析的呢,只需要配置两个文件:

// tsconfig.json
{
...
"paths": {
"@backend/*": ["packages/backend/src/*"],
"@frontend/*": ["packages/frontend/src/*"],
"@shared/*": ["packages/shared/src/*"]
}
...
}
// packages/build-tools/createConfig.js
const baseConfig = {
...
resolve: {
alias: {
'@backend': resolve('backend/src/'),
'@shared': resolve('shared/src/'),
'@frontend': resolve('frontend/src/')
}
}
...
};

简易中间件服务器

San DevTools 里我们简易实现一个中间件服务器。

Zj6Vb22.png!mobile

class Server {
constructor(options) {
this._middlewares = [];


// 添加中间件
this.use(...);


this.createServer();
}


createServer() {
this._server = http.createServer((req, res) => {
this._requestHandler(req, res, err => {
...
});
});
}
_requestHandler(req, res, errorHandler) {
let idx = 0;
const middlewares = this._middlewares;
const firstHandler = middlewares[idx];


run(firstHandler);


function next() {
idx++;
if (idx < middlewares.length) {
run(middlewares[idx]);
}
}


function run(fn) {
fn(req, res, next);
}
}
use(fn) {
this._middlewares.push(fn);
}
};

消息合并

做移动端Hybird等通信方案的同学,可能知道消息发送过于频繁时,不能立即发送所有事件,需要合并消息后发送,降低发送频次。在 San DevTools 中,消息不是立即发送,先放入堆栈中,通过 requestAnimationFrame 自动根据系统的帧频来发送事件。简化后的示例代码如下:

const BATCH_DURATION = 100;


interface Wall {
listen: (fn: Function) => void;
send: (data: any) => void;
}


export default class Bridge extends EventEmitter {
constructor(wall: Wall) {
super();
this.wall = wall;
wall.listen((messages: Message | Message[]) => {
this._emit(messages);
});
}


/**
* Send an event.
*
* @param {String} event
* @param {*} payload
*/
send(event: string, payload: any) {
this._batchingQueue.push({
event,
payload
});


const now = Date.now();
if (now - this._time > BATCH_DURATION) {
this._flush();
}
else {
this._timer = setTimeout(() => this._flush(), BATCH_DURATION);
}
}


_emit(message: Message) {
if (typeof message === 'string') {
this.emit(message);
}
else if (message.chunk) {
// chunk 模式时,合并数据
this._receivingQueue.push(message.chunk);
if (message.isLast) {
this.emit(message.event, this._receivingQueue);
this._receivingQueue.length = 0;
}
}
else {
this.emit(message.event, message.payload);
}
}


_send(messages: Message | Message[]) {
this._sendingQueue.push(messages);
this._nextSend();
}


_nextSend() {
// 如果没有消息或正在发送中,停止发送,等待 requestAnimationFrame
if (!this._sendingQueue.length || this._sending) {
return;
}
this._sending = true;
const messages = this._sendingQueue.shift();


// 真正发送
this.wall.send(messages);


this._sending = false;


// 根据系统帧频调用发送事件
requestAnimationFrame(() => this._nextSend());
}
}

有三个点可以关注下:

  1. _emit 方法接收数据时有个chunk模式,先把数据接收到 _receivingQueue ,当标志为最后一条数据时,把 _receivingQueue 整体 emit 出去;

  2. send 方法里有个BATCH_DURATION的变量,控制事件的最小时间;

  3. _nextSend 中通过 requestAnimationFrame 根据系统帧频发送事件;

字体图标Iconfont

阿里的iconfont提供了非常丰富的字体图标,可以通过项目管理的方式,在平台管理一系列图标,并做为整体下载提供一个字体文件,包含了所有的图标。对于多人维护的项目,管理起来不是很方便,也不便于确认哪些图标没有使用。 DevTools 中我们通过一定的技巧,通过代码的方式维护所有的图标,使用起来非常方便。

  1. 首先实现 icon.san 基础字体组件,核心是通过 type 属性,渲染不同的字体图标   

<template>
<svg
xmlns="http://www.w3.org/2000/svg"
class="{{['Icon', className]}}"
width="24"
height="24"
viewBox="0 0 1024 1024">
<template s-for="item in paths">
<path fill="currentColor" d="{{item}}" />
</template>
</svg>
</template>
<script>
import icons from './icon.ts';
export default {
initData() {
return {
className: '',
}
},

computed: {
paths() {
let type = this.data.get('type');
return icons[type] || null;
}
}

}
</script>
<style lang="less">
.Icon {
width: 1em;
height: 1em;
fill: currentColor;
}
</style>

2. 在 icons.ts 定义/维护全部的字体图标

  /* eslint-disable */
const PATH_START_RECORD = ['M920.833 281.025a37 37 0 0 0-36.959-0.071L728.47 370.148V233.041c0-20.435-16.565-37-37-37H121.708c-20.435 0-37 16.565-37 37V790.96c0 20.435 16.565 37 37 37H691.47c20.435 0 37-16.565 37-37V653.865l155.406 89.182a36.975 36.975 0 0 0 18.416 4.909 37 37 0 0 0 37-37V313.044a36.998 36.998 0 0 0-18.459-32.019zM525.402 541.971L385.664 651.059a36.993 36.993 0 0 1-38.929 4.118 37 37 0 0 1-20.839-33.139l-0.857-219.66a37.002 37.002 0 0 1 59.873-29.228l140.597 110.572a37 37 0 0 1-0.107 58.249z m339.89 105.092L728.47 568.546V455.47l136.822-78.529v270.122z'];

const ICONS = {
'start-record': PATH_START_RECORD
};

export default ICONS;

3. 最后使用时就非常简单了,指定type即可

<template>
<san-custom-icon type="start-record"></san-custom-icon>
</template>
<script>
import CustomIcon from '@components/icon.san';
export default {
components: {
'san-custom-icon': CustomIcon,
}
}
</script>

最后

San生态全景,繁荣的生态建设,落地在Feed、搜索、小程序等百度核心主航道业务,已经成为百度大前端的基础设施。

iiUraez.png!mobile

参考资料

[1] Extending DevTools https://developer.chrome.com/docs/extensions/mv2/devtools/

[2] Chrome DevTools Protocol https://github.com/chromedevtools/devtools-protocol

[3] Chrome Browser Protocol https://github.com/ChromeDevTools/devtools-protocol/blob/master/json/browser_protocol.json

[4] Chrome Js Protocol https://github.com/ChromeDevTools/devtools-protocol/blob/master/json/js_protocol.json

[5] 深入理解 Chrome DevTools https://zhaomenghuan.js.org/blog/chrome-devtools.html

[6] Chrome DevTools Frontend 运行原理浅析 https://zhaomenghuan.js.org/blog/chrome-devtools-frontend-analysis-of-principle.html

[7] Chrome DevTools 远程调试协议分析及实战 https://cloud.tencent.com/developer/article/1620907

[8] Chrome插件开发全攻略 https://www.cnblogs.com/liuxianan/p/chrome-plugin-develop.html

EOF

作者:刘斌 焕宇

2020 年 12 月 30 日


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK