面試官,我實現了一個 Chrome Devtools
網頁會加載資源、運行 JS、渲染界面、存儲數據等,我們開發時怎么看到執行的狀態呢?用調試工具 chrome devtools。它支持 dom 調試、JS debugger、本地存儲的展示、運行時間的 profile 等。
Node.js 也是同樣,不過它只支持 JS debugger 和 profile。我們可以通過 chrome devtools 或者 vscode debugger 等來調試。
這些工具都是遠程 attach 到運行的程序上來調試的,之間怎么交互數據呢?通過 webSocket。而且還制定了 chrome devtools protocol 的協議,規定了有什么能力,如何通信。
這種基于 websocket 的調試協議叫做 chrome devtools protocol。因為功能比較多,所以分了多個域(一般復雜的東西都會分域),包括 DOM、Debugger、Network、Page 等等,分別放不同的調試協議。chrome devtools 就是通過這個協議實現的調試。
新版 chrome(金絲雀版)可以打開設置中的實驗特性的 Protocol Monitor 面板。
就可以看到傳輸的 CDP 數據:
這就是 chrome devtools 的原理。
理解了這個原理有什么用呢?
我們可以重新實現服務端,只要對接了調試協議,那么就能夠用 chrome devtools 來調試。
比如 kraken(把 css 渲染到 flutter)是怎么做到用 chrome devtools 調試 dom 和樣式的?就是對接了這個協議。
我們可以重新實現客戶端,只要對接了這個協議,那就可以用任何工具調試網頁/Node.js。
大家用 chrome devtools 可以調試 Node.js 和網頁,用 vscode debugger 也可以,用 webstorm debugger 也可以。為什么呢?因為它們都對接了這個協議。
那我們是不是可以對接這個協議實現一個類似 chrome devtools 的調試工具呢?
我們來實驗下:
我們啟動 chrome,通過 --remote-debugging-port 指定調試端口:
- /Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --remote-debugging-port=9222
然后連上它。
這里我們不用直接對接協議,chrome 提供了各種語言的 sdk,調用 api 就行:
我們先連接上 chrome:
- const CDP = require('chrome-remote-interface');
- async function test() {
- let client;
- try {
- client = await CDP();
- const { Page, DOM, Debugger } = client;
- //...
- } catch(err) {
- console.error(err);
- }
- }
- test();
然后打開 baidu.com,等 2s,做個截圖:
- const CDP = require('chrome-remote-interface');
- const fs = require('fs');
- async function test() {
- let client;
- try {
- client = await CDP();
- const { Page, DOM, Debugger } = client;
- await Page.enable();
- await Page.navigate({url: 'https://baidu.com'});
- await new Promise(resolve => setTimeout(resolve, 2000));
- const res = await Page.captureScreenshot();
- fs.writeFileSync('./screenshot.jpg', res.data, {
- encoding: 'base64'
- });
- } catch(err) {
- console.error(err);
- }
- }
- test();
查看下效果:
這樣,我們就跑通了 CDP 的第一段代碼。
其余的功能,包括 Network、Debugger、DOM 等等也能實現,我們簡單試一下:
- await DOM.enable();
- const { root } = await DOM.getDocument({
- depth: -1
- });
depth 為深度,設置為 -1 就是返回整個 dom:
有了這些數據我們是不是可以做 DOM 的瀏覽呢?
還有 DOM.setAttributeValue 可以設置屬性、DOM.getBoxModel 拿到盒模型大小等。
基于這些,我們做個 dom 編輯器沒問題吧。
還有網絡部分:
- await Network.enable();
- Network.on('responseReceived', async evt => {
- const res = await Network.getResponseBody({
- requestId: evt.requestId
- });
- console.log(evt.response.url);
- console.log(res.body);
- });
我們通過 responseReceived 事件監聽每一個響應,然后再通過 Network.getResponseBody 拿到響應的內容:
基于這些,我們實現 Network 面板的功能沒問題吧。
還可以對接 profiler:
- await Profiler.start();
- await new Promise(resolve => setTimeout(resolve,2000));
- const { profile } = await Profiler.stop();
有這些數據,我們就可以通過 canvas 畫出個火焰圖出來。
理論上來說,chrome devtools 的所有功能我們都能實現。而且,一個網頁同時用多個調試工具調試是可以的,因為 websocket 本來就可以有多個客戶端。
可能你會說自己實現 chrome devtools 有什么意義?
大家自己做開源前端項目的時候,一般都是寫個網易云音樂客戶端,因為有現成的數據可以用。那為什么不做個 chrome devtools 呢?也有現成的數據啊,啟動瀏覽器就行,而且這個逼格多高啊。
我們也不用實現完整的 chrome devtools,可以單把網絡部分、單把 DOM 部分、單把 debugger 部分實現了,可以做不同的 UI,可以做 chrome devtools 沒有的功能和交互。
比如你面試可視化崗位,你說你對接了 chrome devtools protocol 的 profiler 部分,用 canvas 畫了個火焰圖,會加分很多的。
總結
Chrome 的調試是通過 WebSocket 和調試客戶端通信,制定了 Chrome Devtools Protocol 的協議,Node.js 也是,不過協議叫做 V8 debugger protocol。我們可以通過 protocol monitor 的面板看到所有的 CDP 協議請求響應。
可以實現 CDP 服務端,來對接 chrome devtools 的調試功能,調試不同的目標,比如 kraken 渲染引擎。
可以實現 CDP 客戶端,來用不同的工具調試,比如 vscode debugger、webstorm debugger 等。
我們也可以通過 sdk 的 api 來和 CDP 服務端對接,拿到數據,實現調試的功能。比如單獨實現 DOM 編輯器、Network 查看器、JS Debugger、 Profiler 和火焰圖都可以,而且可以做到比 chrome devtools 更強的功能,更好的交互。
當大家想做開源項目沒有數據的時候,不妨考慮下做個 CDP 客戶端,這不比云音樂項目香么?