在開發人員工具中翻新 CSS 基礎架構

開發人員工具架構更新:翻新開發人員工具中的 CSS 基礎架構

本文是一系列網誌文章之一,旨在說明開發人員工具架構的變動及建構方式。我們會說明 CSS 在開發人員工具中的過往運作方式,以及我們如何翻新開發人員工具中的 CSS,以便 (最終) 遷移至在 JavaScript 檔案中載入 CSS 的網路標準解決方案。

開發人員工具中的 CSS 先前的狀態

開發人員工具以兩種方式導入 CSS:一種用於開發人員工具中舊版部分的 CSS 檔案,一種用於開發人員工具中使用的新型網頁元件

開發人員工具中的 CSS 導入經過多年定義,現已過時。開發人員工具無法順利使用 module.json 模式,而且我們投入了大量心力移除這些檔案。移除這些檔案的最後一項攔截器是 resources 區段,用來載入 CSS 檔案。

我們想花一些時間探索各種可行的解決方案,這些解決方案最終可以變造為 CSS 模組指令碼。這麼做是為了消除舊版系統造成的技術債,同時讓 CSS Module Script 遷移程序更輕鬆。

凡是在開發人員工具中的 CSS 檔案,由於系統是透過 module.json 檔案載入,而正在移除中,因此系統會將這些檔案視為「舊版」。所有 CSS 檔案都必須列在 module.json 檔案的 resources 底下,與 CSS 檔案位於同一個目錄中。

其他 module.json 檔案的範例:

{
  "resources": [
    "serviceWorkersView.css",
    "serviceWorkerUpdateCycleView.css"
  ]
}

這些 CSS 檔案會填入名為 Root.Runtime.cachedResources 的全域物件對應,做為從路徑到其內容的路徑對應。如要在開發人員工具中加入樣式,您必須呼叫 registerRequiredCSS,且使用要載入的檔案的確切路徑。

registerRequiredCSS 呼叫範例:

constructor() {
  …
  this.registerRequiredCSS('ui/legacy/components/quick_open/filteredListWidget.css');
  …
}

這個方法會擷取 CSS 檔案的內容,並使用 appendStyle 函式將其以 <style> 元素的形式插入網頁。

appendStyle 函式,可使用內嵌樣式元素新增 CSS:

const content = Root.Runtime.cachedResources.get(cssFile) || '';

if (!content) {
  console.error(cssFile + ' not preloaded. Check module.json');
}

const styleElement = document.createElement('style');
styleElement.textContent = content;
node.appendChild(styleElement);

推出新型網頁元件時 (使用自訂元素),我們一開始就決定透過元件檔案中的內嵌 <style> 標記使用 CSS。這也面臨了各自面臨的挑戰:

  • 不支援語法醒目顯示功能。針對以內嵌 CSS 編寫語法的外掛程式,通常比以 .css 檔案編寫的 CSS 的語法醒目顯示和自動完成功能來得差。
  • 建構效能負擔。內嵌 CSS 也意味著需要兩張檢查程序才能進行程式碼檢查,一個用於 CSS 檔案,另一個用於內嵌 CSS。如果所有 CSS 都是以獨立的 CSS 檔案編寫,我們可能會移除不必要的效能負擔。
  • 壓縮中的難題。系統無法輕鬆壓縮內嵌 CSS,因此未壓縮任何 CSS。開發人員工具的發布子版本檔案大小,也會因同一個網頁元件的多個執行個體所導入的重複 CSS 而增加。

我的實習專案目標是為 CSS 基礎架構找到合適的解決方案,以便搭配舊版基礎架構和開發人員工具中使用的新網路元件。

研究可能的解決方案

問題可能分為兩部分:

  • 瞭解建構系統如何處理 CSS 檔案。
  • 瞭解開發人員工具如何匯入及使用 CSS 檔案。

我們研究了各部分的可能解決方案,以下將概略說明。

匯入 CSS 檔案

在 TypeScript 檔案中匯入及使用 CSS 的目標我們也希望找到合適的解決方案,將變更移轉到新的網路平台標準,例如 CSS 模組指令碼。

因此,@import 陳述式和 標記似乎不適用於開發人員工具。這些工具不會與其他開發人員工具中的匯入內容保持一致,進而產生未設定樣式內容的 Flash 檔案 (FOUC)。遷移至 CSS 模組指令碼會比較困難,因為匯入作業必須明確加入,處理方式和處理 <link> 標記的方式不同。

const output = LitHtml.html`
<style> @import "css/styles.css"; </style>
<button> Hello world </button>`
const output = LitHtml.html`
<link rel="stylesheet" href="styles.css">
<button> Hello World </button>`

@import<link> 可能的解決方案。

相反地,我們選擇找出一種方式,將 CSS 檔案匯入為 CSSStyleSheet 物件,以便使用其 adoptedStyleSheets 屬性,將 CSS 檔案匯入 Shadow Dom (開發人員工具現已使用 Shadow DOM 數年)。

Bundler 選項

我們需要可將 CSS 檔案轉換為 CSSStyleSheet 物件,以便在 TypeScript 檔案中處理。我們將 Rollupwebpack 視為潛在的組合來完成這項變革。開發人員工具已在正式環境中使用匯總功能,但如果在正式版本中新增組合器,在使用目前的建構系統時可能會發生效能問題。我們的與 Chromium 的 GN 建構系統整合會讓套裝組合更加困難,因此封裝器往往無法與目前的 Chromium 建構系統完美整合。

不過,我們改為探索可以選擇使用目前的 GN 建構系統來進行這項轉換作業。

在開發人員工具中使用 CSS 的新基礎架構

新的解決方案包括使用 adoptedStyleSheets 為特定 Shadow DOM 新增樣式,以及使用 GN 建構系統產生 CSSStyleSheet 物件可供 documentShadowRoot 採用。

// CustomButton.ts

// Import the CSS style sheet contents from a JS file generated from CSS
import customButtonStyles from './customButton.css.js';
import otherStyles from './otherStyles.css.js';

export class CustomButton extends HTMLElement{
  …
  connectedCallback(): void {
    // Add the styles to the shadow root scope
    this.shadow.adoptedStyleSheets = [customButtonStyles, otherStyles];
  }
}

使用 adoptedStyleSheets 有許多好處,包括:

  • 我們正努力成為現代網路標準
  • 避免 CSS 重複
  • 僅將樣式套用至 Shadow DOM,避免 CSS 檔案中類別名稱或 ID 選取器重複所造成的任何問題
  • 可輕鬆遷移至日後的網路標準,例如 CSS 模組指令碼和匯入斷言

要解決這個問題的唯一理由是,import 陳述式要求匯入 .css.js 檔案。為了讓 GN 在建構期間產生 CSS 檔案,我們編寫了 generate_css_js_files.js 指令碼。建構系統現在會處理每個 CSS 檔案,並將其轉換成 JavaScript 檔案,而這個檔案預設會匯出 CSSStyleSheet 物件。這對我們來說非常實用,因為我們可以匯入 CSS 檔案,而且很容易採用。此外,我們現在還可以輕鬆壓縮正式版本,節省檔案大小:

const styles = new CSSStyleSheet();
styles.replaceSync(
  // In production, we also minify our CSS styles
  /`${isDebug ? output : cleanCSS.minify(output).styles}
  /*# sourceURL=${fileName} */`/
);

export default styles;

範例從指令碼產生的 iconButton.css.js

使用 ESLint 規則遷移舊版程式碼

雖然網頁元件可以輕鬆手動遷移,但 registerRequiredCSS 舊版使用情形的遷移程序更加複雜。註冊舊版樣式的兩個主要函式是 registerRequiredCSScreateShadowRootWithCoreStyles。我們認定遷移這些呼叫的步驟相當具有機械性,因此可以使用 ESLint 規則套用修正程式,並自動遷移舊版程式碼。開發人員工具已使用一些開發人員工具程式碼集專屬的自訂規則。這種做法很有用,因為 ESLint 已經將程式碼剖析為抽象語法樹狀結構(abbr. AST),而我們能夠查詢用於註冊 CSS 的特定呼叫節點。

我們編寫遷移 ESLint 規則時遇到的最大問題,就是擷取極端案件。我們想確保在知道哪些極端案例值得擷取,以及應手動遷移的情況下,才能取得適當平衡。我們也想確保在建構系統未自動產生匯入的 .css.js 檔案時,可以向使用者表明,以免執行階段未發現任何檔案錯誤。

使用 ESLint 規則進行遷移的一項缺點是,我們無法變更系統中必要的 GN 建構檔案。這些變更必須由每個目錄中的使用者手動完成。雖然這項工作需要完成更多工作,但有助於確認每個匯入的 .css.js 檔案是否確實由建構系統產生。

整體而言,使用 ESLint 規則進行這項遷移作業,非常實用,因為我們可以將舊版程式碼快速遷移至新基礎架構,而且在 AST 隨時都可用,因此我們也可以在規則中處理多個極端情況,並使用 ESLint 的修正工具 API 穩定自動修正這些情況。

接下來要我為你做什麼呢?

目前 Chromium 開發人員工具中的所有網頁元件都已改用新的 CSS 基礎架構,而非內嵌樣式。大部分的 registerRequiredCSS 舊版使用情況也都已改為使用新版系統。最後,請盡可能移除最多 module.json 檔案,然後遷移這個目前的基礎架構,以便日後導入 CSS 模組指令碼!

下載預覽管道

考慮使用 Chrome Canary 版開發人員版Beta 版做為預設開發瀏覽器。這些預覽管道可讓您存取開發人員工具的最新功能、測試最先進的網路平台 API,並在使用者使用之前就在網站上發現問題!

與 Chrome 開發人員工具團隊聯絡

使用下列選項,討論文章的新功能和異動,以及其他與開發人員工具相關的事項。

  • 請透過 crbug.com 提交建議或意見回饋。
  • 如要回報開發人員工具的問題,請在開發人員工具中依序點選「更多選項」圖示 更多   >「說明」 >「回報開發人員工具的問題」
  • @ChromeDevTools 張貼推文。
  • 歡迎對開發人員工具的 YouTube 影片或開發人員工具秘訣 (YouTube 影片) 提供意見。