您是否注意到 Chrome 開發人員工具「Styles」分頁中的 CSS 屬性最近看起來更精緻了?這些更新是在 Chrome 121 至 128 之間推出,是因為我們大幅改善了剖析及呈現 CSS 值的方式。本文將詳細說明這項轉換作業的技術細節,包括從規則運算式比對系統轉換為更強大的剖析器。
讓我們比較目前的 DevTools 與先前版本:
差異相當明顯,對吧?以下是主要強化功能的詳細說明:
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.5k 個資源宣告,並在 M1 機器上以 6 倍的節流率,剖析以規則運算式和剖析器為基礎的版本。
如預期,在該情況下,以剖析為基礎的方法比以規則運算式為基礎的方法慢了 27%。以規則運算式為基礎的方法需要 11 秒才能算繪,以剖析器為基礎的方法則需要 15 秒才能算繪。
考量到新做法帶來的優勢,我們決定繼續採用。
特別銘謝
在此深深感謝 Sofia Emelianova 和 Jecelyn Yeen 協助編輯這篇文章!
下載預覽管道
建議您將 Chrome Canary、開發人員版或Beta 版設為預設開發人員版瀏覽器。這些預覽管道可讓您存取最新的 DevTools 功能,測試最新的網路平台 API,並在使用者發現問題前,協助您找出網站的問題!
與 Chrome 開發人員工具團隊聯絡
請使用下列選項討論新功能、更新或任何與開發人員工具相關的內容。
- 請前往 crbug.com 提交意見回饋和功能要求。
- 在開發人員工具中,依序按一下「more_vert」 更多選項 >「Help」 >「Report a DevTools issue」,即可回報開發人員工具的問題。
- 在 Twitter 上傳送訊息給 @ChromeDevTools。
- 在 YouTube 影片「What's new in DevTools」或「DevTools 提示」YouTube 影片中留言。