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