Facebook 重構:拋棄 Sass / Less ,迎接原子化 CSS 時代

文章由 ssh 翻譯 / 潤色,原文地址css

隨着 Facebook 和 Twitter 最近的產品部署,我認爲一個新的趨勢正在緩慢增加:Atomic CSS-in-JShtml

在這篇文章中,咱們將看到什麼是Atomic CSS(原子 CSS),它如何與 Tailwind CSS 這種實用工具優先的樣式庫聯繫起來,目前不少大公司在 React 代碼倉庫中使用它們。前端

因爲我不是這方面的專家,因此我不會去深刻探討它的利弊。我只是但願能幫助你瞭解它的大體內容。react

先拋出一個使人開心的結論,新的 CSS 編寫和構建方式讓 Facebook 的主頁減小了 80% 的 CSS 體積git

什麼是原子 CSS?

你可能據說過各類 CSS 方法,如 BEM, OOCSS…github

<button class="button button--state-danger">Danger button</button>
複製代碼

如今,人們真的很喜歡 Tailwind CSS 和它的 實用工具優先(utility-first) 的概念。這與 Functional CSS 和 Tachyon 這個庫的理念很是接近。web

<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 最佳實踐時使用的。npm

/* 原子 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!react-native

這種緊密耦合在現代 CSS-in-JS 的 React 代碼庫中也獲得了認可,但彷佛 是 CSS 世界裏最早對傳統的關注點分離有一些異議。

CSS 權重也不是什麼問題,由於咱們使用的是最簡單的類選擇器。

咱們如今經過 html 標籤來添加樣式,發現了一些有趣的事兒:

  • 咱們增長新功能的時候,樣式表的增加減緩了。
  • 咱們能夠處處移動 html 標籤,而且能確保樣式也一樣生效。
  • 咱們能夠刪除新特性,而且確保樣式也同時被刪掉了。

能夠確定的缺點是,html 有點臃腫。對於服務器渲染的 web 應用程序來講多是個缺點,可是類名中的高冗餘使得 gzip 能夠壓縮得很好。同時它能夠很好地處理以前重複的 css 規則。

一旦你的實用工具/原子 CSS 準備好了,它將不會有太大的變化或增加。能夠更有效地緩存它(你能夠將它附加到 vendor.css 中,從新部署的時候它也不會失效)。它還具備至關好的可移植性,能夠在任意其餘應用程序中使用。

實用工具/原子 CSS 的限制

實用工具/原子 CSS 看起來頗有趣,但它們也帶來了一些挑戰。

人們一般手工編寫實用工具/原子 CSS,精心制定命名約定。可是很難保證這個約定易於使用、保持一致性,並且不會隨着時間的推移而變得臃腫。

這個 CSS 能夠團隊協做開發並保持一致性嗎?它受巴士因子的影響嗎?

巴士係數是軟件開發中關於軟件專案成員之間訊息與能力集中、未被共享的衡量指標,也有些人稱做「貨車因子」、「卡車因子」(lottery factor/truck factor)。一個專案或計劃至少失去若干關鍵成員的參與(「被巴士撞了」,指代職業和生活方式變更、婚育、意外傷亡等任意致使缺席的原因)即致使專案陷入混亂、癱瘓而沒法存續時,這些成員的數量即爲巴士係數。

你還須要預先開發好一個不錯的實用工具/原子樣式表,而後才能開始開發新功能。

若是實用工具/原子 CSS 是由別人製做的,你將不得不首先學習類命名約定(即便你知道 CSS 的一切)。這種約定是有主觀性的,極可能你不喜歡它。

有時,你須要一些額外的 CSS,而實用工具/原子 CSS 並不提供這些 CSS。沒有約定好的方法來提供這些一次性樣式。

Tailwind 趕來支援

Tailwind 使用的方法是很是便捷的,而且解決了上述一些問題。

它經過 Utility-First 的理念來解決 CSS 的一些缺點,經過抽象出一組類名 -> 原子功能的集合,來避免你爲每一個 div 都寫一個專有的 class,而後整個網站重複寫不少重複的樣式。

傳統卡片樣式寫法:

Tailwind 卡片樣式寫法:

它並非真的爲全部網站提供一些惟一的實用工具 CSS,取而代之的是,它提供了一些公用的命名約定。經過一個配置文件,你能夠爲你的網站生成一套專屬的實用工具 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 一直致力於推廣 React 生態系統中 CSS-in-JS 理念。在不少次演講中,他都解釋了 CSS 的問題:

  1. 全局命名空間
  2. 依賴
  3. 無用代碼消除
  4. 代碼壓縮
  5. 共享常量
  6. 非肯定性(Non-Deterministic)解析
  7. 隔離

實用工具/原子 CSS 也解決了其中的一些問題,但也確實無法解決全部問題(特別是樣式的非肯定性解析)。

若是它們有不少類似之處,那咱們可否同時使用它們呢?

探索原子 CSS-in-JS

原子 CSS-in-JS 能夠被視爲是「自動化的原子 CSS」:

  • 你再也不須要建立一個 class 類名約定

  • 通用樣式和一次性樣式的處理方式是同樣的

  • 可以提取頁面所須要的的關鍵 CSS,並進行代碼拆分

  • 有機會修復 JS 中 CSS 規則插入順序的問題

我想強調兩個特定的解決方案,它們最近推進了兩個大規模的原子 CSS-in-JS 的部署使用,來源於如下兩個演講。

也能夠看看這些庫:

  • 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;
}
複製代碼

能夠看出這兩個樣式中 cursormargin-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:

不少人可能會被嚇到,可是其實它很好用,並且保持了 可訪問性

在 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 就能在 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

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 是一個很是值得關注的趨勢,我但願你能從這篇文章中學到一些有用的東西。

感謝閱讀。

感謝

本文首發於公衆號「前端從進階到入院」,歡迎關注。

字節跳動招人啦,明年光是咱們組就新增了十幾個 HC,真的很缺人。北上廣深杭都有,隨時面試均可以,我會幫你作好全方位的內推服務!歡迎經過留言、公衆號聯繫到我諮詢 😁。

相關文章
相關標籤/搜索