StyleX 是什麼? 解決了什麼問題? 適用在什麼場景?

2023年12月18日

💎 加入 E+ 成長計畫 與超過 350+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源
image

最近 Meta 開源了內部使用的 CSS 解決方案 StyleX,不意外地引來社群的一陣討論。事實上,StyleX 並不是什麼新東西,早在 React Conf 2019 時的《Building the New Facebook with React and Relay》講座中,Frank Yan 就已經提到了,只是當時這個工具還沒被開源。在這一期雙週報,我們從 Frank 當時的講座出發,來跟大家聊聊 StyleX。

在這之前,先請大家帶入兩個情境,首先,假如今天你要去面試前端工程師職位,當被問到「StyleX 解決了什麼問題? 適用在什麼場景?」你該如何回答? 假如你還沒辦法解釋、分析與比較,以及選擇是否用 StyleX,那就讓我們一起往下看吧。

首先,StyleX 是個樣式系統,它做的事情是透過編譯的方式,把樣式轉成原子 CSS (atomic CSS)。原子 CSS 解決了許多問題,而對於程式碼可維護性上帶來一些好處。

其中一大好處是《淺談 Atomic CSS 的發展背景與 Tailwind CSS》 提到的「在改樣式的時候,你可以直接把 class name 刪掉而不用怕影響到其他的元素,這是多麽美好的一件事情,你再也不用擔心改 A 壞 B,因為 class name 都跟 context 無關了」,這是 StyleX 文件中提的 colocation 能帶來的好處。

讀到這你可能會問說,寫純 HTML 或用 Svelte 這類框架,把 <style> 跟 HTML 放在同個檔案,又或者用 CSS Modules 這類工具,也可以確保 scope 被限縮,不用擔心改 A 影響到 B。但這仍會存在一個問題,就是不容易規模化。

《淺談 Atomic CSS 的發展背景與 Tailwind CSS》有提到原子 CSS 的另一個核心是「Define once, use everywhere」意即定義一次後全局都可以使用。這裡說的定義一次,代表著不改 stylesheet 又能改樣式。

這也是為什麼 Svelte 核心團隊的成員在這則推文提到「Imagine if svelte's style tag could be turned into atomic styling, sort of what Vanilla Extract and StyleX do!! ....... Like, instead of the same css coming out, compiler inserts atomic css classes into your markup, and extracts the most common styles out into atomic classes....... CSS will grow at O(logn) after this change」

原本 Svelte 的寫法看起來雖然有 colocation,但是 O(n) 的寫法,如果你要有新的樣式,就要在多定義一個新的。「原子化」的核心就是要做到 colocation 同時又能夠把「寫 CSS 這件事」從 O(n) 變成 O(logn)

不過就像很多演算法,在測試案例小時,O(n) 解法可能還比較快。在這在寫 CSS 也是,小的專案用熟悉的方法,可能還比較快;但大型有超多人要一起維護,就是完全不同的狀況了。

這邊先稍微總結一下第一點好處,StyleX 讓你可以不用擔心改 A 結果弄壞 B,與此同是讓你可以定義一次就全局使用,修改樣式時不會動到樣式表,這些都對於寫樣式有很直接的幫助。

上面談了 colocation,接著來聊 StyleX 文件談到的另一個好處 deterministic resolution。談這點前要先了解在大型專案下寫傳統的香草 CSS 會有什麼問題。在 Frank Yan 的講座中用很具體的範例來講解。我們先來看這個簡單的 CSS 問題,大家覺得下面的 <span> 會是什麼顏色呢?

// css 文件中
.blue {color: blue;}
.red {color: red;}

// HTML 文件中
<span class="red blue">這會是什麼顏色?</span>

大家腦中有答案了嗎? 如果你覺得是藍色,可能是因為在 class 當中 blue 在後面,但是這邊會是紅色,因為在 CSS 的優先序上,文件後者會優先。所以如果你只看 HTML 是沒辦法判斷出會是哪個顏色。

這還不是最讓人困擾的,我們來看第二個例子。現在假如 blue 與 red 是在兩個不同的檔案中,下面的 <span> 會是什麼顏色呢?

// blue.css
.blue {color: blue;}

// red.css
.red {color: red;}

// HTML 文件中
<span class="red blue">這會是什麼顏色?</span>

這個案例作基本上沒辦法直接回答出來的,因為要解答這問題,需要去了解這兩個 CSS 檔案是基於什麼打包規則才能回答。對於開發者來說,沒辦法在寫的時候判斷,是個很大的問題,而這也正是 StyleX 文件當中提到的 Deterministic resolution 要解決的問題

StyleX 本身的編譯器會幫你處理這問題,讓你確保在下方這個 <span> 一定會是後者的優先於前者,所以你可以很確定它會是藍色

<span {...styles.props(styles.red, styles.blue)}></span>

對比起來,同樣是原子 CSS 的 Tailwind,現在本身也還沒解決這問題,所以在下方的這個範例中,也仍存在不確定最終會是哪個顏色的問題。當然,目前社群中有 Tailwind Merge 這種好用的套件幫忙解決這件事。但這意味需要額外裝一個套件,有額外的維護成本。

// Tailwind 範例
<span class="text-blue-100 text-red-100">這會是什麼顏色?</span>

當然你可能會說,優先級在香草 CSS 的脈絡下,也不是沒辦法被處理。只是大型專案下,東西變多變複雜,若要解決優先度的問題,往往把類別定得很詳細,但這就會導致 Frank Yan 提的「優先級戰爭」,當專案一大一複雜、經歷多次重構,就會出現下面這種超長的類別,也就是 StyleX 文件中提到的 bloated CSS 問題,這會導致程式碼很難被維護。

.coolest-redesign .cooler-redesign .cool-redesign .cool {
  // ...
}

以筆者自己的經驗來說,之前有處理過一個在微前端架構,主應用加東西,多數子應用都正常渲染,唯獨有幾個子應用,在該新功能的樣式蓋過主應用的樣式。這是《淺談 CSS 方法論與 Atomic CSS》有談到 「一但開發的專案龐大起來,並且有多位前端工程師在進行程式碼撰寫時,就很容易遇到命名衝突、stylesheet 過於龐大等問題,主因是在 CSS 的世界中,所有規則集都是全域的」

你可能會說,命名前跟別人溝通協調一下。當然可以這樣做,但這樣做就是額外的時間成本,而在大的專案下這麼做,就會耗費更多時間成本,這顯然是不樂見的。以筆者上面提到的問題來說,不同子應用是不同團隊維護,團隊在不同國家,溝通存在時差問題,來回可能就是一兩天過去。

因此不是說其他方法做不到,而是 StyleX 的作法,是用更簡單、更容易維護複雜專案的方式,來實現 deterministic resolution,讓大型、跨國跨時區的專案,可以省去這些成本。

除了上面提到的 colocation 與 deterministic resolution,StyleX 還有許多其他好處,例如型別檢查,這是 Tailwind 目前也還沒做到的,在開發上能有效避免開發者寫錯東西。但礙於電子報的篇幅,今天就先不多談這些其他好處,推薦大家可以到 StyleX 的官方文件了解 [連結]

回到上面提的,如果面試中被問「StyleX 解決了什麼問題? 跟其他 CSS 解決方案分別適用在什麼場景?」希望這篇內容能讓大家講得出 colocation 與 deterministic resolution 這兩點。

至於適用的場景,確實若是在一個小型、沒有太多樣式要定義、不牽涉多人維護、不會頻繁迭代的專案的情境下,寫香草 CSS 其實很足夠。以 DHH 為例,他到現在開新的專案,還是選香草 CSS,也完全沒問題。但如果是大型、各種樣式複雜、跨團隊跨時區的協作、迭代很頻繁的專案,StyleX 能比上很大的忙。

不過你可能會問,比起其他的原子 CSS,例如 Tailwind,StyleX 的額外好處在哪? 整體比較起來,StyleX 多的好處是上面提到的 deterministic resolution 不需用額外裝一個 Tailwind Merge 就能做到,以及 Type-Safe 這點,也是在大型專案很有幫助的。

資料來源

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們