面試官,我實(shí)現(xiàn)了一個(gè) Chrome Devtools
網(wǎng)頁會(huì)加載資源、運(yùn)行 JS、渲染界面、存儲(chǔ)數(shù)據(jù)等,我們開發(fā)時(shí)怎么看到執(zhí)行的狀態(tài)呢?用調(diào)試工具 chrome devtools。它支持 dom 調(diào)試、JS debugger、本地存儲(chǔ)的展示、運(yùn)行時(shí)間的 profile 等。
Node.js 也是同樣,不過它只支持 JS debugger 和 profile。我們可以通過 chrome devtools 或者 vscode debugger 等來調(diào)試。
這些工具都是遠(yuǎn)程 attach 到運(yùn)行的程序上來調(diào)試的,之間怎么交互數(shù)據(jù)呢?通過 webSocket。而且還制定了 chrome devtools protocol 的協(xié)議,規(guī)定了有什么能力,如何通信。
這種基于 websocket 的調(diào)試協(xié)議叫做 chrome devtools protocol。因?yàn)楣δ鼙容^多,所以分了多個(gè)域(一般復(fù)雜的東西都會(huì)分域),包括 DOM、Debugger、Network、Page 等等,分別放不同的調(diào)試協(xié)議。chrome devtools 就是通過這個(gè)協(xié)議實(shí)現(xiàn)的調(diào)試。
新版 chrome(金絲雀版)可以打開設(shè)置中的實(shí)驗(yàn)特性的 Protocol Monitor 面板。
就可以看到傳輸?shù)?CDP 數(shù)據(jù):
這就是 chrome devtools 的原理。
理解了這個(gè)原理有什么用呢?
我們可以重新實(shí)現(xiàn)服務(wù)端,只要對接了調(diào)試協(xié)議,那么就能夠用 chrome devtools 來調(diào)試。
比如 kraken(把 css 渲染到 flutter)是怎么做到用 chrome devtools 調(diào)試 dom 和樣式的?就是對接了這個(gè)協(xié)議。
我們可以重新實(shí)現(xiàn)客戶端,只要對接了這個(gè)協(xié)議,那就可以用任何工具調(diào)試網(wǎng)頁/Node.js。
大家用 chrome devtools 可以調(diào)試 Node.js 和網(wǎng)頁,用 vscode debugger 也可以,用 webstorm debugger 也可以。為什么呢?因?yàn)樗鼈兌紝恿诉@個(gè)協(xié)議。
那我們是不是可以對接這個(gè)協(xié)議實(shí)現(xiàn)一個(gè)類似 chrome devtools 的調(diào)試工具呢?
我們來實(shí)驗(yàn)下:
我們啟動(dòng) chrome,通過 --remote-debugging-port 指定調(diào)試端口:
- /Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --remote-debugging-port=9222
然后連上它。
這里我們不用直接對接協(xié)議,chrome 提供了各種語言的 sdk,調(diào)用 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,做個(gè)截圖:
- 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 等等也能實(shí)現(xiàn),我們簡單試一下:
- await DOM.enable();
- const { root } = await DOM.getDocument({
- depth: -1
- });
depth 為深度,設(shè)置為 -1 就是返回整個(gè) dom:
有了這些數(shù)據(jù)我們是不是可以做 DOM 的瀏覽呢?
還有 DOM.setAttributeValue 可以設(shè)置屬性、DOM.getBoxModel 拿到盒模型大小等。
基于這些,我們做個(gè) dom 編輯器沒問題吧。
還有網(wǎng)絡(luò)部分:
- 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 事件監(jiān)聽每一個(gè)響應(yīng),然后再通過 Network.getResponseBody 拿到響應(yīng)的內(nèi)容:
基于這些,我們實(shí)現(xiàn) Network 面板的功能沒問題吧。
還可以對接 profiler:
- await Profiler.start();
- await new Promise(resolve => setTimeout(resolve,2000));
- const { profile } = await Profiler.stop();
有這些數(shù)據(jù),我們就可以通過 canvas 畫出個(gè)火焰圖出來。
理論上來說,chrome devtools 的所有功能我們都能實(shí)現(xiàn)。而且,一個(gè)網(wǎng)頁同時(shí)用多個(gè)調(diào)試工具調(diào)試是可以的,因?yàn)?websocket 本來就可以有多個(gè)客戶端。
可能你會(huì)說自己實(shí)現(xiàn) chrome devtools 有什么意義?
大家自己做開源前端項(xiàng)目的時(shí)候,一般都是寫個(gè)網(wǎng)易云音樂客戶端,因?yàn)橛鞋F(xiàn)成的數(shù)據(jù)可以用。那為什么不做個(gè) chrome devtools 呢?也有現(xiàn)成的數(shù)據(jù)啊,啟動(dòng)瀏覽器就行,而且這個(gè)逼格多高啊。
我們也不用實(shí)現(xiàn)完整的 chrome devtools,可以單把網(wǎng)絡(luò)部分、單把 DOM 部分、單把 debugger 部分實(shí)現(xiàn)了,可以做不同的 UI,可以做 chrome devtools 沒有的功能和交互。
比如你面試可視化崗位,你說你對接了 chrome devtools protocol 的 profiler 部分,用 canvas 畫了個(gè)火焰圖,會(huì)加分很多的。
總結(jié)
Chrome 的調(diào)試是通過 WebSocket 和調(diào)試客戶端通信,制定了 Chrome Devtools Protocol 的協(xié)議,Node.js 也是,不過協(xié)議叫做 V8 debugger protocol。我們可以通過 protocol monitor 的面板看到所有的 CDP 協(xié)議請求響應(yīng)。
可以實(shí)現(xiàn) CDP 服務(wù)端,來對接 chrome devtools 的調(diào)試功能,調(diào)試不同的目標(biāo),比如 kraken 渲染引擎。
可以實(shí)現(xiàn) CDP 客戶端,來用不同的工具調(diào)試,比如 vscode debugger、webstorm debugger 等。
我們也可以通過 sdk 的 api 來和 CDP 服務(wù)端對接,拿到數(shù)據(jù),實(shí)現(xiàn)調(diào)試的功能。比如單獨(dú)實(shí)現(xiàn) DOM 編輯器、Network 查看器、JS Debugger、 Profiler 和火焰圖都可以,而且可以做到比 chrome devtools 更強(qiáng)的功能,更好的交互。
當(dāng)大家想做開源項(xiàng)目沒有數(shù)據(jù)的時(shí)候,不妨考慮下做個(gè) CDP 客戶端,這不比云音樂項(xiàng)目香么?