超越規則運算式:強化 Chrome 開發人員工具中的 CSS 值剖析結果

Philip Pfaffe
Ergün Erdogmus
Ergün Erdogmus

你是否注意到 Chrome 開發人員工具「Styles」分頁中的 CSS 屬性最近看起來更精緻了?這些更新是在 Chrome 121 和 128 之間推出的更新,是我們在剖析及呈現 CSS 值的方式上有明顯改善。本文將詳細說明這項轉換作業的技術細節,包括從規則運算式比對系統轉換為更強大的剖析器。

讓我們比較目前的開發人員工具與先前版本:

頂端:最新版 Chrome,底端:Chrome 121。

差異相當明顯,對吧?以下是主要強化功能的詳細說明:

  • color-mix:方便的預覽畫面,可視覺化呈現 color-mix 函式中的兩個顏色引數。
  • pink:命名為 pink 的顏色顏色預覽,可供點選。點選圖示即可開啟顏色挑選器,輕鬆調整設定。
  • var(--undefined, [fallback value]):改善未定義變數的處理方式,未定義變數會顯示為灰色,並顯示可點選的顏色預覽畫面,顯示目前的備用值 (在本例中為 HSL 顏色)。
  • hsl(…):另一個可點選的 hsl 顏色函式預覽畫面,可快速存取顏色挑選器。
  • 177deg:可點選的角度時鐘,可讓您以互動方式拖曳及修改角度值。
  • var(--saturation, …):指向自訂屬性定義的可點按連結,可輕鬆跳至相關宣告。

差異相當明顯。為達成這項目標,我們必須教導 DevTools 更深入瞭解 CSS 屬性值。

我們已不再提供這些預覽畫面嗎?

雖然這些預覽圖示看起來很熟悉,但並非一律會顯示,尤其是在複雜的 CSS 語法 (如上述範例) 中。即使這些指令確實發揮作用,通常需要花費大量心力,才能正常運作。

這是因為自 DevTools 推出以來,用於分析值的系統一直在自然成長。不過,CSS 近期推出了許多令人驚豔的新功能,並相應增加了語言的複雜度,因此 CSS 已無法滿足需求。為了因應系統的演進,我們必須進行全面性的重新設計,而這正是我們所做的!

CSS 屬性值的處理方式

在開發人員工具中,「Styles」分頁中轉譯和修飾屬性宣告的程序分為兩個階段:

  1. 結構分析。這個初始階段會剖析資源宣告,找出基礎元件及其關係。舉例來說,在宣告 border: 1px solid red 時,系統會將 1px 視為長度、solid 視為字串,而 red 視為顏色。
  2. 轉譯中。在結構分析的基礎上,轉譯階段會將這些元件轉換為 HTML 表示法。透過互動元素和視覺提示,讓顯示的屬性文字更豐富。舉例來說,顏色值 red 會以可點選的顏色圖示呈現,點選後會顯示顏色挑選器,方便修改。

規則運算式

我們先前是使用規則運算式 (regex) 來分析屬性值,以便進行結構分析。我們會維護一份規則運算式清單,確保符合我們考慮裝飾的屬性值位元。舉例來說,我們有幾個運算式與 CSS 顏色、長度、角度相符,以及更複雜的子運算式,例如 var 函式呼叫等等。為了進行值分析,我們從左到右掃描文字,持續從清單中尋找與下一段文字相符的第一個運算式。

雖然這在大多數情況下運作良好,但未能正常運作的案例數量持續增加。多年下來,我們收到許多錯誤報告,指出比對結果不正確。在修正這些問題的過程中,我們發現有些修正很簡單,有些則相當複雜,因此必須重新思考如何避免技術債。讓我們來看看一些問題!

須符合 color-mix()

我們為 color-mix() 函式使用的規則運算式為

/color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g

符合其語法:

color-mix(<color-interpolation-method>, [<color> && <percentage [0,100]>?]#{2})

請嘗試執行下列範例,以便將相符項目顯示為視覺化資料。

const re = /color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g;

// it works - simpler example
const simpler = re.exec('color-mix(in srgb, pink, hsl(127deg 100% 50%))');
console.table(simpler.groups);

re.exec('');

// it doesn't work - complex example
const complex = re.exec('color-mix(in srgb, pink, var(--undefined, hsl(127deg var(--saturation, 100%) 50%)))');
console.table(complex.groups);

色彩混合函式的結果。

簡單的範例運作正常。不過,在更複雜的範例中,<firstColor> 比對的是 hsl(177deg var(--saturation<secondColor> 比對的是 100%) 50%)),這完全沒有意義。

我們知道這是個問題。畢竟 CSS 是一種正式語言,並非規則,因此我們已納入特殊處理程序,以處理更複雜的函式引數,例如 var 函式。不過,如第一張螢幕截圖所示,這項做法仍無法在所有情況下運作。

須符合 tan()

其中一個比較有趣的回報錯誤是關於三角函數 tan() 函式。我們用來比對顏色的規則運算式包含子運算式 \b[a-zA-Z]+\b(?!-),用於比對命名顏色,例如 red 關鍵字。接著,我們檢查比對的部分是否為命名顏色,結果 tan 也是命名顏色!因此,我們將 tan() 運算式誤解為顏色。

須符合 var()

讓我們來看另一個範例:var() 函式,其中備援函式包含其他 var() 參照:var(--non-existent, var(--margin-vertical))

var() 的規則運算式會與這個值相符。但這會在第一個右括號「停止」比對。因此,上述文字會比對為 var(--non-existent, var(--margin-vertical)。這是規則運算式比對的典型限制。需要配對括號的語言基本上不是正規語言。

改用 CSS 剖析器

使用規則運算式進行文字分析時 (因為所分析的語言並非正常內容),接下來就有一項標準做法:使用剖析器取得較廣泛的文法。如果是 CSS,代表剖析器適用於無情境語言的剖析器。事實上,這種剖析器系統已存在於 DevTools 程式碼庫中:CodeMirror 的 Lezer,這是 CodeMirror 中語法醒目顯示的基礎,您可以在「Sources」面板中找到這個編輯器。Lezer 的 CSS 剖析器可讓我們為 CSS 規則產生 (非抽象) 語法樹狀結構,並準備好供我們使用。勝利。

屬性值 `hsl(177deg var(--saturation, 100%) 50%)` 的語法樹狀結構。這是 Lezer 剖析器產生的結果簡化版本,只保留半形逗號和括號的純語法節點。

不過,我們發現無法直接從規則運算式比對方式,改為以剖析器為基礎的比對方式,因為這兩種方法的運作方向相反。當 DevTools 使用規則運算式比對值時,會從左至右掃描輸入內容,並重複嘗試從排序的模式清單中找出最早的配對項目。使用語法樹狀圖時,比對作業會由下而上開始,例如先分析呼叫的引數,再嘗試比對函式呼叫。可以想成是評估算術運算式,其中應先考量加上括號的運算式,再考量乘法運算子,再考量相加運算子。在這個架構中,規則運算式比對會對應從左到右評估算術運算式。我們真的不想從頭開始重寫整個比對系統:有 15 個不同的比對器和轉譯器組合,每個組合都有數千行程式碼,因此我們不太可能在單一里程碑中完成。

因此,我們提出了可讓我們逐步進行變更的解決方案,詳情請見下文。簡而言之,我們保留了兩階段方法,但在第一階段,我們會嘗試由下而上比對子運算式 (因此會中斷規則運算式流程),而在第二階段,我們會由上而下進行轉譯。在兩個階段中,我們可以使用現有的規則運算式比對器和算繪器,幾乎不需變更,因此可以逐一遷移。

階段 1:自下而上的比對

第一階段大致上只會執行封面上所述的內容。我們會依序從下往上瀏覽樹狀圖,並嘗試比對所造訪的每個語法樹狀圖節點中的子運算式。為了比對特定子運算式,比對器可以使用規則運算式,就像在現有系統中一樣。從 128 版開始,我們在某些情況下仍會這樣做,例如比對長度。或者,比對器可以分析以目前節點為根的子樹結構。這樣一來,就能同時偵測語法錯誤並記錄結構資訊。

以上述的語法樹狀結構範例為例:

階段 1:對語法樹狀結構進行自下而上的比對。

針對這個樹狀結構,比對器會依照以下順序套用:

  1. hsl(177degvar(--saturation, 100%) 50%):首先,我們會找出 hsl 函式呼叫的第一個引數,即色相角。我們會比對角度比對器,以便用角度圖示裝飾角度值。
  2. hsl(177degvar(--saturation, 100%)50%):其次,我們會使用 var 比對器找出 var 函式呼叫。針對這類呼叫,我們主要想執行兩項操作:
    • 查詢變數的宣告並計算其值,然後分別在變數名稱中加入連結和彈出式視窗,以便連結至這些項目。
    • 如果計算值是顏色,請使用顏色圖示裝飾呼叫。還有第三點,我們稍後會再說明。
  3. hsl(177deg var(--saturation, 100%) 50%):最後,我們會比對 hsl 函式的呼叫運算式,這樣就可以使用顏色圖示裝飾。

除了搜尋要修飾的子運算式,我們還會在比對程序中執行第二個功能。請注意,在步驟 2 中,我們提到會查詢變數名稱的運算值。實際上,我們更進一步,將結果向上拉出樹狀結構。不僅是變數,備用值也一樣!在造訪 var 函式節點時,我們保證會預先造訪該函式節點的子項,因此我們已經知道備用值中可能出現的任何 var 函式的結果。因此,我們可以輕鬆且經濟實惠地隨時將 var 函式替換為其結果,這可讓我們輕鬆回答「這個 var 呼叫的結果是顏色嗎?」這類問題,就像我們在步驟 2 中所做的那樣。

階段 2:由上而下的算繪

在第二階段,我們會反轉方向。取得第 1 階段的比對結果後,我們將樹狀圖,從上至下週遊,以將樹狀圖轉譯成 HTML。對於每個造訪的節點,我們會檢查是否相符,如果相符,就呼叫比對器的對應轉譯器。我們為文字節點加入預設比對工具和轉譯器,避免需要為只含文字的節點 (例如 NumberLiteral「50%」) 進行特殊處理。轉譯器只會輸出 HTML 節點,這些節點會在組合後產生屬性值的表示法,包括裝飾。

第 2 階段:語法樹狀結構上由上往下轉譯。

以下是範例樹狀結構中屬性值的算繪順序:

  1. 請參閱 hsl 函式呼叫。兩者相符,因此請呼叫色彩函式算繪器。這個程式會執行以下兩項作業:
    • 針對任何 var 引數,使用即時替換機制計算實際顏色值,然後繪製顏色圖示。
    • 以遞迴方式轉譯 CallExpression 的子項。這會自動處理函式名稱、括號和半形逗號的算繪作業,因為這些都是文字。
  2. 造訪 hsl 呼叫的第一個引數。兩者相符,因此呼叫角度算繪器,繪製角度圖示和角度文字。
  3. 造訪第二個引數,也就是 var 呼叫。這項變數與條件相符,因此請呼叫 var renderer,輸出以下內容:
    • 開頭的文字:var(
    • 變數名稱會以變數定義的連結或灰色文字顏色加以裝飾,表示該變數未定義。此外,變數也會為變數加上彈出式視窗,顯示其值相關資訊。
    • 逗號會遞迴轉譯備用值。
    • 右括號。
  4. 請參閱 hsl 呼叫的最後一個引數。系統未找到相符項目,因此只會輸出文字內容。

您是否注意到,在這個演算法中,算繪會完全控制相符節點的子項如何算繪?遞迴轉譯子項是主動式操作。這個技巧可讓您逐步從以規則運算式為基礎的算繪,改為以語法樹狀結構為基礎的算繪。若是與舊版規則運算式比對器相符的節點,可在原始格式中使用對應的轉譯器。使用語法樹狀結構時,您必須負責轉譯整個子樹狀結構,使其結果 (HTML 節點) 可以整齊地插在周圍的轉譯程序中。這讓我們可以選擇以成對的方式移植比對器和轉譯器,並逐一替換。

控制相符節點子項轉譯作業的轉譯器另一個很酷的功能,是讓我們能夠推斷新增圖示之間的依附元件。在上述範例中,hsl 函式產生的顏色顯然取決於色調值。這表示顏色圖示所顯示的顏色取決於角度圖示所顯示的顏色。如果使用者透過該圖示開啟角度編輯器並修改角度,我們現在就能即時更新顏色圖示的顏色:

如上方範例所示,我們也將這個機制用於其他圖示配對,例如 color-mix() 及其兩個色彩管道,或是從備用項傳回顏色的 var 函式。

效能影響

為了改善穩定性並修正長期存在的問題,我們深入研究這個問題,並開始執行完整的剖析器,因此預期會發生一些效能倒退情形。為了進行測試,我們建立了可算繪約 3.500 個屬性宣告的基準測試,並在 M1 機器上剖析以規則運算式和剖析器為基礎的版本,且配置頻率達 6 倍。

就跟我們預期的一樣,這個以剖析為基礎的方法結果,比使用規則運算式的做法慢了 27%。以規則運算式為基礎的方法需要 11 秒才能完成轉譯,而以剖析器為基礎的方法則需要 15 秒才能完成轉譯。

考量到新做法帶來的優勢,我們決定繼續採用。

特別銘謝

我們最深的感激 邀請 Sofia Emelianova 和 Jecelyn Yeen 參加,因為他們對編輯這篇文章非常寶貴!

下載預覽頻道

建議您使用 Chrome CanaryDevBeta 版做為預設的開發瀏覽器。這些預覽管道可讓您存取最新的 DevTools 功能,測試最新的網路平台 API,並在使用者發現問題前,協助您找出網站的問題!

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

請使用下列選項討論新功能、更新或任何與開發人員工具相關的內容。