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

Philip Pfaffe
Ergün Erdogmus
Ergün Erdogmus

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

讓我們比較目前的 DevTools 與先前版本:

頂端:最新版 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.5k 個資源宣告,並在 M1 機器上以 6 倍的節流率,剖析以規則運算式和剖析器為基礎的版本。

如預期,在該情況下,以剖析為基礎的方法比以規則運算式為基礎的方法慢了 27%。以規則運算式為基礎的方法需要 11 秒才能算繪,以剖析器為基礎的方法則需要 15 秒才能算繪。

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

特別銘謝

在此深深感謝 Sofia Emelianova 和 Jecelyn Yeen 協助編輯這篇文章!

下載預覽管道

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

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

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