個案研究:使用開發人員工具改善 Angular 偵錯服務

提升偵錯體驗

過去幾個月來,Chrome 開發人員工具團隊與 Angular 團隊合作,改進 Chrome 開發人員工具的偵錯體驗。雙方團隊的員工都共同合作,並按授權觀點要求開發人員針對網頁應用程式偵錯及剖析:從來源語言和專案結構方面著手,讓使用者能夠存取他們熟悉且相關的資訊。

這篇文章將在幕後介紹為達成這個目標,必須對 Angular 和 Chrome 開發人員工具進行哪些變更。雖然部分變更是透過 Angular 示範,但也適用於其他架構。Chrome 開發人員工具團隊鼓勵其他架構採用新的主控台 API 和來源對應擴充點,這樣一來,也能為使用者提供更好的偵錯體驗。

忽略列出的程式碼

使用 Chrome 開發人員工具對應用程式進行偵錯時,作者通常只想要查看「程式碼」,而不想要查看位於 node_modules 資料夾中的架構或某些依附元件。

為此,開發人員工具團隊推出了名為 x_google_ignoreList來源地圖擴充功能。這項擴充功能可用於識別第三方來源,例如架構程式碼或 Bundler 產生的程式碼。當架構使用這個擴充功能時,作者現在可以自動避免使用不想查看或逐步執行的程式碼,無須事先手動設定

實際操作時,Chrome 開發人員工具可以自動隱藏堆疊追蹤、來源樹狀結構、快速開啟對話方塊這類識別的程式碼,同時改善偵錯工具中的步驟和繼續行為。

顯示開發人員工具前後的 GIF 動畫。請注意,之後圖片中的開發人員工具會在樹狀結構中顯示已編寫的程式碼,不再建議「快速開啟」選單的任何架構檔案,並在右側顯示更簡潔的堆疊追蹤。

x_google_ignoreList 來源對應擴充功能

在來源對應中,新的 x_google_ignoreList 欄位參照了 sources 陣列,並列出該來源對應中所有已知第三方來源的索引。剖析來源對應時,Chrome 開發人員工具會使用這項資訊,判斷哪些程式碼部分應忽略。

以下是產生的檔案 out.js 的來源對應。有兩個原始 sources 導致產生輸出檔案:foo.jslib.js。前者是由網站開發人員撰寫,後者則是他們使用的架構。

{
  "version" : 3,
  "file": "out.js",
  "sourceRoot": "",
  "sources": ["foo.js", "lib.js"],
  "sourcesContent": ["...", "..."],
  "names": ["src", "maps", "are", "fun"],
  "mappings": "A,AAAB;;ABCDE;"
}

以下兩個來源都已包含 sourcesContent,而 Chrome 開發人員工具預設會在 Debugger 中顯示這些檔案:

  • 儲存為來源樹狀結構中的檔案。
  • 開啟「快速開啟」對話方塊時。
  • 在中斷點暫停和執行步驟時,錯誤堆疊追蹤中含有對應的呼叫框架位置。

現在您可以在來源地圖中加入額外的資訊,以識別第一個或第三方程式碼的來源:

{
  ...
  "sources": ["foo.js", "lib.js"],
  "x_google_ignoreList": [1],
  ...
}

新的 x_google_ignoreList 欄位包含參照 sources 陣列的單一索引:1. 這會指定對應到 lib.js 的區域其實是應自動加進忽略清單的第三方程式碼。

在更複雜的範例中 (如下所示),索引 2、4 和 5 指明區域對應到 lib1.tslib2.coffeehmr.js 都是應自動加進忽略清單的所有第三方程式碼。

{
  ...
  "sources": ["foo.html", "bar.css", "lib1.ts", "baz.js", "lib2.coffee", "hmr.js"],
  "x_google_ignoreList": [2, 4, 5],
  ...
}

如果您是架構或套件開發人員,請務必在建構過程中產生的來源對應包含這個欄位,以便使用 Chrome 開發人員工具中的這些新功能。

角度:x_google_ignoreList (Angular)

Angular v14.1.0 起,node_moduleswebpack 資料夾的內容已標示為「忽略」

透過建立可掛入 Webpack Compiler 模組的外掛程式angular-cli 中的變更即可達成這項目的。

我們的工程師建立 Webpack 外掛程式 這個外掛程式,會在 PROCESS_ASSETS_STAGE_DEV_TOOLING 階段中填入 x_google_ignoreList 欄位,以供 Webpack 產生並瀏覽器載入的最終素材資源取得。

const map = JSON.parse(mapContent) as SourceMap;
const ignoreList = [];

for (const [index, path] of map.sources.entries()) {
  if (path.includes('/node_modules/') || path.startsWith('webpack/')) {
    ignoreList.push(index);
  }
}

map[`x_google_ignoreList`] = ignoreList;
compilation.updateAsset(name, new RawSource(JSON.stringify(map)));

連結的堆疊追蹤

堆疊追蹤可用來回答「我怎麼走到這裡」的問題,但從機器的角度來看,這不一定是符合開發人員的觀點或應用程式執行階段的心理模型。當某些作業排定在稍後以非同步方式進行時更是如此:可能還是需要瞭解這類作業的「根本原因」或排程側的排程,但這其實不是非同步堆疊追蹤的一部分。

使用標準瀏覽器排程基元時,內部的 V8 內部有一種機制,可追蹤這類非同步工作,例如 setTimeout。系統會預設在這類情況下完成這項操作,因此開發人員可以檢查這些圖片!但對於較複雜的專案來說,情況並不單純,尤其是使用具備更進階排程機制的架構時,例如執行區域追蹤、自訂工作佇列,或者將更新分割成數個長時間執行的工作單元。

為解決此問題,開發人員工具會在 console 物件上公開名為「Async Stack Tagging API」的機制,讓架構開發人員可以提示作業排程和執行這些作業的位置。

Async Stack Tagging API

如未與非同步堆疊標記共用,針對以複雜方式以複雜方式執行程式碼的堆疊追蹤,就會顯示堆疊追蹤,但這類程式碼不會連結到已排定排程的程式碼。

部分非同步執行程式碼的堆疊追蹤,但未提供排程時間相關資訊。系統只會顯示從「requestAnimationFrame」開始的堆疊追蹤,但不包括排定時間以來的資訊。

透過非同步堆疊標記,就能提供這個背景資訊,而堆疊追蹤則如下所示:

部分非同步執行程式碼的堆疊追蹤,其中包含排定時間的相關資訊。請注意,與先前不同,系統會在堆疊追蹤中加入「businessLogic」和 `schedule`。

為此,請使用 Async Stack Tagging API 提供的名為 console.createTask() 的新 console 方法。其簽章如下:

interface Console {
  createTask(name: string): Task;
}

interface Task {
  run<T>(f: () => T): T;
}

叫用 console.createTask() 會傳回 Task 例項,您之後可以用它執行非同步程式碼。

// Task Creation
const task = console.createTask(name);

// Task Execution
task.run(f);

非同步作業也可以建立巢狀結構,「根本原因」會依序顯示在堆疊追蹤中。

工作可以執行不限次數,而且每次執行的工作酬載可能不同。系統會記住排程網站上的呼叫堆疊,直到工作物件完成垃圾收集為止。

Angular 中的 Async Stack Tagging API

在 Angular 中,我們已對 NgZone 做出變更;Angular 的執行內容會在所有非同步工作中持續存在。

排定工作時,如果可用,則會使用 console.createTask()。產生的 Task 例項會儲存起來,以供日後使用。叫用工作後,NgZone 會使用儲存的 Task 執行個體來執行該工作。

這些變更已透過提取要求 #46693#46958,發布到 Angular 的 NgZone 0.11.8 版本。

友善通話頁框

建置專案時,架構通常會根據各種範本語言產生程式碼,例如將 HTML 樣式程式碼轉換為純 JavaScript 的 Angular 或 JSX 範本,這些範本最終會在瀏覽器中執行。有時候,這類產生的函式的名稱可能是經過壓縮後的單一字母名稱,或者可能無法辨識或陌生的名稱,但實際上並非如此。

在 Angular 中,堆疊追蹤中會顯示名稱為 AppComponent_Template_app_button_handleClick_1_listener 的呼叫框架。

含有自動產生的函式名稱的堆疊追蹤螢幕截圖。

為解決這個問題,Chrome 開發人員工具現已支援透過來源對應重新命名這些函式。如果來源對應有一個函式範圍起始名稱項目 (也就是參數清單的左側參數),呼叫框架應在堆疊追蹤中顯示該名稱。

Angular 中的友善呼叫框

在 Angular 中重新命名呼叫框架目前仍在持續進行。我們希望之後能逐步改善這些改善措施。

剖析作者撰寫的 HTML 範本時,Angular 編譯器會產生 TypeScript 程式碼,最後轉換成瀏覽器載入並執行的 JavaScript 程式碼。

在此程式碼產生過程中,也會建立來源對應。我們目前正在研究如何將函式名稱納入來源地圖的「名稱」欄位,並在產生的程式碼和原始程式碼之間的對應中參照這些名稱。

舉例來說,如果已產生事件事件監聽器的函式,且其名稱在壓縮期間會不實用或移除,來源對應現在可在「names」欄位中,加入這個函式中更易記的名稱,而函式範圍開頭的對應現在可指稱這個名稱 (也就是參數清單的左括號)。Chrome 開發人員工具隨後會使用這些名稱,重新命名堆疊追蹤中的呼叫框架。

展望未來

以 Angular 進行前測,驗證我們的工作成果,是很美好的經驗。我們很樂意聆聽架構開發人員的想法,並針對這些延長期限提供意見

但我們也想探索更多領域。特別是如何改善開發人員工具中的剖析體驗。