您是否注意到 Chrome 開發人員工具「樣式」分頁中的 CSS 屬性最近看起來更精緻了?這些更新在 Chrome 121 到 128 之間推出,大幅改善了我們剖析及呈現 CSS 值的方式。本文將逐步說明這項轉換的技術細節,也就是從規則運算式比對系統,轉換為更強大的剖析器。
讓我們比較目前的開發人員工具與舊版:

這落差可真大!以下詳細說明主要強化功能:
color-mix:方便的預覽畫面,以視覺化方式呈現color-mix函式中的兩個顏色引數。pink:可點選的顏色預覽,代表具名顏色pink。點選即可開啟顏色挑選器,輕鬆調整顏色。var(--undefined, [fallback value]):改善未定義變數的處理方式,未定義變數會顯示為灰色,而有效的備用值 (在本例中為 HSL 顏色) 則會顯示可點選的顏色預覽。hsl(…):hsl顏色函式的另一個可點選顏色預覽,可快速存取顏色挑選器。177deg:可點選的角度時鐘,可讓您以互動方式拖曳及修改角度值。var(--saturation, …):自訂屬性定義的可點選連結,方便您跳至相關宣告。
兩者差異顯著。為此,我們必須讓開發人員工具更瞭解 CSS 屬性值。
這些預先發布版不是已經推出了嗎?
雖然這些預覽圖示看起來很熟悉,但並非總是能一致顯示,尤其是在複雜的 CSS 語法中,如上例所示。即使能運作,也往往需要耗費大量精力才能確保正常運作。
這是因為自開發人員工具推出以來,分析值的系統就一直在自然成長。不過,它無法跟上 CSS 最近推出的驚人新功能,以及相應增加的語言複雜度。為了跟上發展腳步,系統必須全面重新設計,而我們也確實做到了!
CSS 屬性值的處理方式
在開發人員工具中,轉譯及裝飾「Styles」分頁中屬性宣告的程序分為兩個階段:
- 結構分析。這個初始階段會剖析屬性宣告,找出基礎元件及其關係。舉例來說,在宣告
border: 1px solid red中,系統會將1px視為長度、solid視為字串,red視為顏色。 - 轉譯。在結構分析的基礎上,轉譯階段會將這些元件轉換為 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 中語法醒目顯示功能的基礎,也就是「來源」面板中的編輯器。Lezer 的 CSS 剖析器可為 CSS 規則產生 (非抽象) 語法樹狀結構,可供我們使用。勝利。

不過,我們發現無法直接從以規則運算式為基礎的比對,遷移至以剖析器為基礎的比對,因為這兩種方法是從相反的方向運作。使用規則運算式比對部分值時,開發人員工具會從左到右掃描輸入內容,反覆嘗試從已排序的清單中找出最早的相符項目。使用語法樹狀結構時,比對作業會從底部開始,例如先分析呼叫的引數,再嘗試比對函式呼叫。這就像評估算術運算式,您會先考慮括號運算式,然後是乘法運算子,最後是加法運算子。在這個架構中,以規則運算式為基礎的比對,相當於從左到右評估算術運算式。我們真的不想從頭重寫整個比對系統:有 15 個不同的比對器和轉譯器組合,其中包含數千行程式碼,因此我們不太可能在單一里程碑中推出。
因此,我們想出一個解決方案,可以逐步進行變更,詳情請見下文。簡而言之,我們保留了兩階段方法,但在第一階段,我們嘗試由下而上比對子運算式 (因此打破了 regex 流程),在第二階段,我們由上而下算繪。在這兩個階段中,我們可以使用現有的規則運算式比對器和轉譯器,幾乎不需變更,因此可以逐一遷移。
第 1 階段:由下而上比對
第一階段基本上只會執行上述作業。我們會由下而上遍歷樹狀結構,並嘗試在每個造訪的語法樹狀結構節點比對子運算式。如要比對特定子運算式,比對器可以使用規則運算式,就像在現有系統中一樣。在 128 版中,我們在少數情況下仍會使用,例如比對長度。或者,比對器可以分析以目前節點為根的子樹結構。這樣一來,就能同時找出語法錯誤並記錄結構資訊。
請參考上方的語法樹狀結構範例:

在這個樹狀結構中,比對器會按照下列順序套用:
hsl(177degvar(--saturation, 100%) 50%):首先,我們找出hsl函式呼叫的第一個引數,也就是色相角度。我們使用角度比對器進行比對,以便用角度圖示裝飾角度值。hsl(177degvar(--saturation, 100%)50%):第二,我們使用 var 比對器找出var函式呼叫。對於這類呼叫,我們主要想做兩件事:- 查詢變數的宣告並計算其值,然後在變數名稱中加入連結和彈出式視窗,分別連結至這些項目。
- 如果計算值是顏色,請使用顏色圖示裝飾呼叫。其實還有第三個,但我們稍後再談。
hsl(177deg var(--saturation, 100%) 50%):最後,我們比對hsl函式的呼叫運算式,以便用顏色圖示裝飾。
除了搜尋要裝飾的子運算式外,我們在比對過程中還會執行第二項功能。請注意,在步驟 2 中,我們提到要查詢變數名稱的計算值。事實上,我們更進一步,將結果向上傳播到樹狀結構。不只是變數,備用值也一樣!系統保證在造訪 var 函式節點時,其子節點已事先造訪,因此我們已知可能出現在備用值中的任何 var 函式結果。因此,我們能輕鬆且低成本地即時將 var 函式替換為其結果,這讓我們能輕鬆回答「這個 var 呼叫的結果是顏色嗎?」等問題,就像我們在步驟 #2 中所做的那樣。
階段 2:由上而下算繪
第二階段則反向。我們採用第 1 階段的比對結果,由上而下依序遍歷樹狀結構,將其轉譯為 HTML。對於每個造訪的節點,我們會檢查是否相符,如果是,則呼叫比對器的對應轉譯器。我們為文字節點提供預設比對器和轉譯器,因此不需要對僅包含文字的節點 (例如 NumberLiteral「50%」) 進行特殊處理。轉譯器只會輸出 HTML 節點,這些節點組合在一起後,就會產生屬性值的表示法,包括其裝飾。

在範例樹狀結構中,屬性值的算繪順序如下:
- 查看
hsl函式呼叫。兩者相符,因此呼叫顏色函式轉譯器。這會執行兩項作業:- 使用即時替換機制計算任何
var引數的實際顏色值,然後繪製顏色圖示。 - 遞迴算繪
CallExpression的子項。這會自動處理函式名稱、括號和半形逗號 (只是文字) 的轉譯作業。
- 使用即時替換機制計算任何
- 造訪
hsl呼叫的第一個引數。兩者相符,因此呼叫角度轉譯器,繪製角度圖示和角度文字。 - 造訪第二個引數,也就是
var呼叫。兩者相符,因此呼叫 var renderer,輸出內容如下:- 開頭的文字
var(。 - 變數名稱,並以變數定義的連結或灰色文字顏色裝飾,表示未定義。此外,也會在變數中加入彈出式視窗,顯示其值的相關資訊。
- 逗號,然後遞迴算繪備用值。
- 右括號。
- 開頭的文字
- 查看
hsl呼叫的最後一個引數。不相符,因此只輸出文字內容。
您是否注意到,在這個演算法中,轉譯器會完全控制相符節點的子項轉譯方式?遞迴算繪子項是主動式。這項技巧可讓您逐步從以規則運算式為基礎的轉譯,遷移至以語法樹狀結構為基礎的轉譯。如果節點與舊版規則運算式比對器相符,則可使用原始形式的對應轉譯器。以語法樹狀結構來說,它會負責轉譯整個子樹狀結構,而其結果 (HTML 節點) 可以完全插入周圍的轉譯程序。這樣一來,我們就能成對移植比對器和轉譯器,並逐一替換。
轉譯器控制相符節點子項的轉譯,還有另一個很棒的功能,就是可以推論要新增的圖示之間的依附關係。在上述範例中,hsl 函式產生的顏色顯然取決於其色相值。也就是說,顏色圖示顯示的顏色取決於角度圖示顯示的角度。如果使用者透過該圖示開啟角度編輯器並修改角度,我們現在可以即時更新顏色圖示的顏色:
如上例所示,我們也會將這項機制用於其他圖示組合,例如 color-mix() 及其兩個色彩通道,或從備用色彩傳回顏色的 var 函式。
效能影響
為提升穩定性並修正長期存在的問題,我們深入探討這個問題,並預期在開始執行完整的剖析器後,效能會有所下降。為了測試這項功能,我們建立了一個基準,可轉譯約 3.5k 個屬性宣告,並在 M1 機器上以 6 倍節流功能剖析規則運算式和剖析器版本。
正如我們預期,在這種情況下,剖析法的速度比規則運算式法慢 27%。規則運算式方法需要 11 秒才能算繪,剖析器方法則需要 15 秒。
考量到新做法帶來的效益,我們決定繼續採用。
特別銘謝
我們由衷感謝 Sofia Emelianova 和 Jecelyn Yeen 協助編輯這篇文章,他們的貢獻十分寶貴!
下載預覽管道
建議使用 Chrome Canary、開發人員版或 Beta 版做為預設開發瀏覽器。您可以透過這些預先發布版管道,使用最新的開發人員工具功能、測試先進的網路平台 API,並在使用者發現網站問題前,搶先找出問題!
聯絡 Chrome 開發人員工具團隊
您可以透過下列管道,討論新功能、更新或任何與開發人員工具相關的事項。
- 如要提供意見或提出功能要求,請前往 crbug.com。
- 如要回報開發人員工具問題,請在開發人員工具中依序點選 「More options」>「Help」>「Report a DevTools issue」。
- 在 Twitter 上發文並標記 @ChromeDevTools。
- 在開發人員工具新功能 YouTube 影片或開發人員工具提示 YouTube 影片留言。