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

Philip Pfaffe
Ergün Erdogmus
Ergün Erdogmus

您是否注意到 Chrome 開發人員工具中的 CSS 屬性[樣式] 分頁最近看起來更精緻的?這些更新是在 Chrome 121 版和 128 版之間推出的更新,是我們在剖析及呈現 CSS 值的方式上大幅提升。本文將逐步說明這項轉換的相關技術細節,包括從規則運算式比對系統移往更強大的剖析器。

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

頂端:這是最新版 Chrome,位於底部:Chrome 121。

這個差異很大,對吧?以下詳細說明重要強化功能:

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

但差異相當顯著。為此,我們必須訓練開發人員工具瞭解 CSS 屬性值,比以往更好。

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

這些預覽圖示看起來可能很眼熟,但使用者無法一直看到這些圖示,尤其是在如上述範例這類複雜的 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 來說,這表示可剖析無情境語言的剖析器。事實上,這類剖析器系統已存在於開發人員工具程式碼集:CodeMirror 的 Lezer,這是其基礎,例如 CodeMirror 的語法醒目顯示 (位於「Sources」面板) 的編輯器。Lezer 的 CSS 剖析器讓我們為 CSS 規則產生 (非抽象) 語法樹狀結構,並可隨時使用。勝利手勢。

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

除此之外,我們發現無法直接從規則運算式比對改為剖析剖析器比對的方式,因為這兩種做法無法直接相反方向。將值與規則運算式比對時,開發人員工具會重複掃描從左到右的輸入內容,嘗試從已排序的模式清單中找出最早的相符項目。使用語法樹狀結構時,比對作業會從由下而開始 (例如先分析呼叫的引數),然後再嘗試比對函式呼叫。可以想成是評估算術運算式,其中應先考量加上括號的運算式,再考量乘法運算子,再考量相加運算子。在這個架構中,規則運算式比對會對應從左到右評估算術運算式。我們真的不想重寫整個相符的系統:比對器和轉譯器組合共有 15 個,內含數千行程式碼,這導致我們不太可能在單一里程碑中推出整個比對系統。

因此我們想出一種解決方案,方便我們進行漸進式調整,這在以下將詳細說明。簡單來說,我們保留了兩階段做法,但在第一階段嘗試比對子運算式由下而下 (這樣與規則運算式流程不同),第二階段則會由上而下呈現。在這兩個階段中,我們都可以使用現有的規則運算式比對器和轉譯功能 (幾乎不會改變),因此能夠逐一遷移。

階段 1:由下而上比對

第一階段的表現完全不同,完全取決於封面上說的內容。我們會從下往上掃遍樹狀圖,並嘗試比對每個造訪的每個語法樹狀結構節點中的子運算式。如要比對特定子運算式,比對器可以使用規則運算式,就像在現有系統中一樣。但自 128 版起,我們仍在少數情況下仍適用,例如比對長度相符。或者,比對器也可以分析目前節點根層級子樹狀結構的結構。這可讓系統在擷取語法錯誤時,同時記錄結構資訊。

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

第 1 階段:在語法樹狀結構上由下往上比對。

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

  1. hsl(177degvar(--saturation, 100%) 50%):首先,我們會找出 hsl 函式呼叫的第一個引數,也就是色角。我們會將這個值與角度比對器配對,這樣我們就可以用角圖示裝飾角度值。
  2. hsl(177degvar(--saturation, 100%)50%):第二,我們探索具有變數比對器的 var 函式呼叫。對於這類電話,我們主要要做兩件事:
    • 查詢變數宣告並計算其值,在變數名稱中加入連結和彈出式視窗,分別連結變數。
    • 如果運算的值是顏色,請使用顏色圖示來裝飾呼叫。 還有第三點,我們稍後會再說明。
  3. hsl(177deg var(--saturation, 100%) 50%):最後,我們會比對 hsl 函式的呼叫運算式,這樣就可以使用顏色圖示裝飾。

除了搜尋要裝飾的子運算式外,另一項比對過程中還有另一項功能。請注意,在步驟 2 中,我們提到我們是查詢已計算的變數名稱值。實際上,我們更進一步,將結果向上拉出樹狀結構。這不只適用於變數,也包括備用值!在造訪 var 函式節點時,我們保證會預先造訪該函式節點的子項,因此我們已經知道備用值中可能出現的任何 var 函式的結果。因此,我們可以輕鬆地以低廉的價格,即時將 var 函式替換為結果,讓我們能夠像步驟 2 一樣,輕鬆回答「這個 var 的結果是否呼叫顏色?」等問題。

階段 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 版做為預設的開發瀏覽器。透過這些預覽版本,您可以存取開發人員工具中的最新功能、測試最先進的網路平台 API,以及找出網站的問題,以免使用者發現問題。

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

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

  • 歡迎透過 crbug.com 提出建議或意見。
  • 使用「更多選項」更多 > 回報開發人員工具問題說明 >在開發人員工具中回報開發人員工具問題
  • 前往 @ChromeDevTools 張貼 Tweet。
  • 歡迎在「開發人員工具」推出「最新消息」YouTube 影片或「開發人員工具秘訣」YouTube 影片留言。