自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

攜程商旅在 Atomic Css 下的探索

開發(fā) 前端
未來Css-In-Js 的 Atomic Css 解決方案無論是在業(yè)務(wù)代碼還是基礎(chǔ) Components 中一定會(huì)是一個(gè)不錯(cuò)的方案。

作者簡(jiǎn)介

19組清風(fēng),攜程資深前端開發(fā)工程師,負(fù)責(zé)商旅前端公共基礎(chǔ)平臺(tái)建設(shè),關(guān)注NodeJs、研究效能領(lǐng)域。

一、引言

三年前 Facebook 開始思考在目前設(shè)計(jì)系統(tǒng)下面臨的問題,那時(shí)它們?cè)谇岸隧?xiàng)目、系統(tǒng)組件等部分使用的是 cssmodule 的樣式方案。

直至今日,F(xiàn)acebook 已經(jīng)將所有的 Web 前端使用 React 進(jìn)行重寫的同時(shí),也使用了一種新的 Atomic Css-in-JS 對(duì)于它們的 Css 方案進(jìn)行了重寫。

最近,F(xiàn)acebook 團(tuán)隊(duì)開源了他們內(nèi)部的 Atomic Css 解決方案:stylex,正是這套解決方案讓 Facebook 首頁樣式文件體積減少了至少 80%。

這篇文章中我們就著 Atomic Css 來聊聊 Facebook 最近剛好開源的 stylex。

二、Atomic Css

2.1 概念

Atomic Css 是一種通過為每個(gè)樣式聲明創(chuàng)建一個(gè)規(guī)則來減少定義規(guī)則總量的方法。

舉一個(gè)不是那么恰當(dāng)?shù)睦?,比如說你可以將 Atomic 理解為 a、b、c... 一個(gè)一個(gè)原子化的字母,而每一個(gè)元素最終生效的樣式則是通過 a、b、c... 這樣一個(gè)一個(gè)原子化的字母拼接而來。

假設(shè)我們想要得到一個(gè)長(zhǎng)寬為百分百、背景色為紅色的正方形,那么使用 Atomic Css 的方式來表示的話:

.w-full { width: 100%; };
.h-full { height: 100% };
.bg-red { backgroundColor: red }
<div class="w-full h-full bg-red">Square</div>

2.2 權(quán)衡

在Acss首次提出 Atomic Css 的實(shí)現(xiàn)方案后,之后有關(guān)于 Atomic Css 的相關(guān)討論以及實(shí)踐在前端社區(qū)內(nèi)就如雨后春筍般四處開花。

無論是 Acss、Unocss、Tailwind 等等之類 Css 庫,其實(shí)歸根結(jié)底都是來源于同一個(gè)實(shí)現(xiàn)思路:Atmoic Css,那么 Atomic Css 究竟有什么好處呢?

接下來,聊聊我們關(guān)于 Atomic Css 的看法。

2.2.1 多 Npm Package 下的樣式復(fù)雜性

我所在的團(tuán)隊(duì)日常除了常規(guī)的前端頁面開發(fā)外,還負(fù)責(zé)了以下兩個(gè)方面:

  • 日常項(xiàng)目中和業(yè)務(wù)強(qiáng)相關(guān)的偏業(yè)務(wù)性組件。
  • 日常項(xiàng)目中和業(yè)務(wù)無關(guān)性質(zhì)的基礎(chǔ)性組件。

無論是在業(yè)務(wù)還是基礎(chǔ)組件的開發(fā)和維護(hù)上,如何縮減相關(guān)組件體積以至于可以和使用到該組件的不同業(yè)務(wù)團(tuán)隊(duì)最小化的結(jié)合一直是我們?cè)趯で蟮哪繕?biāo)。

舉一個(gè)比較簡(jiǎn)單的例子,假設(shè)我們?cè)陂_發(fā)一個(gè) Button 組件的同時(shí)定義了一個(gè) corp-button 的樣式:

.corp-button {
    height: 100%;
    width:  100%;
}

此時(shí)當(dāng)我們?cè)俅伍_發(fā)另一個(gè) input 組件時(shí),大多時(shí)候我們其實(shí)會(huì)經(jīng)常用到 height:100%; width:100% 的樣式,傳統(tǒng)的 Scoped Css 解決方案下難免我們會(huì)重新定義一份樣式聲明:

.corp-input {
    height: 100%;
    width: 100%;
    font-weight: 500;
}

顯而易見,往往在同一份組件庫代碼中不同的 class 定義存在無數(shù)重復(fù)的樣式聲明,無論是 CssModule 還是 Css-In-Js 都無法將這部分重復(fù)樣式聲明在構(gòu)建/運(yùn)行時(shí)刪除掉。

上邊的情況只是在單一存儲(chǔ)庫中很常見的問題,我們團(tuán)隊(duì)日常負(fù)責(zé)的 NpmPackage 遠(yuǎn)遠(yuǎn)不止一個(gè),所以 Atomic Css 的概念可以幫助解決這個(gè)問題。

只要可以保證 Atomic 原子性,那么無論是存在多少 Package ,也可以在項(xiàng)目中最大程度的保證這份樣式文件的復(fù)用。

舉一個(gè)簡(jiǎn)單的例子,比如上述的 corp-button 使用 atomic css 的方案,可以拆分為更加原子化的 class 聲明:

.w-full {
    width: 100%;
};
.h-full {
    height: 100%;
}


.corp-button => Compiled => .w-full .h-full

而在 input 組件中同樣可以使用拆分后的 atomic:

.font-normal {
    font-weight: 500;
}


.corp-input => Compiled => .w-full .h-full .font-normal

這種情況下,Atomic Css 可以大大減少調(diào)用方在使用不同 Npm Package 下的樣式文件體積,從而對(duì)于頁面加載性能來說是一種極大的提升。

2.2.2 單一項(xiàng)目復(fù)雜度上升時(shí)的樣式文件體積

往往大多數(shù)前端團(tuán)隊(duì)由于歷史、規(guī)模原因,無法左右依賴組件方的技術(shù)架構(gòu)方案。

與其息息相關(guān)更多的是隨著頻繁、快速的業(yè)務(wù)迭代,帶來樣式文件復(fù)雜度直線上升的問題。

那么怎么解釋這里的樣式文件復(fù)雜度直接上升的問題呢,我們來看一個(gè)稍微抽象的例子。

比如 A 同學(xué)在負(fù)責(zé) ProjectA 項(xiàng)目,跟隨著頻繁的業(yè)務(wù)迭代難免一直會(huì)有新的頁面、功能增加到現(xiàn)有的項(xiàng)目中。

那么,對(duì)于前端工程師來說,隨著需求的頻繁增加,難免需要增加很多個(gè)新的 class 來編寫這部分新增的樣式。

傳統(tǒng)的 CssModule 以及 Css-In-Js 方案,可以讓我們?cè)?class 的聲明上無需考慮新命名和舊命名重復(fù)的問題,但它仍無法解決隨著新的需求到來,仍然會(huì)增加新的樣式聲明內(nèi)容,從而帶來更大的樣式文件體積影響頁面性能。

如果我們使用 Atomic 方案來處理 Css 文件的話,無論在多么頻繁的需求迭代背景下,樣式文件體積并不會(huì)跟隨項(xiàng)目復(fù)雜度而直線上升,原子化的 Css 文件體積到達(dá)一個(gè)極限的拐點(diǎn)之后會(huì)漸漸趨于平穩(wěn)。

上面的描述稍微有些抽象,但是并不難理解。就好比如果在項(xiàng)目中需要增加一個(gè)背景色為紅色,寬高均為百分五十的 div 時(shí),在之前的方案中我們會(huì)直接聲明:

.new-demand-block {
    width: 50%;
    height: 50%;
    background: red;
}

最終構(gòu)建之后的樣式文件中,會(huì)加入 .new-demand-block 這部分的樣式。

而如果使用 Aotmic Css 的方案,由于之前已經(jīng)定義過 width:50%、height:50%、background:red 的 Atomic Class ,所以新的樣式文件中并不會(huì)存在這些根據(jù)新需求而來的樣式。

不過有些同學(xué)會(huì)疑惑,這不是會(huì)將樣式文件體積轉(zhuǎn)化到了 HTML 中去了嗎?

實(shí)際的確是這樣,但是這也僅僅是首屏 HTML 會(huì)攜帶這部分 Atomic ClassName。同時(shí)對(duì)于 HTML 模版中的相同 Atomic Css,Gzip 會(huì)幫我們把這部分重復(fù)的 ClassName 壓縮到一個(gè)足夠小的體積。

2.2.3 日常業(yè)務(wù)交付標(biāo)準(zhǔn)下的樣式復(fù)用“錯(cuò)亂”性

同樣,Atomic Css 方案還有一個(gè)和日常開發(fā)息息相關(guān)的影響點(diǎn)。

相信絕大多數(shù)開發(fā)同學(xué)都會(huì)碰到,伴隨著新需求上線或者修復(fù)某些 Bug 的同時(shí),突然發(fā)現(xiàn)影響了之前已經(jīng)經(jīng)過驗(yàn)證的頁面樣式。

這也是 Utility Css 會(huì)帶來的問題,為了節(jié)省樣式體積或是節(jié)約開發(fā)成本,我們往往會(huì)選擇在項(xiàng)目中復(fù)用相同類型的樣式。

但是隨著項(xiàng)目日積月累,我們會(huì)面臨修改這部分樣式時(shí)帶來的隱患:修改 A 模塊的 class 樣式內(nèi)容,或許會(huì)影響到 B、C 等模塊。

這無異對(duì)于開發(fā)還是測(cè)試來說都是一種災(zāi)難,Atomic Css 的出現(xiàn)可以很好地幫助我們解決這個(gè)問題。

每一處的元素都是由一個(gè)一個(gè) Atomic 組成的樣式,在編寫新的 Css 聲明時(shí)由于已經(jīng)是 Atomic 方案,所以大可不必?fù)?dān)心樣式體積的冗余而抽離一些 Utility Css。

自然,當(dāng)我們修改某一處樣式文件內(nèi)容時(shí),也完全無需擔(dān)心會(huì)影響到別的地方,因?yàn)槊看挝覀冃薷牡牟⒉皇?class 代表的意義,而是使用一個(gè)一個(gè) Atomic Class 來拼裝獲得當(dāng)前元素最終的樣式。

當(dāng)前,如果你能保證你團(tuán)隊(duì)的樣式系統(tǒng)是百分百的標(biāo)準(zhǔn),以及 Utility 的聲明非常規(guī)范化,Atomic Css 在這個(gè)問題下的解決方案就稍微顯得有些牽強(qiáng)。不過在我看來,絕大多數(shù)業(yè)務(wù)項(xiàng)目由于客觀原因,是無法和組件庫之類的對(duì)齊做到百分百的樣式系統(tǒng)規(guī)范化。

2.3 成果 

樣式文件體積過大,?直是攜程商旅在性能上存在的痛點(diǎn),我們也在積極探索通過 Atomic 的方式尋找更好的用戶體驗(yàn)。目前我們?cè)趪H站 Trip.Biz 已經(jīng)從 CssModule 的方案全量切入 Atomic 方案,在樣式文件體積上取得了指數(shù)級(jí)變化的成果。 

比如同樣為 App 端首頁,在采用 Atomic 方案后的國際站首頁對(duì)比 CssModule 方案的國內(nèi)站首頁,相似的頁面樣式下,國際站首頁在首屏渲染時(shí)僅需要加載 13.2KB 樣式文件,而國內(nèi) App 端首頁則需要加載 694KB 樣式文件, 前后對(duì)比首屏需要加載的樣式文件體積足足相差 96% 。 

在國內(nèi)站的改版頁面中,同樣也取得了顯著的成果。比如在商旅 PC新版大首頁中,前后同樣一個(gè)查詢框業(yè)務(wù)組件,在使用了 Atomic 方案之后,新版首頁中查詢框組件所需要的樣式規(guī)則完全可以被項(xiàng)目覆蓋,最終單個(gè)查詢框組件跟隨頁面編譯后的樣式文件體積可以趨近于0。

三、Stylex

3.1 開始之前

Atmoic Css 在 stylex 出現(xiàn)之前也有許多優(yōu)秀的解決方案,比如 Tailwind、WindCss、UnoCss 等。

我們團(tuán)隊(duì)目前在使用的也并非 stylex 而是 Tailwind ,這篇文章更多是和大家介紹 Stylex 的用法以及我個(gè)人對(duì)于 stylex 的一些見解。

我們完全不用片面的認(rèn)為 Atomic Css 就一定是 Tailwind 或者 Stylex 之類的某種實(shí)現(xiàn)框架。

無論 tailwind 還是 stylex ,他們都是 Atomic Css 方案的不同實(shí)現(xiàn)方案而已,至于應(yīng)該選擇哪一種框架來實(shí)現(xiàn) Atomic Css ,更多還是根據(jù)大家各自團(tuán)隊(duì)中的實(shí)際情況來見仁見智。

究竟是 Utility 方式的 Tailwind 還是 Css-In-Js 方式的 Stylex ,哪一種更優(yōu)秀,這篇文章中并不會(huì)討論。討論這些,就好比我在告訴你應(yīng)該使用 Vue 還是 React 來寫前端一樣。

3.2 簡(jiǎn)介

stylex是 Facebook 最近開源的一套 Css-In-Js 的 Atomic Css 解決方案。

Stylex 的工作原理是通過 Babel 在編譯階段將編寫的 Css-In-JS 代碼生成一個(gè)一個(gè) Atomic Css 樣式,為輸出的元素增加這些 classname 的同時(shí)最終輸出在樣式文件中。雖然寫法上和 Css-In-Js 類似,但是 stylex 幾乎沒有任何運(yùn)行時(shí)的成本。

同時(shí)對(duì)于需要結(jié)合不同變量增加不同樣式的運(yùn)行時(shí)場(chǎng)景,Stylex 會(huì)在必要時(shí)根據(jù)不同條件來快速的生成組件的類名字符串,添加到對(duì)應(yīng)元素中。

3.3 stylex.create/stylex.props

我們可以通過 stylex.create 方法創(chuàng)建 Atomic 樣式內(nèi)容,從而使用 stylex.props 將 stylex.create 方法生成的 Atomic Css 應(yīng)用到元素上。

比如:

import * as stylex from '@stylexjs/stylex';


// stylex.create 創(chuàng)建樣式內(nèi)容
const styles = stylex.create({
  root: {
    backgroundColor: 'red',
    padding: '1rem',
    paddingInlineStart: '2rem'
  },
  title: {
    backgroundColor: 'blue'
  },
  dynamic: (opacity) => ({
    opacity
  })
});


function HomePage() {
  return (
    // stylex.props 應(yīng)用創(chuàng)建的樣式內(nèi)容到元素上
    <div {...stylex.props(styles.root)}>
      <h2 {...stylex.props(styles.title)}>Stylex</h2>
      <p {...stylex.props(styles.dynamic(0.2))}>
        Introduction to the basics of stylex.
      </p>
    </div>
  );
}


export default HomePage;

上邊的代碼經(jīng)過編譯后的 Css 樣式文件輸出如下:

.x1uz70x1:not(#\#){padding:1rem}
.x1t391ir:not(#\#):not(#\#){background-color:blue}
.xrkmrrc:not(#\#):not(#\#){background-color:red}
.x1u4uod0:not(#\#):not(#\#){opacity:var(--opacity,revert)}
.xld8u84:not(#\#):not(#\#){padding-inline-start:2rem}

我們可以看到對(duì)于 stylex.create 創(chuàng)建的樣式內(nèi)容,均被編譯成為了一個(gè)一個(gè) Atomic Css 的 classname。

同時(shí)對(duì)于頁面上的元素,在經(jīng)過 stylex 的 babel 插件編譯后,元素的 classname 上會(huì)增加上一個(gè)又一個(gè)編譯后的 Atomic classname:

圖片

唯一需要注意的一點(diǎn)是:在 p 標(biāo)簽中我們使用了 styles.dynamic,它表示一個(gè)動(dòng)態(tài)生成的 Css 透明度樣式。

透過上述編譯后的內(nèi)容,我們可以清楚地看到在 stylex 內(nèi)部是將這部分需要運(yùn)行時(shí)生成的 Css 樣式內(nèi)容的值,編譯為了 Css 變量的形式。

從而對(duì)于需要使用到動(dòng)態(tài) Css 變量的元素,動(dòng)態(tài)替換它的 Css 變量值從而實(shí)現(xiàn)更新元素樣式的效果,這個(gè)實(shí)現(xiàn)思路還是比較巧妙的。

3.4 stylex.defineVars/stylex.createTheme

3.4.1 stylex.defineVars

stylex 中還提供了 defineVars Api 來幫助我們快速定義樣式變量的值。

// src/components/ButtonTokens.stylex.ts
import * as stylex from '@stylexjs/stylex';


// 通過 stylex 定義一系列 Button 相關(guān)樣式變量
export const buttonTokens = stylex.defineVars({
  bgColor: 'green',
  textColor: 'red',
  cornerRadius: '4px',
  paddingBlock: '4px',
  paddingInline: '8px'
});


// src/components/Button.ts
import * as stylex from '@stylexjs/stylex';
import './ButtonTokens.stylex';
import { buttonTokens } from './ButtonTokens.stylex';


const styles = stylex.create({
  base: {
    borderWidth: 0,
    backgroundColor: buttonTokens.bgColor,
    color: buttonTokens.textColor,
    borderRadius: buttonTokens.cornerRadius,
    paddingBlock: buttonTokens.paddingBlock,
    paddingInline: buttonTokens.paddingInline
  }
});
function Button() {
  return <button {...stylex.props(styles.base)}>This is Single Button</button>;
}


export default Button;

需要額外注意的是,官網(wǎng)文檔中明確標(biāo)注關(guān)于 defineVars 方法需要滿足在文件名為 .stylex.js/*.stylex.ts 的文件中被具名導(dǎo)出。

需要注意雖然文檔上沒提,但是 import './ButtonTokens.stylex'; 必不可少。如果缺少這句導(dǎo)入,實(shí)際樣式內(nèi)容并不會(huì)正常顯示。

此時(shí)頁面中的 Button :

圖片

圖片

3.4.2 stylex.createTheme

stylex.createTheme 接受兩個(gè)參數(shù),第一個(gè)參數(shù)為通過 defineVars 創(chuàng)建的變量集合,第二個(gè)參數(shù)為用于覆蓋第一個(gè)參數(shù)的值,它是一個(gè)對(duì)象。

我們可以通過 stylex.createTheme 創(chuàng)建一個(gè) StyleXStyles 對(duì)象,從而提供給 stylesx.props 方法使用。

同時(shí),我們也可以使用stylex.createTheme來通過覆蓋stylex.defineVars 聲明的變量,從而創(chuàng)建主題,比如:

我們對(duì)上述的按鈕稍作修改,讓按鈕可以支持一個(gè)自定義主題的傳入:

import * as stylex from '@stylexjs/stylex';
import './ButtonTokens.stylex';
import { buttonTokens } from './ButtonTokens.stylex';


const styles = stylex.create({
  base: {
    borderWidth: 0,
    backgroundColor: buttonTokens.bgColor,
    color: buttonTokens.textColor,
    borderRadius: buttonTokens.cornerRadius,
    paddingBlock: buttonTokens.paddingBlock,
    paddingInline: buttonTokens.paddingInline
  }
});


// Button 組件可以額外接受一個(gè) theme 的主題
function Button(props: { theme?: stylex.Theme<typeof buttonTokens> }) {
  return (
    <button {...stylex.props(props.theme, styles.base)}>
      This is Single Button
    </button>
  );
}


export default Button;

然后再將使用 Button 的地方稍做修改:

import * as stylex from '@stylexjs/stylex';
import Button from './components/Button';
import { buttonTokens } from './components/ButtonTokens.stylex';


const otherTheme = stylex.createTheme(buttonTokens, {
  bgColor: '#000',
  textColor: 'yellow',
  cornerRadius: '4px',
  paddingBlock: '4px',
  paddingInline: '8px'
});


function HomePage() {
  return (
    <div>
      {/* 未傳入特定主題,使用默認(rèn)主題 */}
      <Button />
      {/* 傳入特定主題,覆蓋原本主題 */}
      <Button theme={otherTheme} />
    </div>
  );
}


export default HomePage;

此時(shí),頁面上會(huì)出現(xiàn)兩個(gè)不同主題的按鈕:

圖片

實(shí)際上 createTheme 對(duì)于默認(rèn)的 defineVars 的覆蓋,也是通過 Css 變量?jī)?yōu)先級(jí)來確定主題優(yōu)先級(jí)的:

圖片

圖片

紅色文字按鈕的樣式變量,來源于 defineVars 的全局 Css 變量,而黃色按鈕通過在元素上編譯為同樣的  Css 變量的方式,自然優(yōu)先級(jí)會(huì)比全局 Css 更高。

四、展望

以上和大家簡(jiǎn)單聊 stylex 的 Api,以及它的基本使用姿勢(shì)。

目前我們對(duì)于 stylex 也并沒有太多的實(shí)踐經(jīng)驗(yàn),看起來相較于目前流行的 Tailwind 這種類似 Utility Css 的 atomic css 方案, Css-In-Js 的解決方案在代碼組織上,以及類型約束上,的確對(duì)于代碼的可讀性以及組織性會(huì)更加便攜一些。

不過 stylex 現(xiàn)階段無論是從構(gòu)建生態(tài)、內(nèi)置實(shí)現(xiàn)(比如#197,#40 都是我在編寫 Demo 時(shí)碰到的一些問題)來說,可能對(duì)于在生產(chǎn)應(yīng)用上使用還是有所欠缺。

總的來講,未來Css-In-Js 的 Atomic Css 解決方案無論是在業(yè)務(wù)代碼還是基礎(chǔ) Components 中一定會(huì)是一個(gè)不錯(cuò)的方案。

后續(xù)我們也會(huì)關(guān)注 stylex 的更新,并帶來更多關(guān)于 stylex 的實(shí)踐,希望本文的內(nèi)容可以幫助到大家。

責(zé)任編輯:張燕妮 來源: 攜程技術(shù)
相關(guān)推薦

2024-12-26 09:27:51

2024-12-18 10:03:30

2017-02-23 21:17:00

致遠(yuǎn)

2023-06-06 11:49:24

2023-10-27 09:34:34

攜程應(yīng)用

2024-11-05 09:56:30

2022-06-17 10:44:49

實(shí)體鏈接系統(tǒng)旅游AI知識(shí)圖譜攜程

2022-08-06 08:23:47

云計(jì)算公有云廠商成本

2023-08-18 10:49:14

開發(fā)攜程

2023-07-07 12:26:39

攜程開發(fā)

2024-04-18 09:41:53

2023-11-13 11:27:58

攜程可視化

2014-12-25 17:51:07

2024-03-22 15:09:32

2023-06-06 16:01:00

Web優(yōu)化

2017-07-06 19:57:11

AndroidMVP攜程酒店

2022-07-21 19:36:35

樂高攜程前端

2022-04-07 17:30:31

Flutter攜程火車票渲染

2022-11-29 20:32:07

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)