San DevTools 技术解析(下)
2021.01.11 23:24浏览量:535简介:除Backend、Frontend、Message Channel和Debugging Protocol这些技术外,今天来讲下:DevTools 扩展开发和项目中其他比较有价值的技术点。
前言
我们已经连续分享了《San DevTools 技术解析》上篇与中篇,讲了其中四大模块:Backend
、Frontend
、Message Channel
和Debugging 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 扩展实例
实现扩展可通过简单的三步实现:
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,调试时各模块方式也不同,这里做个汇总:
-
Content Script 调试:F12或右键->检查,包括Inject Script
-
Devtools Panel 调试:panel 面板里右键 -> 检查
-
Backgroud 调试:chrome://extensions/ -> 背景页
总结:
-
Backgroud 是常驻的,与浏览器生命周期相同
-
Content Script 是页面级的,与页面生命周期相同
-
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
里我们简易实现一个中间件服务器。
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());
}
}
有三个点可以关注下:
-
_emit
方法接收数据时有个chunk模式,先把数据接收到_receivingQueue
,当标志为最后一条数据时,把_receivingQueue
整体emit
出去; -
在
send
方法里有个BATCH_DURATION的变量,控制事件的最小时间; -
_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、搜索、小程序等百度核心主航道业务,已经成为百度大前端的基础设施。
参考资料
[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
发表评论
登录后可评论,请前往 登录 或 注册