開發人員工具架構更新:開發人員工具中的 CSS 基礎架構翻新
這篇文章是一系列網誌文章的一部分,說明我們對 DevTools 架構所做的變更,以及如何建構這項工具。我們將說明 CSS 在 DevTools 中的歷史運作方式,以及我們如何在 DevTools 中將 CSS 改為新式版本,以便 (最終) 遷移至網頁標準解決方案,在 JavaScript 檔案中載入 CSS。
開發人員工具中的 CSS 先前狀態
開發人員工具採用兩種方式實作 CSS:一種是用於開發人員工具的舊版部分,另一種是用於開發人員工具中使用的新式網路元件。
開發人員工具中的 CSS 實作方式是在多年前定義,現在已過時。開發人員工具一直使用 module.json
模式,因此我們花了許多心力移除這些檔案。移除這些檔案的最後一個阻礙因素是 resources
區段,用於載入 CSS 檔案。
我們想花時間探索各種潛在解決方案,最終轉變為 CSS 模組指令碼。目的是清除舊版系統造成的技術負債,並簡化遷移至 CSS 模組指令碼的程序。
任何在 DevTools 中的 CSS 檔案都會視為「舊版」,因為這些檔案是使用 module.json
檔案載入,而 module.json
檔案目前正在移除中。所有 CSS 檔案都必須列在與 CSS 檔案位於相同目錄中的 module.json
檔案的 resources
底下。
其餘 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 也意味著需要進行兩次 linting 作業:一個是 CSS 檔案,另一個是內嵌 CSS。如果所有 CSS 都以獨立的 CSS 檔案編寫,我們就能移除這項效能開銷。
- 壓縮時遇到的挑戰。內嵌 CSS 無法輕易壓縮,因此未壓縮 CSS。開發人員工具發布子版本的檔案大小也因相同網頁元件的多個例項所導入的重複 CSS 而增加。
實習專案的目標是為 CSS 基礎架構找出解決方案,讓這個架構能同時支援舊版基礎架構和 DevTools 中使用的新網頁元件。
研究潛在解決方案
問題可能分為兩個部分:
- 瞭解建構系統如何處理 CSS 檔案。
- 瞭解 CSS 檔案如何匯入及由開發人員工具使用。
我們已針對各部分的不同潛在解決方案進行研究,並在下方列出這些解決方案。
匯入 CSS 檔案
在 TypeScript 檔案中匯入及使用 CSS 的目的,是為了盡可能遵循網路標準,在 DevTools 中確保一致性,並避免 HTML 中出現重複的 CSS。我們也希望能夠選取解決方案,讓我們能夠將變更遷移至新的網路平台標準,例如 CSS 模組指令碼。
因此,開發人員工具中的 @import 陳述式和 標記並不適合。這些內容與開發人員工具中其他部分的匯入內容不一致,因此會導致閃現未格式化的內容 (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>
的潛在解決方案。
我們改為選擇以 CSSStyleSheet
物件的方式匯入 CSS 檔案,以便使用其 adoptedStyleSheets
屬性將檔案新增至 Shadow Dom (DevTools 已使用 Shadow DOM 好幾年了)。
Bundler 選項
我們需要一種方法,將 CSS 檔案轉換為 CSSStyleSheet
物件,以便在 TypeScript 檔案中輕鬆操作。我們考慮使用 Rollup 和 webpack 做為潛在的套件處理器,以便執行這項轉換作業。開發人員工具已在正式版本中採用 Rollup,不過在與目前的建構系統搭配使用時,無論是將 Bundler 新增至正式版群組,都有可能導致效能問題。我們整合了 Chromium 的 GN 建構系統,因此整合作業變得更加困難,因此整合器通常無法與目前的 Chromium 建構系統順利整合。
我們改為探索使用目前 GN 建構系統的選項,以便執行這項轉換作業。
在開發人員工具中使用 CSS 的新基礎架構
新解決方案包括使用 adoptedStyleSheets
為特定 Shadow DOM 新增樣式,同時使用 GN 建構系統產生可由 document
或 ShadowRoot
採用的 CSSStyleSheet 物件。
// 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
檔案。為了在建構期間產生 CSS 檔案,我們編寫了 generate_css_js_files.js
指令碼。建構系統現在會處理每個 CSS 檔案,並將其轉換為預設會匯出 CSSStyleSheet
物件的 JavaScript 檔案。這很棒,因為我們可以輕鬆匯入 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
的舊用法則需要更多步驟。註冊舊版樣式的兩個主要函式為 registerRequiredCSS
和 createShadowRootWithCoreStyles
。我們認為,由於遷移這些呼叫的步驟相當機械化,因此可以使用 ESLint 規則套用修正,並自動遷移舊版程式碼。開發人員工具已使用許多專屬於開發人員工具程式碼集的自訂規則。這很有幫助,因為 ESLint 已將程式碼剖析成抽象語法樹(簡稱 AST),並且可以查詢某些呼叫註冊 CSS 的呼叫節點。
編寫遷移 ESLint 規則時,遇到的最大問題是擷取各種極端案件。我們希望確保在瞭解哪些極端情況值得擷取,以及哪些情況應手動遷移之間取得平衡。我們也想確保,在建構系統未自動產生匯入的 .css.js
檔案時,我們可以通知使用者,以免在執行階段發生任何檔案未找到的錯誤。
使用 ESLint 規則進行遷移作業的一個缺點,是我們無法變更系統中必要的 GN 建構檔案。使用者必須在每個目錄中手動進行這些變更。雖然這需要進行更多工作,但可以確認每個匯入的 .css.js
檔案是否確實由建構系統產生。
整體而言,使用 ESLint 規則進行遷移非常實用,因為我們可以快速將舊程式碼遷移至新基礎架構,而且 AST 隨時可用,因此我們也可以處理規則中的多個極端案例,並使用 ESLint 的修正程式 API 自動可靠地修正這些案例。
接下來該怎麼做?
目前為止,Chromium 開發人員工具中的所有網頁元件都已遷移至使用新的 CSS 基礎結構,而非使用內嵌樣式。大部分的舊版 registerRequiredCSS
使用方式也已改用新系統。接下來,您只需盡可能移除 module.json
檔案,然後將目前的基礎架構遷移至日後要導入 CSS 模組指令碼的環境即可!
下載預覽管道
建議您使用 Chrome Canary、Dev 或 Beta 版做為預設的開發瀏覽器。這些預覽管道可讓您存取最新的 DevTools 功能,測試最新的網路平台 API,並在使用者發現問題前,協助您找出網站的問題!
與 Chrome 開發人員工具團隊聯絡
請使用下列選項討論新功能、更新或任何與開發人員工具相關的內容。
- 請前往 crbug.com 提交意見回饋和功能要求。
- 在開發人員工具中,依序按一下「更多選項」>「說明」>「回報開發人員工具的問題」,然後使用 回報開發人員工具的問題。
- 在 Twitter 上傳送訊息給 @ChromeDevTools。
- 在 YouTube 影片「What's new in DevTools」或「DevTools 提示」YouTube 影片中留言。