npm i -g devtools-pro
# OR
yarn global add devtools-pro
复制代码
devtools-pro -h
# or
dp -h
复制代码
Options:
-h, --help Show help [boolean]
--plugins Add plugins [array]
--config Provide path to a devtools configuration file e.g.
./devtools.config.js [string] [default: "devtools.config"]
-o, --open Open browser when server start [boolean] [default: true]
--https Use HTTPS protocol. [boolean]
-p, --port Port to use [8899] [number]
--verbose Displays verbose logging [boolean] [default: false]
--hostname Address to use [0.0.0.0] [string]
-v, --version Show version number [boolean]
复制代码
devtools.config.js
为了方便项目统一配置,DevTools-pro 支持配置文件,能够在项目中建立一个名为devtools.config.js
的文件,支持的配置项以下:css
silent
verbose
8899
0.0.0.0
https=true
,或者使用此字段配置nodejs/https 模块相关配置,例如:https:{
key: fs.readFileSync('/path/to/server.key'),
cert: fs.readFileSync('/path/to/server.crt'),
ca: fs.readFileSync('/path/to/ca.pem'),
}
复制代码
mkdir devtools-pro
git clone git@github.com:ksky521/devtools-pro.git devtools-pro
复制代码
yarn
# 初始化:将chrome-devtools-frontend/front_end复制出来
sh init.sh
复制代码
yarn dev
复制代码
访问:html
DevTools-pro 是基于chrome-devtools-frontend进行开发的,经过自建 WebSocket 通道实现 Frontend 和 Backend 的通讯。前端
DevTools 主要由四部分组成:vue
这四部分的交互逻辑以下图所示:node
简单来讲:被调试页面引入 Backend 后,会跟 Frontend 创建链接;在 backend 中,对于一些 JavaScript API 或者 DOM 操做等进行了监听和 mock,从而页面执行对应操做时,会发送消息到 Frontend。同时 Backend 也会监听来自于 Frontend 的消息,收到消息后进行对应处理。react
DevTools-pro 是能够经过插件增长功能的,好比:git
插件能够发布一个 NPM 包,而后在项目下的devtools.config.js
中经过plugins
进行添加,一个 plugins 是一个 NPM 包,由如下三部分组成:github
这三部分根据本身插件的实际功能进行开发,并不是都包含。三部分的定义是在 NPM 包的package.json
中devtools
字段,相似:web
{
name: 'js-native-monitor',
version: '1.0.0',
main: 'index.js',
// ....
devtools: {
// middleware
frontend: {
name: 'jsna_monitor',
type: '', // remote/autostart
dir: 'frontend'
},
// backend字段,该文件内容会被merge到backend.js中
backend: 'index.js',
// middleware
middleware: 'middleware.js'
}
}
复制代码
Frontend 是彻底符合的chrome-devtools-frontend的模块,package.json
中的devtools.frontend
包含配置有:chrome
hostname:port/devtools/${name}/**
则自动转发到这里,优先级高于内置和 chrome-devtools-frontend/front_end 文件,若是 name 是 chrome-devtools-frontend/front_end 已经存在的则优先级高于 chrome-devtools-frontend;autostart
和remote
,含义参考 Chrome DevTools 具体实现;dir 文件夹中的重要文件是模块描述文件module.json
,经过文件夹下的 module.json
配置文件进行定义,配置文件有如下几个属性:
scripts
:模块中包含的 JavaScript 文件数组,这里的路径名称是相对于 module.json 的位置;skip_compilation
:相似于脚本,可是 Closure Compiler 不会对这些文件进行类型检查;resources
:模块使用的非 JavaScript 文件数组;dependencies
:模块使用的其余模块的数组;extensions
:具备 type 属性的对象数组。 扩展能够经过运行时系统查询,并能够经过任何模块中的代码进行访问。类型包括 "setting"、"view","context-menu-item"。例如能够按以下方式注册出如今设置屏幕中的设置:{
"extensions": [
{
"type": "setting",
"settingName": "interdimensionalWarpEnabled",
"settingType": "boolean",
"defaultValue": false,
"storageType": "session",
"title": "Show web pages from other dimensions"
},
...
]
}
复制代码
DevTools Frontend 经过 Module 和 Extension 机制为 Application 增长了“插件化”的能力,而后经过配置进行灵活的组装。
咱们应用作多的多是添加一个面板,例如我要添加一个js-native
的面板,则module.json
内容以下:
{
extensions: [
{
// 类型
type: 'view',
// 位置
location: 'panel',
id: 'jsna_monitor',
// 面板显示文字
title: 'jsNative monitor',
order: 110,
// 启动className
className: 'JSNAMonitor.JSNAMonitor'
}
],
// 依赖
dependencies: ['platform', 'ui', 'host', 'components', 'data_grid', 'source_frame', 'sdk'],
scripts: [],
// 资源
modules: ['jsna_monitor.js', 'jsna_monitor-legacy.js', 'JSNAMonitor.js'],
resources: ['jsna.css']
}
复制代码
此部分能够参考@ksky521/js-native-monitor实现。
当被调试的页面引入hostname:port/backend.js
时,backend 的文件会被合并到backend.js
中输出。这里提供了全局命名空间$devtools
,它的定义在./src/runtime.js中。后面通讯部分会详细介绍
在原来的 CDP 基础上,为了方便开发插件开发,DevTools-pro 提供了两种 Backend 和 Frontend 插件的通讯方式:CDP 事件和自建 WebSocket。
在 Backend 中,提供了一个全局命名空间$devtools
,能够经过下面方法进行事件注册。
// backend中代码
$devtools.registerEvent('PluginName.method', data => {
const result = '处理完的返回数据';
console.log(data);
//...
return result;
});
// frontend插件中,发送命令给backend
runtime.bridge.sendCommand('PluginName.method', {}).then(a => console.log(111, a));
// 输出:111,处理完的返回数据
// -> frontend发送数据以后,会获得一个Promise,获得的数据是backend的事件处理函数直接返回的数据。
复制代码
注意:推荐事件命名上采用跟 CDP 一致的方式,即以.
间隔,以此来防止命名冲突,形成事件相互覆盖。
DevTools-pro 自己自带 WebSocket 服务,因此能够在 Backend 中使用$devtools.createWebsocketConnection(wsUrl)
建立一个 WebSocket 连接:
// backend代码
const channelId = $devtools.nanoid();
// -> 这里注意路径必须是/backend/开头
const wsUrl = $devtools.createWebsocketUrl(`/backend/${channelId}`);
const ws = $devtools.createWebsocketConnection(wsUrl);
ws.on('message', event => {
// message
});
// 发送数据
ws.send('hi~');
// ws连接创建成功
ws.on('open', onOpen);
复制代码
在 Frontend 插件中,须要利用 ChannelId 创建一条相同的 MessageChannel,这时候应该经过 CDP 事件将 channelId 由 Backend,发送的 Frontend:
// backend
$devtools.sendCommand('PluginName.channelId', channelId);
复制代码
而后在 Frontend 插件中:
runtime.bridge.registerEvent('PluginName.channelId', channelId => {
const wsUrl = `/frontend/${channelId}`;
const ws = new WebSocket(wsUrl);
ws.onmessage = event => {
console.log(event.data);
};
ws.send('i am ready');
});
复制代码
middleware 的定义是在server/Server.js,接受 3 个参数middleware(router, logger, serverInstance)
:
router
是koa-router的实例;logger
是consola对象,有logger.log
、logger.info
、logger.debug
等方法;serverInstance
是 Server 类实例给 server 添加 router:
// middleware.js
module.exports = router => {
router.get('/hi', ctx => {
ctx.body = 'world';
});
};
复制代码
咱们能够启动 DevTools-pro 以后,经过chrome-remote-interface连接 WebSocket,而后经过发送 CDP 命令,进行自动化测试。
const CDP = require('chrome-remote-interface');
CDP(
{
target: 'ws://localhost:8899/frontend/TDBmn-IDKkaIV98iW20Qh'
},
async client => {
const {Page, Runtime} = client;
await Page.enable();
const result = Runtime.evaluate({expression: 'window.location.toString()'});
console.log(result);
}
);
复制代码
咱们能够在 frontend 的 module 中,添加一个 iframe 面板:
export class SanDevtoolsPanel extends UI.VBox {
constructor() {
super('san_devtools');
this.registerRequiredCSS('san_devtools/san_devtools.css', {enableLegacyPatching: false});
this.contentElement.classList.add('html', 'san-devtools');
}
wasShown() {
this._createIFrame();
}
willHide() {
this.contentElement.removeChildren();
}
_createIFrame() {
this.contentElement.removeChildren();
const iframe = document.createElement('iframe');
iframe.className = 'san-devtools-frame';
iframe.setAttribute('src', '/san-devtools.html');
iframe.tabIndex = -1;
UI.ARIAUtils.markAsPresentation(iframe);
this.contentElement.appendChild(iframe);
}
}
复制代码
而后在 Frontend 嵌入的页面中,能够直接创建本身的 WebSocket 连接直接跟 Backend 进行通讯。