CSS工程化演進

idsv0427 6年前發布 | 32K 次閱讀 CSS 前端技術

CSS工程化演進

CSS 技術的演進

CSS 是 Web 開發中不可或缺的一部分,在前端工程化的不斷進步的今天,一方面在 CSS 特性隨著規范的升級越來越豐富,另一方面,前端業務復雜性的增加帶來的工程愈加龐大,驅使者開發者不斷尋找CSS工程化的最佳實踐。

Web開發模塊化趨勢

不可否認,無論從現代前端框架(React,Vue,Angular,Polymer等),還是從W3C的 Web Components 草案看來,組件化已經是前端開發的主流之選和未來的發展方向,正如在 reddit 上有網友說道 "非死book.com's codebase includes over 20,000 components"。廣義上看,所有頁面上都可以被劃分成一個個組件,相對于過去以網頁作為開發單位,以組件為單位開發有著可復用,可擴展等等一系列有利于項目工程化的優點。

在這種組件化趨勢的背景下,CSS 模塊化也漸漸有有著各種嘗試。

預處理與后處理

預處理

比較流行的CSS預處理器有 Sass , Less 和 Stylus ,CSS 預處理器的出現主要針對于 CSS 缺少編程語言的靈活性而生的,是引入了一些編程概念而生的 DSL,開發者編寫簡介的語義化 DSL 代碼,由預處理器編譯成 CSS。

以 Sass 為例,該預處理器支持 .scss , .sass 文件類型,其語法支持變量、選擇器嵌套、繼承(extend)、混合(mixin)和一些邏輯語句,同時還支持跨文件的導入功能,因而使得開發者能夠很好的使用編程思想書寫樣式。

從實際使用情況來看,幾個預處理器各有優缺點,社區活躍度上看 Sass > Less > Stylus,在于 Sass 是三個中間最早也是最成熟的,因而有著很多開源積累和很好編程范式,像內置了很多 Sass 的函數的 compass 框架,就是一個很好的例子;Less 相對于 Sass 的優點在于十分的輕量,也完全兼容 CSS,相對于但另一方面可編程能力也不如 Sass, Bootstrap 最新版本的 CSS 預處理器也從 Less 換成 Sass;Stylus 是來源于 node 社區,使用體驗上并不輸于 Sass 和 Less,無論是編譯速度還是語法范式,個人看來,stylus 在某種程度上更加優于其他兩個。

后處理

后處理器是對原生 CSS 進行處理并最終生成 CSS 的處理器,廣義上還是個預處理器,與上面不同的是,它處理的對象是標準 CSS,比較典型的后處理工具有:

  • clean-css -- 壓縮 CSS
  • AutoPrefixer -- 自動添加 CSS3 屬性各瀏覽器的前綴
  • Rework -- 取代 stylus 的插件化框架
  • PostCSS

CSS工程化演進

圖1

PostCSS

PostCSS 一開始是從 AutoPrefixer 項目中抽象出來的框架,它本身并不對CSS做具體的業務操作,只是將CSS解析成抽象語法樹(AST),樣式的操作由之后運行的插件系統完成。正如其本身所言“Tra nsforming styles with JS plugins”

CSS工程化演進

圖2

更多時候我們討論的 PostCSS ,并不止是其解析 CSS 的核心工具,更包括它創建的插件系統,而今 PostCSS 最為吸引開發者的正是其擴展性較強的插件系統和豐富的插件支持。

常用的插件

當然,如果已有的插件不能滿足現有的需求,完全可以 手寫一個插件 :

// 示例 rem 轉 px
var custom = function(css, opts){
    css.eachDecl(function(decl){
        decl.value = decl.value.replace(/\d+rem/, function(str){            return 16 * parseFloat(str) + "px";
        });
    });
};

當然,PostCSS 的解析并不局限于 CSS,結合它提供的 自定義 語法解析接口,完全可以定義自己的語法。其實類似于 postcss-scss 的插件社區已經有很多了,使用這些插件,可以將原來基于 SASS , LESS 等預處理器的代碼遷移成至 PostCSS。相對于傳統的預處理器,PostCSS這種開放平臺型的體系,能夠不拘束開發者的開發方式,同時也促進了更多對于 CSS 解決方案的探索。

回過頭來看,為什么會有對于 CSS 的預處理操作后處理操作 ?其實主要的原因在于前端項目的膨脹使得用傳統手工編寫并維護 CSS 變得很不堪,根本上由于 CSS 沒有缺少編程語言特性,要做到對于 CSS 代碼的模塊化以及高復用的抽象處理,就必須引入一些編程的思想。相對于 JS 標準推進以及基礎設施的完備,CSS 在于編程方面的探索更多來自于社區,也并無統一的事實標準,這也是 CSS 發展落后于JS的原因。

namespace 約束

一方面我們需要關注技術能夠帶來代碼上的模塊化,另一方面我們又要思考如何使用一個良好的風格架構起項目中的 CSS。CSS 除了代碼外,另一個很重要的就是 CSS 選擇標記,但 CSS 選擇器的命名空間是全局的,并沒有局部的概念,因而如何利用好這個全局的空間,選擇良好的結構風格,也是在開發過程中必須考慮的。

OOCSS

OOCS (Object-Oriented CSS)即面向對象 CSS,主要有兩個核心原則

  • 分離結構和皮膚(separate structure and skin)

    皮膚即一些重復的視覺特征,如邊框、背景、顏色,分離是為了更多的復用;結構是指元素大小特征,如高度,寬度,邊距等等。

.button {
  padding: 10px;
  box-shadow: rgba(0, 0, 0, .5) 2px 2px 5px;
}
.widget {
  overflow: auto;
  box-shadow: rgba(0, 0, 0, .5) 2px 2px 5px;
}

根據此原則,我們需要對公用的皮膚進行提取并分離,如下

.button {
  padding: 10px;
}
.widget {
  overflow: auto;
}
.skin {
  box-shadow: rgba(0, 0, 0, .5) 2px 2px 5px;
}
  • 分離容器和內容(separate container an content)

    打破容器內元素對于容器的依賴,元素樣式應該獨立存在。

    舉個例子

<div class="container"><h2>xxx</h2></div>
.container h2 {...}

上面的 h2 元素依賴于父元素 container , 對應此原則, h2 元素需要使用一個單獨的選擇器,如下

<div class="container"><h2 class="category">xxx</h2></div>
.category {...}

從實踐中看出,使用 OOSCC 范式,遵守了 DRY 的原則,能夠大量減少重復的樣式代碼,提高代碼復用;同時,視覺元素可以意靈活組合各個類名,展示不同的效果,豐富的類名也同時使得元素有著更好的可讀性;另一方面,由于容器和內容的分離,CSS 完成了與 HTML 結構解耦。

但同時也會帶來一些缺點,抽象復用會使class越來越多,極端情況會產生可能產生很多原子類,這對于那些偏向于“單一來源原則”的開發者來說并不受歡迎。

SMACSS

SMACSS (Scalable and Modular Architecture for CSS) 即模塊化架構的可擴展CSS,它主要是將規則分為5類

  • 基礎(Base)

    tag select 的樣式,定義最基礎全局樣式,如 CSS REST 。

html, body, form { margin: 0; padding: 0; }
a { color: #039; } a:hover { color: #03C; }
  • 布局(Layout)

    將頁面分為各個區域的元素塊

.header{}
....
.footer{}
  • 模塊(Module)

    可復用的單元。在模塊中需要注意的是選擇器一律選擇 class selector ,避免嵌套子選擇器,減少權重,方便外部覆蓋。

<div class="pod pod-constrained">...</div>
<div class="pod pod-callout">...</div>
.pod { width: 100%; }
.pod .pod-callout { width: 200px; }
.pod .pod-constrained{}
  • 狀態(State)

    狀態 class 一般通過js動態掛載到元素上,可以根據狀態覆蓋元素上特定屬性。

.tab { background-color: purple;... } 
.is-tab-active { background-color: white; }
  • 主題(Theme)

    可選的視覺外觀。一般根據需求有顏色,字體,布局等等,實現是將這些樣式單獨抽出來,根據外部條件( data 屬性,媒體查詢等)動態設置。

SMACSS 的主要優點在于按照不同的業務邏輯,將整個 CSS 結構化分更加細致,約束好命名,最小化深度,在編寫的時候,使用SMACSS規范能夠更好的組織好 CSS 文件結構和 class 命名。

BEM

BEM 即 Block Element Modifier ;類名命名規則: Block__Element--Modifier

  • Block 所屬組件名稱
  • Element 組件內元素名稱
  • Modifier 元素或組件修飾符 
    其核心思想就是組件化。首先一個頁面可以按層級依次劃分未多個組件,其次就是單獨標記這些元素。BEM通過簡單的塊、元素、修飾符的約束規則確保類名的唯一,同時將類選擇器的語義化提升了一個新的高度。
<form class="form form--theme-xmas form--simple">
<input class="form__input" type="text" />
<input
class="form__submit form__submit--disabled"
type="submit" />
</form>
.form { } 
.form--theme-xmas { } 
.form--simple { } 
.form__input { } 
.form__submit { } 
.form__submit--disabled { }

BEM 通過簡單的命名規則使得關聯類名元素語義性、可讀性更強,利于項目管理和多人協作;同時 BEM 方案中并沒有嵌套,所有類名最淺深度,并不會出現嵌套過深難以覆蓋的情況,易于維護、復用;

另一方面,BEM 強調單一職責原則和單一樣式來源原則,意味著傳統純手工 CSS 可能會產生大量重復的代碼,但是結合各種 CSS 預處理和 PostCSS 就可以很好的避免問題的產生。另外,雖說股則簡單,但在實際使用中,維護 BEM 的命名確實需要一些成本,很多時候命名反而成了一件難事。

CSS IN JS

css in js 的方案一開始是由 非死book 工程師 vjeux 在一次分享中提出的,針對于 CSS 在React開發中遇到的各種問題,隨后社區涌現了各種各樣的方案。

雖然以上模塊化的命名約定可以解決風格上的問題,但正如上面而言,也引入一些成本。而對于一些高復用的組件,使用以上高度語義化的方案是個很好的選擇,這種成本是必需的,但對于沒有復用的業務組件來說,顯然這種命名的成本大于收益,特別是在多人協作時候,另外面對現代前端框架的發展,純靠 CSS 方案并不能很好的解決。

CSS modlue

CSS module 不同于 vjeux 的完全放棄 CSS,它只是選擇了用 js 來管理樣式與元素的關聯, CSS Module 通過為每個本地定義的類名動態創建一個全局唯一類名,然后注入到UI上,實現編寫樣式規則的局部模塊化。

css-loader 內置支持 css-module ,只需設置下查詢參數,即可在 JS 中使用CSS文件的導入:

{
  loader: 'css-loader',
  query: {
    module: true,
    localInentName: '[name]__[local]--[hash:base64:5]'  //  
  }
}

在 JS 中導入 css 文件,最終得到的其實是一個經過 CSS 文件進過 parse 后生成的類名映射對象 {[localName]: [hashed-Name], ....}

// Header.jsx
import style from './Header.css'
...
console.log(style) //  {header: 'Header__header--3kSIq_0'}
export default () => <div className={style.header}></div>

同時 CSS 文件也會被編譯成對應的類名

.Header__header--3kSIq_0- {}   // from Header.css  .header{}

從開發體驗上看, CSS-Module 這種做法讓開發者不必在類名的命名上小心翼翼,直接使用隨機編譯生成唯一標識,讓類名的成為局部變量成為了可能。但同時因為也因為隨機性,失去了通過此局部類名實現樣式覆蓋的可能性,覆蓋時不得不考慮使用其他選擇器(如屬性選擇器)。對于復用的組件而言,靈活性是必不可少的,這種局部模塊化方案并不適合這種高度抽象復用的組件,而對于一次性業務組件確實能夠提升開發效率。

同時 CSS module 還支持使用 composes 實現CSS代碼的組合復用。

/* button.css */
.base{}
.normal {
composes: base
...
}

// button.jsx
import style from './button.css'
export default () => <button className={style.normal}>按按</button> // <button class="button__base--180HZ_0 button__normal--x38Eh_0">按按</button>

當然 CSS-module 還可以配合各種預處理器一起使用,只需在 css-loader 之前添加對于的 loader ,但是在編寫的時候要注意 CSS-module 的語法要在處理器之后合法。實際使用中,對于 CSS 代碼的解耦,如果引入了預處理器,代碼文件的模塊化就不建議使用 composes 來解決。

styled-components

styled-components 也是一個完全的 css-in-js 方案,先看語法:

// button
import styled from 'styled-compenents'
const Button = styled.button`
  padding: 10px;
  ${props => props.primary ? 'palevioletred' : 'white'};
`
<Button>按鈕</Button>
<Button primary>按鈕</Button>

其編譯后也是如同 CSS-in-module 一樣,隨機混淆生成全局唯一類名,對應生成CSS文件。styled-component 的核心是“樣式即組件”,將字符串解析成CSS,并創建對應該樣式的 JSX 元素,而有著JS強大的編程能力,完全可以勝任類似于,同時讓組件樣式與組件邏輯耦合在一起,真正做到組件緊耦合少依賴。當然有些開發不喜歡這種耦合,也完全可以將樣式組件和邏輯組件分離,而在JS中分離代碼本身也是件易事。

當然, styled-components 在真正的應用并不僅僅如此,它完全是一個完備的樣式解決方案,有著如擴展、主題、服務端渲染、Babel 插件、React-Native 等等一系列支持,也深受一些開發者歡迎。這里比較有趣是看似奇怪的語法形式,其實是 ES6 中模板字符的特性。

styled-components 本身是React社區針對于JSX產生的一種方案,當然在 Vue 中通過 vue-styled-components 也能使用該功能,但是使用體驗一般,無論是在模板里還是在JSX中,使用組件都需在提前聲明并注入到組件構建參數中,十分繁瑣,而且不同于 React 純 JSX 的組件渲染語法, Vue 中并不能對既有的組件使用 styled 語法。

但另一方面,將 CSS 完全寫在 JS 中,社區里中也有很多人持反對態度, react-css-modules 的作者就專門 發文 表示反對 styled-component 這種完全拋棄 CSS 文件的開發模式。

總結

我們在開發的之前,面對各種技術方案,一定要選取并組合出最適合自己項目的方案,是選用傳統的CSS預處理器, 還是選用 PostCSS? 是全局手動維護模塊,還是完全交個程序隨機生成類名?都需要結合業務場景、團隊習慣等等因素。另一方面,CSS 本身并無編程特性,但在其工程化技術的發展中缺不乏很多優秀的編程思想,無論是自定義的 DSL 還是基于 JS,這其中帶給我們思考的正是“編譯思想”。

 

來自:https://zhuanlan.zhihu.com/p/32117359

 

 本文由用戶 idsv0427 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!