Facebook 重構:拋棄 Sass / Less ,迎接原子化 CSS 時代
隨著 Facebook 和 Twitter 最近的產品部署,我認為一個新的趨勢正在緩慢增長:Atomic CSS-in-JS。
在這篇文章中,我們將看到什麼是Atomic CSS(原子 CSS),它如何與 Tailwind CSS 這種實用工具優先的樣式庫聯絡起來,目前很多大公司在 React 程式碼倉庫中使用它們。
由於我不是這方面的專家,所以我不會去深入探討它的利弊。我只是希望能幫助你瞭解它的大致內容。
先丟擲一個令人開心的結論,新的 CSS 編寫和構建方式讓 Facebook 的主頁減少了 80% 的 CSS 體積。
什麼是原子 CSS?
你可能聽說過各種 CSS 方法,如 BEM, OOCSS…
<button class="button button--state-danger">Danger button</button>
現在,人們真的很喜歡 Tailwind CSS[1] 和它的 實用工具優先(utility-first)[2] 的概念。這與 Functional CSS 和 Tachyon[3] 這個庫的理念非常接近。
<button
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
Button
</button>
用海量的實用工具類(utility classes)組成的樣式表,讓我們可以在網頁裡大顯身手。
原子 CSS 就像是實用工具優先(utility-first)CSS 的一個極端版本: 所有 CSS 類都有一個唯一的 CSS 規則。原子 CSS 最初是由 Thierry Koblentz (Yahoo!)在 2013 年挑戰 CSS 最佳實踐[4]時使用的。
/* 原子 CSS */
.bw-2x {
border-width: 2px;
}
.bss {
border-style: solid;
}
.sans {
font-style: sans-serif;
}
.p-1x {
padding: 10px;
}
/* 不是原子 CSS 因為這個類包含了兩個規則 */
.p-1x-sans {
padding: 10px;
font-style: sans-serif;
}
使用實用工具/原子 CSS,我們可以把結構層和表示層結合起來:當我們需要改變按鈕顏色時,我們直接修改 HTML,而不是 CSS!
這種緊密耦合在現代 CSS-in-JS 的 React 程式碼庫中也得到了承認,但似乎 是 CSS 世界裡最先對傳統的關注點分離有一些異議。
CSS 權重也不是什麼問題,因為我們使用的是最簡單的類選擇器。
我們現在透過 html 標籤來新增樣式,發現了一些有趣的事兒:
我們增加新功能的時候,樣式表的增長減緩了。
我們可以到處移動 html 標籤,並且能確保樣式也同樣生效。
我們可以刪除新特性,並且確保樣式也同時被刪掉了。
可以肯定的缺點是,html 有點臃腫。對於伺服器渲染的 web 應用程式來說可能是個缺點,但是類名中的高冗餘使得 gzip 可以壓縮得很好。同時它可以很好地處理之前重複的 css 規則。
一旦你的實用工具/原子 CSS 準備好了,它將不會有太大的變化或增長。可以更有效地快取它(你可以將它附加到 vendor.css 中,重新部署的時候它也不會失效)。它還具有相當好的可移植性,可以在任意其他應用程式中使用。
實用工具/原子 CSS 的限制
實用工具/原子 CSS 看起來很有趣,但它們也帶來了一些挑戰。
人們通常手工編寫實用工具/原子 CSS,精心制定命名約定。但是很難保證這個約定易於使用、保持一致性,而且不會隨著時間的推移而變得臃腫。
這個 CSS 可以團隊協作開發並保持一致性嗎?它受巴士因子[5]的影響嗎?
巴士係數是軟體開發中關於軟體專案成員之間訊息與能力集中、未被共享的衡量指標,也有些人稱作“貨車因子”、“卡車因子”(lottery factor/truck factor)。一個專案或計劃至少失去若干關鍵成員的參與(“被巴士撞了”,指代職業和生活方式變動、婚育、意外傷亡等任意導致缺席的緣由)即導致專案陷入混亂、癱瘓而無法存續時,這些成員的數量即為巴士係數。
你還需要預先開發好一個不錯的實用工具/原子樣式表,然後才能開始開發新功能。
如果實用工具/原子 CSS 是由別人製作的,你將不得不首先學習類命名約定(即使你知道 CSS 的一切)。這種約定是有主觀性的,很可能你不喜歡它。
有時,你需要一些額外的 CSS,而實用工具/原子 CSS 並不提供這些 CSS。沒有約定好的方法來提供這些一次性樣式。
Tailwind 趕來支援
Tailwind 使用的方法是非常便捷的,並且解決了上述一些問題。
它透過 Utility-First 的理念來解決 CSS 的一些缺點,透過抽象出一組類名 -> 原子功能的集合,來避免你為每個 div 都寫一個專有的 class,然後整個網站重複寫很多重複的樣式。
傳統卡片樣式寫法:
Tailwind 卡片樣式寫法:
它並不是真的為所有網站提供一些唯一的實用工具 CSS,取而代之的是,它提供了一些公用的命名約定。透過一個配置檔案[6],你可以為你的網站生成一套專屬的實用工具 CSS。
ssh 注:這裡原作者沒有深入介紹,為什麼說是一套命名約定呢而不是生成一些定死的 CSS 呢?
舉幾個例子讓大家感受一些,Tailwind 提供了一套強大的構建系統,比如預設情況下 站長部落格 它提供了一些響應式的斷點值:
// tailwind.config.js
module.exports = {
theme: {
screens: {
'sm': '640px',
// => @media (min-width: 640px) { ... }
'md': '768px',
// => @media (min-width: 768px) { ... }
'lg': '1024px',
// => @media (min-width: 1024px) { ... }
'xl': '1280px',
// => @media (min-width: 1280px) { ... }
}
}
}
你可以隨時在配置檔案中更改這些斷點,比如你所需要的小螢幕 sm 可能指的是更小的 320px,那麼你想要在小螢幕時候採用 flex 佈局,還是照常寫 sm:flex,遵循同樣的約定,只是這個 sm 已經被你修改成適合於專案需求的值了。
在比如說,Tailwind 裡的 spacing 掌管了 margin、padding、width 等各個屬性裡的代表空間佔用的值,預設是採用了 rem 單位,當你在配置裡這樣覆寫後:
// tailwind.config.js
module.exports = {
theme: {
spacing: {
'1': '8px',
'2': '12px',
'3': '16px',
'4': '24px',
'5': '32px',
'6': '48px',
}
}
}
你再去寫 h-6(height), m-2(margin), mb-4(margin-bottom),後面數字的意義就被你改變了。
也許從桌面端換到移動端專案,這個 6 代表的含義變成了 6rem,但是這套約定卻深深的印在你的腦海裡,成為你知識的一部分了。
Tailwind 的知識可以遷移到其他應用程式,即使它們使用的類名並不完全相同。這讓我想起了 React 的「一次學習,到處編寫」理念。
我看到一些使用者反饋說,Tailwind 提供的類名能覆蓋他們 90% - 95% 的需求。這個覆蓋面似乎已經足夠廣了,並不需要經常寫一次性的 CSS 了。
此時,你可能想知道為什麼要使用原子 CSS 而不是 Tailwind CSS?強制執行原子 CSS 規則的一個規則,一個類名,有什麼好處?你最終會得到更大的 html 標籤和更煩人的命名約定嗎?Tailwind 已經有了足夠多的原子類了啊。
那麼,我們是否應該放棄原子 CSS 的想法,而僅僅使用 Tailwind CSS?
Tailwind 是一個優秀的解決方案,但仍然有一些問題沒有解決:
需要學習一套主觀的命名約定
CSS 規則插入順序仍然很重要
未使用的規則可以輕鬆刪除嗎?
我們如何處理剩下的一次性樣式?
與 Tailwind 相比,手寫原子 CSS 可能不是最方便的。
和 CSS-in-JS 比較
CSS-in-JS 和實用工具/原子 CSS 有密切關係。這兩種方法都提倡使用標籤進行樣式化。以某種方式試圖模仿內聯樣式,這讓它們有了很多相似的特性(比如在移動某些功能的時候更有信心)。
Christopher Chedeau[7] 一直致力於推廣 React 生態系統中 CSS-in-JS 理念。在很多次演講中,他都解釋了 CSS 的問題:
全域性名稱空間
依賴
無用程式碼消除
程式碼壓縮
共享常量
非確定性(Non-Deterministic)解析
隔離
實用工具/原子 CSS 也解決了其中的一些問題,但也確實沒法解決所有問題(特別是樣式的非確定性解析)。
如果它們有很多相似之處,那我們能否同時使用它們呢?
探索原子 CSS-in-JS
原子 CSS-in-JS 可以被視為是“自動化的原子 CSS”:
你不再需要建立一個 class 類名約定
通用樣式和一次性樣式的處理方式是一樣的
能夠提取頁面所需要的的關鍵 CSS,並進行程式碼拆分
有機會修復 JS 中 CSS 規則插入順序的問題
我想強調兩個特定的解決方案,它們最近推動了兩個大規模的原子 CSS-in-JS 的部署使用,來源於以下兩個演講。
React-Native-Web at Twitter (更多細節在Nicolas Gallagher 的演講[8])。
Stylex at Facebook (更多細節在Frank Yan 的演講[9])。
也可以看看這些庫:
Styletron
Fela
Style-Sheet
cxs
otion
css-zero
ui-box
style9
stitches
catom
React-Native-Web
React-Native-Web 是一個非常有趣的庫,讓瀏覽器也可以渲染 React-Native 原語。不過我們這裡並不討論跨平臺開發(演講裡有更多細節)。
作為 web 開發人員,你只需要理解 React-Native-Web 是一個常規的 CSS-in-JS 庫,它自帶一些原始的 React 元件。所有你寫 View 元件的地方,都可以用 div 替換。
React-Native-Web 的作者是 Nicolas Gallagher,他致力於開發 Twitter 移動版。他們逐漸把它部署到移動裝置上,不太確定具體時間,大概在 2017/2018 年左右。
從那以後,很多公司都在用它(美國職業足球大聯盟、Flipkart、Uber、紐約時報……),但最重要的一次部署,則是由 Paul Armstrong 領導的團隊在 2019 年推出的新的 Twitter 桌面應用。
Stylex
Stylex 是一個新的 CSS-in-JS 庫,Facebook 團隊為了 2020 年的 Facebook 應用重構而開發它。未來可能會開源,有可能用另一個名字。
值得一提的是,React-Native-Web 的作者 Nicolas Gallagher 被 Facebook 招安。所以裡面出現一些熟悉的概念一點也不奇怪。
我的所有資訊都來自演講 :),還需要等待更多的細節。
可擴充套件性
不出所料,在 Atomic CSS 的加成下,Twitter 和 Facebook 的 CSS體積都大幅減少了,現在它的增長遵循的是對數曲線。不過,簡單的應用則會多了一些 初始體積。
Facebook 分享了具體數字:
舊的網站 僅僅首頁就用了 413Kb 的 CSS
新的網站 整個站點只用了 74Kb,包括暗黑模式
原始碼和輸出
這兩個庫的 API 看起來很相似,但也很難說,因為我們對 Stylex 瞭解不多。
值得強調的是,React-Native-Web 會擴充套件 CSS 語法糖,比如 margin: 0,會被輸出為 4 個方向的 margin 原子規則。
以一個元件為例,來看看舊版傳統 CSS 和新版原子 CSS 輸出的區別。
<Component1 classNames="class1" /> <Component2 classNames="class2" />
.class1 {
background-color: mediumseagreen;
cursor: default;
margin-left: 0px;
}
.class2 {
background-color: thistle;
cursor: default;
jusify-content: flex-start;
margin-left: 0px;
}
可以看出這兩個樣式中 cursor 和 margin-left 是一模一樣的,但它在輸出中都會佔體積。
再來看看原子 CSS 的輸出:
<Component1 classNames="classA classC classD" />
<Component2 classNames="classA classB classD classE" />
class a {
cursor: default;
}
class b {
background-color: mediumseagreen;
}
class C {
background-color: thistle;
}
class D {
margin-left: 0px;
}
class E {
jusify-content: flex-start;
}
可以看出,雖然標籤上的類名變多了,但是 CSS 的輸出體積會隨著功能的增多而減緩增長,因為出現過一次的 CSS Rule 就不會再重複出現了。
生產環境驗證
我們看看 Twitter 上的標籤是什麼樣子的:
現在,讓我們來看看新 Facebook:
很多人可能會被嚇到,但是其實它很好用,而且保持了 可訪問性[10]。
在 Chrome 裡檢查樣式可能有點難,但 devtools 裡就看得很清楚了:
CSS 規則順序
與手寫的工具/原子 CSS 不同,JS 庫能讓樣式不依賴於 CSS 規則的插入順序。
在規則衝突的情況下,生效的不是標籤上 class attribute 中的最後一個類,而是樣式表中最後插入的規則。
以這張圖為例,我們期望的是書寫在後的 blue 類覆蓋前面的類,但實際上 CSS 會以樣式表中的順序來決定優先順序,最後我們看到的是紅色的文字。
在實際場景中,這些庫避免在同一個元素上寫入多個規則衝突的類。它們會確保標籤上書寫在最後的類名生效。其他的被覆蓋的類名則被規律掉,甚至壓根不會出現在 DOM 上。
const styles = pseudoLib.create({
red: {color: "red"},
blue: {color: "blue"},
});
// 只會輸出 blue 相關的 CSS
<div style={[styles.red, styles.blue]}>
Always blue!
</div>
// 只會輸出 red 相關的 CSS
<div style={[styles.blue, styles.red]}>
Always red!
</div>
注意:只有使用最嚴格的原子 CSS 庫才能實現這種可預測的行為。
如果一個類裡有多個 CSS 規則,並且只有其中的一個 CSS 規則被覆蓋,那麼 CSS-in-JS 庫沒辦法進行相關的過濾,這也是原子 CSS 的優勢之一。
如果一個類只有一個簡單的 CSS 規則,如 margin: 0,而覆蓋的是 marginTop: 10。像 margin: 0 這樣的簡寫語法被擴充套件為 4 個不同的原子類,這個庫就能更加輕鬆的過濾掉不該出現在 DOM 上的類名。
仍然喜歡 Tailwind?
只要你熟悉所有的 Tailwind 命名約定,你就可以很高效的完成 UI 編寫。一旦你熟悉了這個設定,就很難回到手寫每個 CSS 規則的時代了,就像你寫 CSS-in-JS 那樣。
沒什麼能阻止你在原子 CSS-in-JS 的框架上建立你自己的抽象 CSS 規則,Styled-system[11] 就能在 CSS-in-JS 庫裡完成一些類似的事情。它基於一些約定創造出一些原子規則,在 emotion 中使用它試試:
import styled from '@emotion/styled';
import { typography, space, color } from 'styled-system';
const Box = styled('div')(typography, space, color);
等效於:
<Box
fontSize={4}
fontWeight="bold"
p={3}
mb={[4, 5]}
color="white"
bg="primary"
>
Hello
</Box>
甚至有可能在 JS 裡複用一些 Tailwind 的命名約定,如果你喜歡的話。
先看些 Tailwind 的程式碼:
<div className="absolute inset-0 p-4 bg-blue-500" />
我們在谷歌上隨便找一個方案,比如我剛剛發現 react-native-web-tailwindcss[12]:
import { t } from 'react-native-tailwindcss';
<View style={[t.absolute, t.inset0, t.p4, t.bgBlue500]} />;
就生產力的角度而言,並沒有太大的不同。甚至可以用 TS 來避免錯別字。
結論
這就是我要說的關於原子 CSS-in-JS 所有內容。
我從來沒有在任何大型生產部署中使用過原子 CSS、原子 CSS-in-JS 或 Tailwind。我可能在某些方面是錯的,請隨時糾正我。
我覺得在 React 生態系統中,原子 CSS-in-JS 是一個非常值得關注的趨勢,我希望你能從這篇文章中學到一些有用的東西。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69990087/viewspace-2754786/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- css前處理器--Sass,Less,StylusCSS
- postcss支援vue/less/css/sass檔案CSSVue
- 被時代拋棄的危機感
- 10個視覺化 CSS 工具, 快速生成 CSS 片段,渣男,又想拋棄我!!視覺化CSS
- 效能優化之拋棄Calendar優化
- webpack的css,less,sass中使用絕對路徑WebCSS
- Cassandra曾被Facebook拋棄,現正幫蘋果壯大蘋果
- webstrom配置sass與lessWeb
- LESS與SASS的區別
- sass和less的區別
- 「CSS思維」元件化VS原子化CSS元件化
- sass/scss 和 less的區別CSS
- 拋棄UITableView,讓所有列表頁不再難構建UIView
- post-css/less/sass樣式巢狀與命令之"&"符號—BEMCSS巢狀符號
- 詳細比較三個CSS前處理器(框架)Sass/LESS/StylusCSS框架
- Vue 應用 Sass、Scss、Less 和 StylusVueCSS
- LESS/SASS/Stylus開發工具彙總
- CSS重構CSS
- CSS程式碼重構與優化之路CSS優化
- 拋棄硬體抽象層 (HAL)抽象
- 拋棄jQuery 深入原生的JavaScriptjQueryJavaScript
- 拋棄NVelocity,來玩玩Razor
- 開發出貪吃蛇的諾基亞,為何會被手遊時代拋棄?
- CSS 即將支援巢狀,SASS/LESS 等前處理器已無用武之地?CSS巢狀
- 在 Create React App 中啟用 Sass 和 LessReactAPP
- 準備好迎接 AIOps 時代AI
- CSS程式碼重構與最佳化之路CSS
- 機器人開啟了步行時代 拋棄輪子他們更接近人了機器人
- Android Studio拋棄滑鼠指南Android
- webpack 4 配置模組化樣式(css/less..)WebCSS
- 金融時報:迎接“大資料”時代大資料
- 蘋果要狠心拋棄iPhone 7 外形感人!蘋果iPhone
- 更智慧——我們才被拋棄 (轉)
- react1.6版本新增sass與less的支援React
- 細談sass和less中的變數及其作用域變數
- CSS程式碼重構與優化簡單介紹CSS優化
- CSS/LESS tips and snippetsCSS
- CSS 前處理器—sassCSS