近期公司內(nèi)在自研小程序吨铸,我負(fù)責(zé)其中的調(diào)試部分,主要面向于開發(fā)者工具祖秒。
本篇文章是導(dǎo)讀文章诞吱。
調(diào)試能力從0到1一共經(jīng)歷了4個版本,接下來的文章將會以這4個版本為主線分別進(jìn)行介紹竭缝。
初始版
上圖為調(diào)試還不存在時的一個通信關(guān)系圖房维。
在彼時已經(jīng)實(shí)現(xiàn)了邏輯代碼與渲染代碼的運(yùn)行隔離,其中邏輯代碼是運(yùn)行在一個vm中的抬纸。
- 渲染層通過Electron提供的IPC能力與electron進(jìn)行通信咙俩。
- electron持有vm的引用,在收到渲染層的請求后湿故,Electron會直接交給vm執(zhí)行暴浦。
- vm中運(yùn)行的代碼會通過vm的Context方法將執(zhí)行結(jié)果拋出溅话。
- vm收到代碼后直接通過渲染層容器BrowserView的引用通過executeJavaScript將結(jié)果返還給渲染層。
通過以上4步完成了一個簡單的渲染層歌焦、邏輯層的通信閉環(huán)飞几。這其中有渲染層代碼、邏輯層代碼独撇、preload屑墨、electron、vm纷铣、BrowserView 6個角色參與卵史。
這個階段的特點(diǎn)是:實(shí)現(xiàn)了渲染代碼與邏輯代碼的隔離,還不具備基礎(chǔ)的斷點(diǎn)調(diào)試能力搜立。
第一版
這一版比初始版要復(fù)雜了一些以躯,它實(shí)現(xiàn)了邏輯代碼的斷點(diǎn)能力。它的主要改進(jìn)是:
- 將vm轉(zhuǎn)移到了獨(dú)立的進(jìn)程中啄踊。
- 通過node --inspect-brk使邏輯層代碼運(yùn)行于調(diào)試狀態(tài)忧设。
- 由于邏輯層代碼運(yùn)行于獨(dú)立進(jìn)程中,所以使用了IPC使渲染層與邏輯層維持通信狀態(tài)颠通。
- 加入了可視化的調(diào)試界面址晕。可以對代碼執(zhí)行基本的調(diào)試控制操作顿锰,可以從控制臺看到渲染層的日志輸出谨垃。
它的不足之處在于無法審查DOM結(jié)構(gòu),也無法查看Network記錄硼控。
第二版
這一版比上一版的改進(jìn)在于可以查看Network記錄刘陶,同時也可以審查基本的DOM結(jié)構(gòu)。
運(yùn)行示例:這一版將邏輯層代碼運(yùn)行于worker內(nèi)牢撼。調(diào)試審查面板采用了electron自帶的調(diào)試工具易核。
在worker內(nèi)部運(yùn)行邏輯代碼解決了Network審查的問題。electron自帶的調(diào)試工具可以以較小的成本在調(diào)試工具中增加一個Tab浪默,這里用了chrome extensions的能力牡直。
為了不影響邏輯代碼的執(zhí)行,這一版采用adapter扮演了上一版vm的角色纳决,使上層的邏輯代碼無感知的進(jìn)行了運(yùn)行時環(huán)境的遷移碰逸,adapter負(fù)責(zé)底層數(shù)據(jù)的通信。
這一版最大的難點(diǎn)在DOM結(jié)構(gòu)的審查阔加。這是因chrome extensions 運(yùn)行于邏輯層容器上饵史,而DOM信息位于渲染層容器上。有人可能會問,把擴(kuò)展放到渲染層容器上不就解決了嗎胳喷?答案是否定的湃番,因?yàn)閏onsole network source 這些能力與邏輯層嚴(yán)格關(guān)聯(lián)。而調(diào)試面板只有一個吭露,必須做出成本方面的取舍吠撮。
解決DOM審查的辦法是將渲染層與邏輯層的審查通信通道打通。這里就不得不提到chrome extensions的實(shí)現(xiàn)讲竿,chrome extensions主要由3部分組成:
- frontend.js 這個文件運(yùn)行于調(diào)試面板tab內(nèi)泥兰。
- backend.js 這個文件運(yùn)行于網(wǎng)頁的上下文中。
- background.js 這個文件負(fù)責(zé)frontend.js與backend.js的通信题禀。
以下這張圖簡單的描述了它們?nèi)咧g的關(guān)系:
這里以已經(jīng)非常成熟的extensions vue-devtools來做說明鞋诗。vue-devtools的結(jié)構(gòu)和上圖一樣,backend.js是負(fù)責(zé)從頁面中獲得Vue的組件樹結(jié)構(gòu)然后再通過background.js發(fā)送給frontend.js來展示的迈嘹。
而在我們的小程序中削彬,backend.js所運(yùn)行的環(huán)境中并沒有Vue的組件信息,這些信息在哪呢秀仲?它位于渲染層的運(yùn)行環(huán)境中融痛。所以我們需要做一些適當(dāng)?shù)母脑欤ɑ趘ue-devtools),如下:
就是將原本運(yùn)行于邏輯層網(wǎng)頁環(huán)境中的backend.js移植到了渲染層網(wǎng)頁環(huán)境中執(zhí)行啄育。而之前在邏輯層網(wǎng)頁環(huán)境中運(yùn)行的backend.js變?yōu)榱薭ackend.proxy.js酌心,它負(fù)責(zé)內(nèi)外環(huán)境的通信拌消。這里的內(nèi)是指extensions的proxy.js挑豌,外是指electron.js。渲染層中的GlueLayout.js扮演了之前的proxy.js的角色墩崩,負(fù)責(zé)backend.js與外部的通信適配氓英。
以上僅僅是打通了邏輯層與渲染層的審查通信通道,而這還不夠鹦筹。因?yàn)槲覀冃枰獙彶榈氖卿秩緦拥腄OM結(jié)構(gòu)铝阐,目前只能看到的是渲染層的Vue組件結(jié)構(gòu)。所以還需要一些改造铐拐。
為了兼容DOM審查與數(shù)據(jù)審查兩種能力徘键,我們想出了一種創(chuàng)新方式,就是將組件結(jié)構(gòu)與DOM結(jié)構(gòu)合二為一遍蟋。例如:
# Main.vue
<template>
<div class="main">
<Hello></Hello>
</div>
</template>
# Hello.vue
<template>
<div class="hello">
<span>This is Hello components!</span>
</div>
</template>
實(shí)際審查時會變?yōu)椋?/p>
<div class="main">
<Hello>
<div class="hello">
<span>This is Hello components!</span>
</div>
</Hello>
</div>
當(dāng)點(diǎn)擊組件節(jié)點(diǎn)時展示的是組件本身的信息(完全是vue-devtools的能力)吹害,而當(dāng)點(diǎn)擊DOM節(jié)點(diǎn)時展示的是元素本身的信息(沒有實(shí)現(xiàn))。
這一版相比上一版實(shí)現(xiàn)了DOM樹結(jié)構(gòu)的審查與組件數(shù)據(jù)審查虚青,也實(shí)現(xiàn)了Network的審查它呀。而不足之處在于還不能夠?qū)崿F(xiàn)Elements本身的審查,比如修改樣式,查看內(nèi)外邊距等基礎(chǔ)能力纵穿。
第三版
這一版相比于上一版有了比較完善的能力:
- 完整的DOM審查能力下隧。
- Console控制臺。
- Source調(diào)試谓媒。
- Network審查淆院。
- 頁面數(shù)據(jù)審查。
與市面上的其它小程序開發(fā)者工具相比篙耗,該有的基礎(chǔ)能力都具備了迫筑。
這一版的調(diào)試面板又采用了第二版所使用的chrome devtools frontend方案。與第二版不同的在于邏輯代碼的運(yùn)行采用的是第三版的方案宗弯。
這一版遇到了三個很大的挑戰(zhàn):
- 如何使用一個調(diào)試面板控制渲染層的DOM結(jié)構(gòu)與邏輯層的代碼邏輯脯燃?
- 如何在缺少資料的情況下在chrome devtools frontend項(xiàng)目中增加一個新的有完全能力的tab?
- 如何獲得審查數(shù)據(jù)蒙保?
這里簡單分別說明一下以上三個問題是如何解決的辕棚。
問題1
chrome devtools frontend(下文簡稱frontend)是谷歌官方研發(fā)的給chrome使用的調(diào)試面板項(xiàng)目。
frontend在啟動后會通過WebSocket連接到一個目標(biāo)調(diào)試地址邓厕,注意逝嚎,這個地址只能是一個地址。那么問題來了详恼,現(xiàn)在邏輯層补君、渲染層分別運(yùn)行于兩個獨(dú)立的環(huán)境中,我應(yīng)該連接誰呢昧互?連接誰都不靠譜挽铁。
唯一的解決方案是,我們提供一個調(diào)試中繼服務(wù)敞掘,讓frontend連接這個中繼服務(wù)叽掘,這個中繼服務(wù)分別去連接邏輯層調(diào)試服務(wù)與渲染層調(diào)試服務(wù)。如下圖所示:
問題2
由于frontend項(xiàng)目在今年完全改為了TS的寫法玖雁,導(dǎo)致每次修改更扁、查看需要花費(fèi)10多分鐘的編譯時間。而為了壓縮這可觀的時間赫冬,順藤摸瓜找到了在修改為TS寫法之前的最后一個版本浓镜,這個版本是用JS寫的,可以修改后直接在瀏覽器中預(yù)覽效果劲厌。最大的好處在于可以實(shí)時的調(diào)試代碼了膛薛,這對了解frontend項(xiàng)目的運(yùn)行原理大開方便之門。
有了以上條件還不夠脊僚,因?yàn)閒rontend項(xiàng)目不同于傳統(tǒng)的前端項(xiàng)目相叁,它沒有構(gòu)建的過程遵绰,龐大的項(xiàng)目全是依靠配置文件動態(tài)加載生成的。
經(jīng)過一段時間的摸索和大量的調(diào)試增淹,找到了frontend從啟動到最終渲染一個TAB的完整過程椿访。知道了它是怎么加載的,那增加一個TAB也是板上釘釘?shù)氖虑榱恕?/p>
在frontend中增加一個TAB的關(guān)鍵代碼一覽:
但問題到此就解決了虑润?不不不成玫,還早著呢。完成以上步驟僅僅是有了一個TAB拳喻,但它里面是空的哭当,什么都沒有,那怎么往里面添加內(nèi)容呢冗澈?
下面這段代碼是調(diào)試面板Element的初始化代碼(一部分):
Emmm钦勘,怎么說呢,和我們一般見到的形式完全不同亚亲,既不是原生DOM操作彻采,也不是JQuery、Vue這類的第三方框架捌归,這怎么下手呢肛响?
原來frontend封裝了大量的組件,上面代碼中的ElementPanel所繼承的UI.Panel.Panel就是一個組件惜索。最開始我嘗試使用這些組件特笋,但由于沒有文檔,加上代碼量龐大巾兆,用起來非常的吃力猎物,效果也不好。最終通過代碼閱讀找到了這些組件暴露在外的element臼寄,那么我將Vue掛載到這個Element上就可以使用vue的方式去實(shí)現(xiàn)這個TAB的內(nèi)容了霸奕。如圖所示:
問題3
因?yàn)閒rontend是基于websocket與外界通信的溜宽,element吉拳、console、source這些模塊都是通過內(nèi)置的websocket client與外界交換數(shù)據(jù)适揉。而這個websocket實(shí)例被高度封裝留攒,很難在Vue中直接使用。例如嫉嘀,Element是通過這種方式去獲取DOM數(shù)據(jù)的:
注意這里的invoke_getDocument方法是動態(tài)合成的:
這里不展開展示細(xì)節(jié)了炼邀。總之如果按照frontend的方式實(shí)現(xiàn)通信的過程改造難度非常大剪侮。這時我想另辟蹊徑拭宁,自建一條通信通道洛退。但后來想想又放棄了,這不是個好的辦法杰标。最終還是決定從內(nèi)置的websocket上入手兵怯,看看哪些關(guān)鍵的地方可以暴露給全局使用。最終經(jīng)過不斷的調(diào)試找到了這個關(guān)鍵的對象:
這樣一來腔剂,我便可以隨便使用了:
export function sendMessage(method, params) {
return new Promise((resolve, reject) => {
// self.target為通信關(guān)鍵對象
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這個對象可以保證請求與回調(diào)完全一一對應(yīng)媒区,不出錯,不混掸犬,不亂袜漩。這為我后來實(shí)現(xiàn)主動監(jiān)聽邏輯層回調(diào)提供了實(shí)現(xiàn)思路。
Final
*小程序的調(diào)試技術(shù)從0到1一共經(jīng)歷了3個版本的演化才達(dá)到了一個完善的狀態(tài)湾碎,雖然演化的過程中被不斷推翻之前的方案宙攻,但帶來的結(jié)果終究是完美的。這是一個必然的過程介褥,因?yàn)椴徊瓤硬恢揽拥拇嬖凇?/strong>
小程序調(diào)試這塊對我來說最大的挑戰(zhàn)在于:每一步幾乎都在摸索粘优。假設(shè)、實(shí)現(xiàn)呻顽、驗(yàn)證無限循環(huán)雹顺,不斷完善。
最后貼一下第三版基本調(diào)試能力實(shí)現(xiàn)全圖:
數(shù)據(jù)審查面板:
Source面板:
Console控制臺:
DOM審查面板:
調(diào)試中斷:
Network網(wǎng)絡(luò)資源審查:
Network XHR審查:
好廊遍,導(dǎo)讀文章就到這里嬉愧。接下來會分幾篇文章詳細(xì)介紹第三版的完整實(shí)現(xiàn)。