Facebook、Instagram 都在用的 CSS 解決方案開源了!
最近,Facebook(已更名為 Meta )開源了其內部使用的 CSS 解決方案:StyleX,目前在 Github 上已經獲得了 3.2k Star。Facebook、WhatsApp、Instagram、Workplace、Threads等產品都在使用 StyleX 作為其樣式解決方案。本文就來看看 StyleX 是的概念、特點和使用方法!
StyleX 基本概念
StyleX 是 Facebook 在 2019 年 React Conf 上首次公開的內部 CSS-in-JS 庫。
從那時起,它一直為 Facebook、Instagram 和 WhatsApp 提供支持,贏得了開發者的廣泛關注。然而,隨著時間的推移,CSS-in-JS 工具的受歡迎程度可能有所下降,原因是它們在性能上存在高昂的權衡。不過,StyleX 通過一個巧妙的 Babel 插件解決了這些問題。
StyleX 是一個強大、富有表現力、確定、可靠且可擴展的樣式系統。它在其他樣式庫的基礎上汲取了最佳的想法,并創造出一些既熟悉又獨特的功能,使開發者在開發過程中能夠更加得心應手。
StyleX 將 CSS-in-JS 庫的開發者體驗與編譯時工具相結合,實現了靜態 CSS 的高性能和可擴展性。然而,StyleX 并不僅僅是一個新的基于編譯器的 CSS-in-JS 庫。它的設計旨在滿足大型應用、可重用組件庫和靜態類型代碼庫的需求。以下是 StyleX 的幾個關鍵特點:
- 支持 CSS 的表現性子集,避免復雜的選擇器,確保生成的 CSS 中不存在特異性沖突。
- 將樣式轉換、組織和優化為“原子”CSS 類名,無需學習或管理單獨的實用類名庫。
- 允許跨文件和組件邊界合并樣式,非常適合允許用戶自定義的組件庫。
- 提供類型信息,并提供類型工具,以允許對組件接受的屬性和值進行精細控制。
特點
StyleX 的主要特點如下:
- 快速:StyleX 在編譯時和運行時都具備高效的性能。Babel 轉換不會對構建過程產生顯著影響。在運行時,StyleX 避免了使用 JavaScript 插入樣式的開銷,并僅在必要時高效地組合類名字符串。生成的 CSS 經過優化,確保即使是大型網站的樣式也能被瀏覽器快速解析。
- 可擴展:StyleX 旨在適應像 Meta 這樣的超大型代碼庫。通過原子構建和文件級緩存,Babel 插件能夠處理數萬個組件在編譯時的樣式處理。由于 StyleX 設計為封裝樣式,它允許在隔離環境中開發新組件,并期望一旦在其他組件中使用時能夠可預測地呈現。
- 可預測性:StyleX 會自動管理 CSS 選擇器的特異性,以確保生成的規則之間不會發生沖突。它為開發人員提供了一個可靠地應用樣式的系統,并確保“最后應用的樣式始終生效”。
- 類型安全:使用 TypeScript 或 Flow 類型來約束組件接受的樣式,每個樣式屬性和變量都具有完全的類型定義。這有助于提高代碼的可讀性和可維護性,同時減少潛在的錯誤和沖突。
- 樣式去重:StyleX 鼓勵在同一文件中編寫樣式和組件。這種方法有助于使樣式在長期內更具可讀性和可維護性。StyleX 能夠利用靜態分析和構建時工具來跨組件去重樣式,并刪除未使用的樣式。
- 可測試性:StyleX 可以配置為輸出調試類名,而不是功能性的原子類名。這可以用于生成快照,以便在對設計進行輕微更改時不會經常變化。通過這種方式,開發人員可以更輕松地測試和驗證樣式的正確性,從而提高開發效率和產品質量。
起源
在早期的 Facebook 網站開發中,他們使用了一種類似于 CSS Module 的方案來處理樣式,但這種方法存在著許多問題。于是,他們萌生了創建一個新的解決方案,即 CSS-in-JS。在普通用戶訪問 facebook.com 時,他們會下載數十兆字節的 CSS,其中很多都是未使用的。為了優化初始加載速度,Facebook 采用了延遲加載 CSS 的策略,但這又導致了更新速度變慢。此外,使用復雜的選擇器也可能會導致樣式沖突。為了解決這些問題,開發者們常常會使用 !important
或更復雜的選擇器,這使得整個樣式系統變得越來越復雜。
幾年前,當 Facebook 開始重構其網站時,他們急需一個更好的解決方案。于是,他們設計并構建了 StyleX。
StyleX 的設計注重可擴展性,其架構經過多年的使用經驗已經得到了驗證。隨著時間的推移,他們不斷在不降低性能和可擴展性的前提下添加新功能,使得 StyleX 更加易于使用。使用 StyleX 極大地改進了應用的可擴展性和表達性。在 facebook.com 的重構過程中,他們成功地將 CSS 捆綁包從數十兆字節的懶加載 CSS 降低到了幾百千字節的單個捆綁包。
Meta 創建 StyleX 的目的不僅是為了滿足 Web 上 React 開發人員的樣式需求,而且是為了統一 React 在 Web 和 Native 平臺上的樣式解決方案。通過采用 StyleX,他們得以實現跨平臺樣式的一致性,從而提高開發效率和產品質量。
StyleX 基本使用
Meta 的目標是使 StyleX 盡可能精簡和易于學習。因此不想過度復雜化而發明太多的API,而是希望能夠盡可能利用常見的 JavaScript 模式,以提供盡可能簡潔的API接口。
從本質上講,StyleX 可以歸納為兩個函數:
- stylex.create:用于創建樣式。
- stylex.props:用于將這些樣式應用到元素上。
在這兩個函數中,Meta 選擇依賴常見的 JavaScript 模式,而不是為 StyleX 引入獨特的 API 或模式。例如,沒有為條件樣式設計 API,而是支持使用布爾值或三元表達式來有條件地應用樣式。
下面來編寫一個按鈕組件:
import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({
base: {
appearance: "none",
borderWidth: 0,
borderStyle: "none",
backgroundColor: "blue",
color: "white",
borderRadius: 4,
paddingBlock: 4,
paddingInline: 8,
},
});
export default function Button({
onClick,
children,
}: Readonly<{
onClick: () => void;
children: React.ReactNode;
}>) {
return (
<button {...stylex.props(styles.base)} onClick={onClick}>
{children}
</button>
);
}
StyleX將樣式與組件緊密關聯,從開發體驗和代碼可讀性的角度來看,這是一個巨大的優勢。它采用了類似于 Emotion 的方式編寫CSS樣式,使得代碼易于理解和維護。同時,StyleX 還能夠在編譯時對CSS進行處理,從而獲得運行時系統無法提供的優勢。
然而,與 Tailwind 不同的是,StyleX 沒有提供類似于 Tailwind 的簡寫樣式的便利性。雖然失去了 Tailwind 的簡寫樣式,但換取了更多的樣式控制權。假設希望允許按鈕的使用者僅更改按鈕的顏色和背景顏色。對于 Tailwind 組件,可以為此定義特定的props,但如果希望用戶能夠調整更多樣式,這種方法就不太具有擴展性。因此,一些作者允許額外的extraClasses
屬性,用戶可以隨意添加樣式。但這樣做會導致用戶可以無限制地更改樣式,這使得后續版本控制變得困難。
StyleX 對于這個問題有一個很棒的解決方案:
import type { StyleXStyles } from "@stylexjs/stylex/lib/StyleXTypes";
export default function Button({
onClick,
children,
style,
}: Readonly<{
onClick: () => void;
children: React.ReactNode;
style?: StyleXStyles<{
backgroundColor?: string;
color?: string;
}>;
}>) {
return (
<button {...stylex.props(styles.base, style)} onClick={onClick}>
{children}
</button>
);
}
我們添加了一個名為style的屬性,并將其限制為只能覆蓋希望覆蓋的樣式。由于在stylex.props調用中將style放在styles.base之后,可以確保覆蓋樣式會適當地覆蓋基礎樣式。這樣我們就可以對Button進行版本控制,因為已經明確了哪些CSS可以更改,哪些不可以。
當想要覆蓋樣式時,使用 Button 的方式如下:
const buttonStyles = stylex.create({
red: {
backgroundColor: "red",
color: "blue",
},
});
<StyleableButton onClick={onClick} **style={buttonStyles.red}**>
Styleable Button
</StyleableButton>
StyleX 支持條件和動態樣式。下面來為按鈕添加一個強調標志:
import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({
...,
emphasized: {
fontWeight: "bold",
},
});
export default function Button({
onClick,
children,
emphasized,
}: Readonly<{
onClick: () => void;
children: React.ReactNode;
emphasized?: boolean;
}>) {
return (
<button
{...stylex.props(styles.base, emphasized && styles.emphasized)}
onClick={onClick}
>
{children}
</button>
);
}
我們只需要在樣式定義中添加另一個部分來定義強調樣式,并根據標志條件性地應用樣式,非常簡單!
這只是 StyleX 可以實現的一小部分功能。如果需要在運行時生成位置或顏色等值,樣式也可以是動態的。只需添加另一個 stylex.create 來定義變體,然后根據屬性使用正確的變體樣式,就可以輕松支持選項如 variant。
StyleX 團隊還將 OpenProps 的所有內容移植到了 StyleX,這意味著可以輕松訪問大量間距選項、顏色、動畫等功能。
StyleX 工作原理
StyleX 是一套協同工作的工具集組成的,包括:
- Babel 插件:作為 StyleX 的核心,此插件在編譯時查找并提取源代碼中的所有樣式定義,并將其轉換為原子類名。通過去重、排序和寫入 CSS 文件等輔助功能,它為打包工具插件的實現提供了支持。
- 運行時庫:這是一個輕量級的運行時庫,用于處理更高級的動態樣式組合模式。經過優化后,它具有高效性能,并利用結果緩存技術提升響應速度。
- ESlint 插件:通過與 ESlint 集成,此插件能夠在開發過程中實時檢測并規范使用 StyleX 的代碼,確保遵循最佳實踐。
- 與打包工具和框架的集成:StyleX 提供了與多種打包工具和框架的集成選項,以確保與項目的順暢整合。
為了優化性能,Babel 插件在可能的情況下會預先計算最終的類名,從而消除運行時的性能開銷,甚至包括同一文件中類名的合并操作。當組件在相同文件中靜態地定義和使用樣式時,運行時開銷將被完全消除。
Meta 如何使用 StyleX?
Meta 已經將 StyleX 確立為內部所有 Web 界面中樣式組件的首選解決方案。無論是 Facebook、WhatsApp、Instagram、Workplace 還是 Threads 等主要外部和內部產品,Meta 都采用 StyleX 來為 React 組件提供樣式,從而轉變了組件的編寫方式并解決了團隊之前所面臨的樣式組件封裝和擴展問題。
Meta 不僅在原始功能上擴展了 StyleX,還使其工程師能夠利用它來編寫靜態和動態樣式。目前,Meta 的團隊正在使用 StyleX 的主題 API 開發“通用”組件,這些組件可以適應不同 Meta 產品中采用的各種設計系統的外觀。由于 StyleX 遵循 React Native 樣式系統引入的封裝原則,Meta 正在逐步加強對跨平臺樣式的支持。