你是否注意到 Chrome 開發人員工具「Styles」分頁中的 CSS 屬性最近看起來更精緻了?這些更新是在 Chrome 121 和 128 之間推出的更新,是我們在剖析及呈現 CSS 值的方式上有明顯改善。本文將詳細說明這項轉換作業的技術細節,包括從規則運算式比對系統轉換為更強大的剖析器。
讓我們比較目前的開發人員工具與先前版本:
差異相當明顯,對吧?以下是主要強化功能的詳細說明:
color-mix
:方便的預覽畫面,可視覺化呈現color-mix
函式中的兩個顏色引數。pink
:命名為pink
的顏色顏色預覽,可供點選。點選圖示即可開啟顏色挑選器,輕鬆調整設定。var(--undefined, [fallback value])
:改善未定義變數的處理方式,未定義變數會顯示為灰色,並顯示可點選的顏色預覽畫面,顯示目前的備用值 (在本例中為 HSL 顏色)。hsl(…)
:另一個可點選的hsl
顏色函式預覽畫面,可快速存取顏色挑選器。177deg
:可點選的角度時鐘,可讓您以互動方式拖曳及修改角度值。var(--saturation, …)
:指向自訂屬性定義的可點按連結,可輕鬆跳至相關宣告。
差異相當明顯。為達成這項目標,我們必須教導 DevTools 更深入瞭解 CSS 屬性值。
我們已不再提供這些預覽畫面嗎?
雖然這些預覽圖示看起來很熟悉,但並非一律會顯示,尤其是在複雜的 CSS 語法 (如上述範例) 中。即使這些指令確實發揮作用,通常需要花費大量心力,才能正常運作。
這是因為自 DevTools 推出以來,用於分析值的系統一直在自然成長。不過,CSS 近期推出了許多令人驚豔的新功能,並相應增加了語言的複雜度,因此 CSS 已無法滿足需求。為了因應系統的演進,我們必須進行全面性的重新設計,而這正是我們所做的!
CSS 屬性值的處理方式
在開發人員工具中,「Styles」分頁中轉譯和修飾屬性宣告的程序分為兩個階段:
- 結構分析。這個初始階段會剖析資源宣告,找出基礎元件及其關係。舉例來說,在宣告
border: 1px solid red
時,系統會將1px
視為長度、solid
視為字串,而red
視為顏色。 - 轉譯中。在結構分析的基礎上,轉譯階段會將這些元件轉換為 HTML 表示法。透過互動元素和視覺提示,讓顯示的屬性文字更豐富。舉例來說,顏色值
red
會以可點選的顏色圖示呈現,點選後會顯示顏色挑選器,方便修改。
規則運算式
我們先前是使用規則運算式 (regex) 來分析屬性值,以便進行結構分析。我們會維護一份規則運算式清單,確保符合我們考慮裝飾的屬性值位元。舉例來說,我們有幾個運算式與 CSS 顏色、長度、角度相符,以及更複雜的子運算式,例如 var
函式呼叫等等。為了進行值分析,我們從左到右掃描文字,持續從清單中尋找與下一段文字相符的第一個運算式。
雖然這在大多數情況下運作良好,但未能正常運作的案例數量持續增加。多年下來,我們收到許多錯誤報告,指出比對結果不正確。在修正這些問題的過程中,我們發現有些修正很簡單,有些則相當複雜,因此必須重新思考如何避免技術債。讓我們來看看一些問題!
須符合 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 規則產生 (非抽象) 語法樹狀結構,並準備好供我們使用。勝利。
不過,我們發現無法直接從規則運算式比對方式,改為以剖析器為基礎的比對方式,因為這兩種方法的運作方向相反。當 DevTools 使用規則運算式比對值時,會從左至右掃描輸入內容,並重複嘗試從排序的模式清單中找出最早的配對項目。使用語法樹狀圖時,比對作業會由下而上開始,例如先分析呼叫的引數,再嘗試比對函式呼叫。可以想成是評估算術運算式,其中應先考量加上括號的運算式,再考量乘法運算子,再考量相加運算子。在這個架構中,規則運算式比對會對應從左到右評估算術運算式。我們真的不想從頭開始重寫整個比對系統:有 15 個不同的比對器和轉譯器組合,每個組合都有數千行程式碼,因此我們不太可能在單一里程碑中完成。
因此,我們提出了可讓我們逐步進行變更的解決方案,詳情請見下文。簡而言之,我們保留了兩階段方法,但在第一階段,我們會嘗試由下而上比對子運算式 (因此會中斷規則運算式流程),而在第二階段,我們會由上而下進行轉譯。在兩個階段中,我們可以使用現有的規則運算式比對器和算繪器,幾乎不需變更,因此可以逐一遷移。
階段 1:自下而上的比對
第一階段大致上只會執行封面上所述的內容。我們會依序從下往上瀏覽樹狀圖,並嘗試比對所造訪的每個語法樹狀圖節點中的子運算式。為了比對特定子運算式,比對器可以使用規則運算式,就像在現有系統中一樣。從 128 版開始,我們在某些情況下仍會這樣做,例如比對長度。或者,比對器可以分析以目前節點為根的子樹結構。這樣一來,就能同時偵測語法錯誤並記錄結構資訊。
以上述的語法樹狀結構範例為例:
針對這個樹狀結構,比對器會依照以下順序套用:
hsl(
177deg
var(--saturation, 100%) 50%)
:首先,我們會找出hsl
函式呼叫的第一個引數,即色相角。我們會比對角度比對器,以便用角度圖示裝飾角度值。hsl(177deg
var(--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.500 個屬性宣告的基準測試,並在 M1 機器上剖析以規則運算式和剖析器為基礎的版本,且配置頻率達 6 倍。
就跟我們預期的一樣,這個以剖析為基礎的方法結果,比使用規則運算式的做法慢了 27%。以規則運算式為基礎的方法需要 11 秒才能完成轉譯,而以剖析器為基礎的方法則需要 15 秒才能完成轉譯。
考量到新做法帶來的優勢,我們決定繼續採用。
特別銘謝
我們最深的感激 邀請 Sofia Emelianova 和 Jecelyn Yeen 參加,因為他們對編輯這篇文章非常寶貴!
下載預覽頻道
建議您使用 Chrome Canary、Dev 或 Beta 版做為預設的開發瀏覽器。這些預覽管道可讓您存取最新的 DevTools 功能,測試最新的網路平台 API,並在使用者發現問題前,協助您找出網站的問題!
與 Chrome 開發人員工具團隊聯絡
請使用下列選項討論新功能、更新或任何與開發人員工具相關的內容。
- 請前往 crbug.com 提交意見回饋和功能要求。
- 在開發人員工具中,依序點選 和 [更多選項] > [說明] > [回報開發人員工具問題] 回報開發人員工具問題。
- 前往 @ChromeDevTools 張貼 Tweet。
- 歡迎留言分享開發人員工具 YouTube 新功能或開發人員工具秘訣 YouTube 影片。