近期團隊內在自研小程式,我負責開發者工具中的除錯部分,除錯作為面向開發者的基礎能力,扮演了極為重要的角色,
本篇文章是導讀文章,
除錯能力從0到1一共經歷了4個版本,接下來的文章將會以這4個版本為主線分別進行介紹,
初始版

上圖為除錯還不存在時的一個通信關系圖,
在彼時已經實作了邏輯代碼與渲染代碼的運行隔離,其中邏輯代碼是運行在一個vm中的,
- 渲染層通過Electron提供的IPC能力與electron進行通信,
- electron持有vm的參考,在收到渲染層的請求后,Electron會直接交給vm執行,
- vm中運行的代碼會通過vm的Context方法將執行結果拋出,
- vm收到代碼后直接通過渲染層容器BrowserView的參考通過executeJavaScript將結果返還給渲染層,
通過以上4步完成了一個簡單的渲染層、邏輯層的通信倍訓,這其中有渲染層代碼、邏輯層代碼、preload、electron、vm、BrowserView 6個角色參與,
這個階段的特點是:實作了渲染代碼與邏輯代碼的隔離,還不具備基礎的斷點除錯能力,
第一版

這一版比初始版要復雜了一些,它實作了邏輯代碼的斷點能力,它的主要改進是:
- 將vm轉移到了獨立的行程中,
- 通過node --inspect-brk使邏輯層代碼運行于除錯狀態,
- 由于邏輯層代碼運行于獨立行程中,所以使用了IPC使渲染層與邏輯層維持通信狀態,
- 加入了可視化的除錯界面,可以對代碼執行基本的除錯控制操作,可以從控制臺看到渲染層的日志輸出,
它的不足之處在于無法審查DOM結構,也無法查看Network記錄,
第二版

這一版比上一版的改進在于可以查看Network記錄,同時也可以審查基本的DOM結構,
運行示例:
這一版將邏輯層代碼運行于worker內,除錯審查面板采用了electron自帶的除錯工具,
在worker內部運行邏輯代碼解決了Network審查的問題,electron自帶的除錯工具可以以較小的成本在除錯工具中增加一個Tab,這里用了chrome extensions的能力,
為了不影響邏輯代碼的執行,這一版采用adapter扮演了上一版vm的角色,使上層的邏輯代碼無感知的進行了運行時環境的遷移,adapter負責底層資料的通信,
這一版最大的難點在DOM結構的審查,這是因chrome extensions 運行于邏輯層容器上,而DOM資訊位于渲染層容器上,有人可能會問,把擴展放到渲染層容器上不就解決了嗎?答案是否定的,因為console network source 這些能力與邏輯層嚴格關聯,而除錯面板只有一個,必須做出成本方面的取舍,
解決DOM審查的辦法是將渲染層與邏輯層的審查通信通道打通,這里就不得不提到chrome extensions的實作,chrome extensions主要由3部分組成:
- frontend.js 這個檔案運行于除錯面板tab內,
- backend.js 這個檔案運行于網頁的背景關系中,
- background.js 這個檔案負責frontend.js與backend.js的通信,
以下這張圖簡單的描述了它們三者之間的關系:

這里以已經非常成熟的extensions vue-devtools來做說明,vue-devtools的結構和上圖一樣,backend.js是負責從頁面中獲得Vue的組件樹結構然后再通過background.js發送給frontend.js來展示的,
而在我們的小程式中,backend.js所運行的環境中并沒有Vue的組件資訊,這些資訊在哪呢?它位于渲染層的運行環境中,所以我們需要做一些適當的改造(基于vue-devtools),如下:

就是將原本運行于邏輯層網頁環境中的backend.js移植到了渲染層網頁環境中執行,而之前在邏輯層網頁環境中運行的backend.js變為了backend.proxy.js,它負責內外環境的通信,這里的內是指extensions的proxy.js,外是指electron.js,渲染層中的GlueLayout.js扮演了之前的proxy.js的角色,負責backend.js與外部的通信適配,
以上僅僅是打通了邏輯層與渲染層的審查通信通道,而這還不夠,因為我們需要審查的是渲染層的DOM結構,目前只能看到的是渲染層的Vue組件結構,所以還需要一些改造,
為了兼容DOM審查與資料審查兩種能力,我們想出了一種創新方式,就是將組件結構與DOM結構合二為一,例如:
# Main.vue
<template>
<div class="main">
<Hello></Hello>
</div>
</template>
# Hello.vue
<template>
<div class="hello">
<span>This is Hello components!</span>
</div>
</template>
實際審查時會變為:
<div class="main">
<Hello>
<div class="hello">
<span>This is Hello components!</span>
</div>
</Hello>
</div>
當點擊組件節點時展示的是組件本身的資訊(完全是vue-devtools的能力),而當點擊DOM節點時展示的是元素本身的資訊(沒有實作),
這一版相比上一版實作了DOM樹結構的審查與組件資料審查,也實作了Network的審查,而不足之處在于還不能夠實作Elements本身的審查,比如修改樣式,查看內外邊距等基礎能力,
第三版

這一版相比于上一版有了比較完善的能力:
- 完整的DOM審查能力,
- Console控制臺,
- Source除錯,
- Network審查,
- 頁面資料審查,
與市面上的其它小程式開發者工具相比,該有的基礎能力都具備了,
這一版的除錯面板又采用了第二版所使用的chrome devtools frontend方案,與第二版不同的在于邏輯代碼的運行采用的是第三版的方案,
這一版遇到了三個很大的挑戰:
- 如何使用一個除錯面板控制渲染層的DOM結構與邏輯層的代碼邏輯?
- 如何在缺少資料的情況下在chrome devtools frontend專案中增加一個新的有完全能力的tab?
- 如何獲得審查資料?
這里簡單分別說明一下以上三個問題是如何解決的,
問題1
chrome devtools frontend(下文簡稱frontend)是谷歌官方研發的給chrome使用的除錯面板專案,
frontend在啟動后會通過WebSocket連接到一個目標除錯地址,注意,**這個地址只能是一個地址,**那么問題來了,現在邏輯層、渲染層分別運行于兩個獨立的環境中,我應該連接誰呢?連接誰都不靠譜,
唯一的解決方案是,我們提供一個除錯中繼服務,讓frontend連接這個中繼服務,這個中繼服務分別去連接邏輯層除錯服務與渲染層除錯服務,如下圖所示:

問題2
由于frontend專案在今年完全改為了TS的寫法,導致每次修改、查看需要花費10多分鐘的編譯時間,而為了壓縮這可觀的時間,順藤摸瓜找到了在修改為TS寫法之前的最后一個版本,這個版本是用JS寫的,可以修改后直接在瀏覽器中預覽效果,最大的好處在于可以實時的除錯代碼了,這對了解frontend專案的運行原理大開方便之門,
有了以上條件還不夠,因為frontend專案不同于傳統的前端專案,它沒有構建的程序,龐大的專案全是依靠組態檔動態加載生成的,
經過一段時間的摸索和大量的除錯,找到了frontend從啟動到最終渲染一個TAB的完整程序,知道了它是怎么加載的,那增加一個TAB也是板上釘釘的事情了,
在frontend中增加一個TAB的關鍵代碼一覽:

但問題到此就解決了?不不不,還早著呢,完成以上步驟僅僅是有了一個TAB,但它里面是空的,什么都沒有,那怎么往里面添加內容呢?
下面這段代碼是除錯面板Element的初始化代碼(一部分):

Emmm,怎么說呢,和我們一般見到的形式完全不同,既不是原生DOM操作,也不是JQuery、Vue這類的第三方框架,這怎么下手呢?
原來frontend封裝了大量的組件,上面代碼中的ElementPanel所繼承的UI.Panel.Panel就是一個組件,最開始我嘗試使用這些組件,但由于沒有檔案,加上代碼量龐大,用起來非常的吃力,效果也不好,最終通過代碼閱讀找到了這些組件暴露在外的element,那么我將Vue掛載到這個Element上就可以使用vue的方式去實作這個TAB的內容了,如圖所示:

問題3
因為frontend是基于websocket與外界通信的,element、console、source這些模塊都是通過內置的websocket client與外界交換資料,而這個websocket實體被高度封裝,很難在Vue中直接使用,例如,Element是通過這種方式去獲取DOM資料的:

注意這里的invoke_getDocument方法是動態合成的:

這里不展開展示細節了,總之如果按照frontend的方式實作通信的程序改造難度非常大,這時我想另辟蹊徑,自建一條通信通道,但后來想想又放棄了,這不是個好的辦法,最侄訓是決定從內置的websocket上入手,看看哪些關鍵的地方可以暴露給全域使用,最終經過不斷的除錯找到了這個關鍵的物件:

這樣一來,我便可以隨便使用了:
export function sendMessage(method, params) {
return new Promise((resolve, reject) => {
// self.target為通信關鍵物件
self.target._router.sendMessage("", "DataInspect", `DataInspect.${method}`, params, (error, result) => {
if (error) {
console.error('Request ' + method + ' failed. ' + JSON.stringify(error));
reject(null);
return;
}
resolve(result);
})
})
}
target這個物件可以保證請求與回呼完全一一對應,不出錯,不混,不亂,這為我后來實作主動監聽邏輯層回呼提供了實作思路,
Final
*小程式的除錯技術從0到1一共經歷了3個版本的演化才達到了一個完善的狀態,雖然演化的程序中被不斷推翻之前的方案,但帶來的結果終究是完美的,這是一個必然的程序,因為不踩坑不知道坑的存在,
小程式除錯這塊對我來說最大的挑戰在于:每一步幾乎都在摸索,假設、實作、驗證無限回圈,不斷完善,實際上除錯涉及的最核心的技術應該是通信:比如邏輯層與渲染層的通信、除錯面板與除錯源的通信,經歷了各種復雜的角色才完成了一項基礎能力,
最后貼一下第三版基本除錯能力實作全圖:
資料審查面板:

Source面板:

Console控制臺:

DOM審查面板:

除錯中斷:

Network網路資源審查:

Network XHR審查:

好,導讀文章就到這里,接下來會分幾篇文章詳細介紹第三版的完整實作,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/291997.html
標籤:其他
上一篇:Centos安裝Nginx
