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 進行通訊。