開發人員工具中的 CSS-in-JS 支援

魯登科 (Alex Rudenko)
Alex Rudenko

本文將介紹自 Chrome 85 版起,開始在開發人員工具中的 CSS-in-JS 支援,以及 CSS-in-JS 代表的意義,以及這項工具與開發人員工具長久以來支援的一般 CSS 有何不同。

什麼是 CSS-in-JS?

CSS-in-JS 的定義不夠明確。大致上來說,這是使用 JavaScript 管理 CSS 程式碼的方法。舉例來說,可能表示 CSS 內容是以 JavaScript 定義,且應用程式即時產生最終 CSS 輸出內容。

在開發人員工具中,CSS-in-JS 代表使用 CSSOM API 將 CSS 內容插入網頁中。一般的 CSS 是使用 <style><link> 元素插入,且具有靜態來源 (例如 DOM 節點或網路資源)。相較之下,CSS-in-JS 通常沒有靜態來源。這裡的特殊案例是,您可以使用 CSSOM API 更新 <style> 元素的內容,導致來源與實際的 CSS 樣式表不同步。

如果您使用任何 CSS-in-JS 程式庫 (例如 styled-componentEmotionJSS),程式庫可能會根據開發模式和瀏覽器,在背景使用 CSSOM API 插入樣式。

讓我們來看看一些範例,瞭解如何使用 CSSOM API 插入樣式表,做法和 CSS-in-JS 程式庫的運作方式類似。

// Insert new rule to an existing CSS stylesheet
const element = document.querySelector('style');
const stylesheet = element.sheet;
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

除此之外,您也可以建立全新的樣式表

// Create a completely new stylesheet
const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

// Apply constructed stylesheet to the document
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];

開發人員工具中的 CSS 支援

在開發人員工具中,處理 CSS 最常用的功能是「樣式」窗格。在「樣式」窗格中,您可以查看特定元素所套用的規則,以及編輯規則並即時檢視網頁上的變更。

去年,我們對於使用 CSSOM API 修改 CSS 規則的支援範圍有限:您只能查看已套用的規則,但無法編輯。我們去年的主要目標,是讓使用者利用「樣式」窗格,編輯 CSS 內部的 CSS 規則。有時我們也會將 CSS 內樣式稱為「建構式」,指出這些樣式是使用 Web API 所建構。

接下來詳細說明開發人員工具中的樣式編輯功能。

開發人員工具中的樣式編輯機制

開發人員工具中的樣式編輯機制

當您在開發人員工具中選取元素時,系統會顯示「Styles」窗格。Styles 窗格會發出名為 CSS.getMatchedStylesForNode 的 CDP 指令,以便取得套用至該元素的 CSS 規則。CDP 是 Chrome 開發人員工具通訊協定的一個 API,這個 API 可讓 DevTools 前端取得已檢查頁面的其他相關資訊。

叫用 CSS.getMatchedStylesForNode 時,會識別文件中的所有樣式表,並透過瀏覽器的 CSS 剖析器進行剖析。接著會建立索引,將每個 CSS 規則與樣式表來源中的位置建立關聯。

您可能會好奇,為何還需要再次剖析 CSS?這會導致問題是,瀏覽器本身就不受 CSS 規則的來源位置影響,因此不會儲存這些規則。不過,開發人員工具需要來源位置才能支援 CSS 編輯功能。我們不希望一般 Chrome 使用者支付效能降低,但我們希望開發人員工具使用者能存取來源位置。這種重新剖析方法以最少的缺點解決這兩種用途。

接著,CSS.getMatchedStylesForNode 實作會要求瀏覽器的樣式引擎提供與指定元素相符的 CSS 規則。最後,這個方法會將樣式引擎傳回的規則與原始碼建立關聯,並提供 CSS 規則的結構化回應,讓開發人員工具瞭解規則的哪個部分是選取器或屬性。可讓開發人員工具獨立編輯選取器和屬性。

接著來看看編輯功能別忘了,CSS.getMatchedStylesForNode 會傳回每項規則的來源位置。這對編輯來說非常重要。變更規則時,開發人員工具會發出另一個 CDP 指令,用於實際更新頁面。這個指令包含待更新規則片段的原始位置,以及片段需要更新的新文字。

在後端處理編輯呼叫時,開發人員工具會更新目標樣式表。也會更新負責維護的樣式表來源副本,並更新更新規則的來源位置。為了回應編輯呼叫,開發人員工具前端會傳回剛更新的文字片段的更新位置。

這就說明瞭為何在 DevTools 中編輯 CSS-in-JS 的功能無法立即運作:CSS-in-JS 未實際儲存任何來源,以及 CSS 規則位於瀏覽器的記憶體內 CSSOM 資料結構中

我們如何新增 CSS-in-JS 支援

因此,為了支援 CSS-in-JS 規則的編輯功能,我們決定最好的解決方法,是為建構的樣式表建立來源,並使用上述現有機制進行編輯。

第一步是建構來源文字。瀏覽器的樣式引擎會將 CSS 規則儲存在 CSSStyleSheet 類別中。該類別就是前面提到的,您可以透過 JavaScript 建立的執行個體。建構來源文字的程式碼如下:

String InspectorStyleSheet::CollectStyleSheetRules() {
  StringBuilder builder;
  for (unsigned i = 0; i < page_style_sheet_->length(); i++) {
    builder.Append(page_style_sheet_->item(i)->cssText());
    builder.Append('\n');
  }
  return builder.ToString();
}

它會疊代處理 CSSStyleSheet 例項中的規則,並從中建構單一字串。建立 InspectorStyleSheet 類別的例項時,就會叫用這個方法。InspectorStyleSheet 類別會包裝 CSSStyleSheet 執行個體,並擷取開發人員工具所需的其他中繼資料:

void InspectorStyleSheet::UpdateText() {
  String text;
  bool success = InspectorStyleSheetText(&text);
  if (!success)
    success = InlineStyleSheetText(&text);
  if (!success)
    success = ResourceStyleSheetText(&text);
  if (!success)
    success = CSSOMStyleSheetText(&text);
  if (success)
    InnerSetText(text, false);
}

在這個程式碼片段中,我們會看見 CSSOMStyleSheetText 會在內部呼叫 CollectStyleSheetRules。如果樣式表不是內嵌樣式或資源樣式表,系統會叫用 CSSOMStyleSheetText。基本上,這兩個程式碼片段已允許對使用 new CSSStyleSheet() 建構函式建立的樣式表進行基本編輯。

特殊情況是指與已使用 CSSOM API 變更的 <style> 標記相關聯的樣式表。在此情況下,樣式表包含來源中沒有的來源文字和額外規則。為處理這種情況,我們引進了可將這些額外規則合併至來源文字的方法。由於 CSS 規則可以插入原始來源文字的中間,所以順序非常重要。舉例來說,假設原始 <style> 元素含有下列文字:

/* comment */
.rule1 {}
.rule3 {}

然後,頁面使用 JS API 插入了部分新規則,並產生下列規則順序:.rule0、.rule1、.rule2、.rule3、.rule4。合併作業後產生的來源文字應如下所示:

.rule0 {}
/* comment */
.rule1 {}
.rule2 {}
.rule3 {}
.rule4 {}

由於規則的來源文字位置必須精確,所以編輯原始註解和縮排相當重要。

CSS-in-JS 樣式表的另一個特點,是網頁可隨時變更。如果實際的 CSSOM 規則與文字版本不同步,便無法進行編輯。為此,我們導入了一種稱作「探測器」,可讓瀏覽器在樣式表變更時通知開發人員工具的後端部分。接著,系統會在下次呼叫 CSS.getMatchedStylesForNode 時,同步處理修改過的樣式表。

完成上述所有設定後,CSS 內文字編輯功能即可正常運作,但我們還是想改善使用者介面,以表明是否已建構樣式表。我們已在 CDP 的 CSS.CSSStyleSheetHeader 中新增 isConstructed 屬性,以供前端用來正確顯示 CSS 規則的來源:

可建構的樣式表

結論

為回顧一下我們的經驗,我們探討了開發人員工具不支援的 CSS-in-JS 相關用途,並逐步探討開發這些用途的解決方案。這個實作過程有趣的部分在於,我們能夠讓 CSSOM CSS 規則具有一般來源文字,充分運用現有功能,而不必在開發人員工具中重新設定樣式。

如需更多背景資訊,請參閱設計提案或列有所有相關修補程式的 Chromium 追蹤錯誤

下載預覽頻道

建議您使用 Chrome Canary開發人員版Beta 版做為預設開發瀏覽器。這些預覽管道可讓您使用最新的開發人員工具、測試最先進的網路平台 API,以及在使用者操作之前在網站上找出問題!

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

使用下列選項,在文章中討論新功能和異動,或與開發人員工具相關的任何其他內容。

  • 透過 crbug.com 提供建議或意見。
  • 如要回報開發人員工具問題,請在開發人員工具中依序點選「更多選項」更多   >「說明」 >「回報開發人員工具的問題」
  • @ChromeDevTools 張貼推文。
  • 歡迎前往開發人員工具的 YouTube 影片或開發人員工具的 YouTube 影片提供新功能留言。