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

改善偵錯體驗

在過去幾個月,Chrome 開發人員工具團隊與 Angular 團隊合作,推出 Chrome 開發人員工具中偵錯體驗的改善功能。兩個團隊的人員合作無間,並採取措施讓開發人員能從編寫角度對網頁應用程式進行偵錯和分析,並取得熟悉且相關的資訊。

本文將深入探討 Angular 和 Chrome 開發人員工具中,哪些變更可達成這項目標。雖然這些變更有些是透過 Angular 示範,但也可以套用至其他架構。Chrome 開發人員工具團隊鼓勵其他架構採用新的控制台 API 和原始碼對應擴充點,以便為使用者提供更優質的偵錯體驗。

忽略程式碼

使用 Chrome 開發人員工具偵錯應用程式時,作者通常只想查看自己的程式碼,而非底層架構或 node_modules 資料夾中隱藏的某些依附元件。

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

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

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

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 開發人員工具會根據預設在偵錯工具中顯示這些檔案:

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

您現在可以將另一項資訊加入來源對應圖,以便識別哪些來源是第一方或第三方程式碼:

{
  ...
  "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 開發人員工具中連結這些新功能。

Angular 的 x_google_ignoreList

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

這項變更是透過變更 angular-cli 來達成,方法是建立可鉤進 webpack 的 Compiler 模組的外掛程式

我們的工程師建立的 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)));

連結的堆疊追蹤

堆疊追蹤可回答「我如何來到這裡」的問題,但這通常是從機器的角度來看,不一定符合開發人員的觀點或他們對應用程式執行階段的心理模型。當某些作業排定在稍後以非同步方式執行時,這一點尤其適用:雖然瞭解這類作業的「根本原因」或排程方面仍可能很有幫助,但這類資訊並不會是非同步堆疊追蹤的一部分。

在使用標準瀏覽器排程原語 (例如 setTimeout) 時,V8 內部會提供機制,用於追蹤這類非同步工作。在這些情況下,系統會預設執行這項操作,因此開發人員可以檢查這些項目。但在較複雜的專案中,情況就沒那麼簡單,尤其是在使用具備更進階排程機制的架構時,例如執行區域追蹤、自訂工作佇列,或將更新分割成多個工作單元,並在一段時間內執行。

為解決這個問題,開發人員工具會在 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。

友善的呼叫框

建構專案時,架構通常會從各種範本語言產生程式碼,例如 Angular 或 JSX 範本,可將看起來像 HTML 的程式碼轉換為純 JavaScript,並最終在瀏覽器中執行。有時候,這類產生的函式會有不易使用的名稱,例如在壓縮後輸入單一字母名稱,或是模糊不清或陌生的名稱,即使名稱並非如此。

在 Angular 中,在堆疊追蹤中看到名稱為 AppComponent_Template_app_button_handleClick_1_listener 的呼叫框架並不罕見。

堆疊追蹤螢幕截圖,其中包含自動產生的函式名稱。

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

Angular 中的友善呼叫框

將 Angular 中的呼叫框重新命名需要持續進行。我們預期這些改善措施會逐漸推行。

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

在這個程式碼產生程序中,系統也會建立原始碼對照表。我們目前正在研究如何在原始對照圖的「名稱」欄位中加入函式名稱,並在產生的程式碼和原始程式碼之間的對應項目中參照這些名稱。

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

展望未來

使用 Angular 做為測試前景,驗證我們的成果,是相當棒的體驗。我們很樂意聆聽架構開發人員的想法,並針對這些延長使用期限提供意見

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