CSS工程化實踐成果分析
作為Web開發(fā)的重要組成部分,CSS技術演進也在推動著前端工程化不斷進步。本文將從CSS模塊化、namespace約束、CSS in JS方案三個方面逐步深入解讀CSS在工程化領域取得的成果。
CSS技術的演進
CSS是Web開發(fā)中不可或缺的一部分,在前端工程化不斷進步的今天,一方面CSS特性隨著規(guī)范的升級越來越豐富,另一方面,前端業(yè)務復雜性的增加帶來的工程愈加龐大, 驅使著開發(fā)者不斷尋找CSS工程化的最佳實踐。
Web開發(fā)模塊化趨勢
不可否認, 無論從現(xiàn)代前端框架(React、 Vue、Angular、 Polymer等), 還是從W3C的Web Components草案來看,組件化已經(jīng)是前端開發(fā)的主流之選和未來的發(fā)展方向,正如在Reddit上有網(wǎng)友說道“Facebook.com’s codebase includes over 20,000 components”。 廣義上看,所有頁面上都可以被劃分成一個個組件,相對于過去以網(wǎng)頁作為開發(fā)單位,以組件為單位開發(fā)有著可復用、可擴展等一系列有利于項目工程化的優(yōu)點。
在這種組件化趨勢的背景下,CSS模塊化也漸漸有著各種嘗試。
預處理與后處理
預處理
比較流行的CSS預處理器有Sass、Less和Stylus,CSS預處理器的出現(xiàn)主要因CSS缺少編程語言的靈活性而生,是引入了一些編程概念而生的DSL,開發(fā)者編寫簡潔的語義化DSL代碼,由預處理器編譯成CSS。
以Sass為例,該預處理器支持.scss、.sass文件類型,其語法支持變量、選擇器嵌套、繼承(extend)、混合(mixin)和一些邏輯語句,同時還支持跨文件的導入功能,因而使得開發(fā)者能夠很好地使用編程思想書寫樣式。
從實際使用情況來看,幾個預處理器各有優(yōu)缺點,從社區(qū)活躍度上看Sass》Less》Stylus。Sass是三個中間最早也是最成熟的,因而有著很多開源積累和很好編程范式,像內置了很多Sass函數(shù)的Compass框架,就是很好的一個例子。Less相對于Sass的優(yōu)點在于十分的輕量,也完全兼容CSS,但另一方面可編程能力不如Sass,Bootstrap最新版本的CSS預處理器也從Less換成Sass。Stylus來源于Node社區(qū),使用體驗上并不輸給Sass和Less,無論是編譯速度還是語法范式,個人看來,Stylus在某種程度上更加優(yōu)于其他兩個。
后處理
后處理器是對原生CSS進行處理并最終生成CSS的處理器,廣義上還是個預處理器,與上面提到的預處理器不同的是,它處理的對象是標準CSS(如圖1所示),比較典型的后處理工具有以下幾種。
clean-css:壓縮CSS
AutoPrefixer:自動添加CSS3屬性各瀏覽器的前綴
Rework:取代Stylus的插件化框架
PostCSS
PostCSS
PostCSS最初是從AutoPrefixer項目中抽象出來的框架,它本身并不對CSS做具體的業(yè)務操作,只是將CSS解析成抽象語法樹(AST),樣式的操作由之后運行的插件系統(tǒng)完成(如圖2所示)。正如其本身所言Transforming styles with JS plugins”。
更多時候我們在討論PostCSS的時候,并不止停留在它是解析CSS的核心工具,更包括它創(chuàng)建的插件系統(tǒng),而今PostCSS最為吸引開發(fā)者的正是其擴展性較強的插件系統(tǒng)和豐富的插件支持。
常用的插件有:
Autoprefixer:自動補全CSS屬性兼容性前綴
postcss-cssnext:使用最新的CSS語法
postcss-modules:組件內自動關聯(lián)樣式至選擇器
Stylelint:CSS語法檢查器等
如果已有的插件不能滿足現(xiàn)有的需求,完全可以手寫一個插件:
// 官方示例rem轉pxvarcustom = function(css, opts){css.eachDecl( function(decl){decl.value = decl.value.replace( /\d+rem/, function(str){return16* parseFloat(str) + “px”; }); }); };
當然,PostCSS的解析并不局限于CSS,結合它提供的自定義語法解析接口,完全可以定義自己的語法。其實類似于postcss-scss的插件社區(qū)已經(jīng)有很多了,使用這些插件,可以將原來基于Sass、Less等預處理器的代碼遷移至PostCSS。相對于傳統(tǒng)的預處理器,PostCSS這種開放平臺型的體系,不拘束開發(fā)者的開發(fā)方式,同時也促進了更多對于CSS解決方案的探索。
回過頭來看,為什么會有CSS預處理操作后的處理操作?其實主要的原因在于前端項目的膨脹使得用傳統(tǒng)手工編寫并維護CSS變得很不堪,根本原因則是CSS缺少編程語言特性,要做到CSS代碼的模塊化以及高復用的抽象處理,就必須引入一些編程的思想。相對于Java標準推進以及基礎設施的完備,CSS在編程方面的探索更多來自于社區(qū),也并無統(tǒng)一的事實標準,這也是CSS發(fā)展落后于Java的原因。
namespace約束
一方面我們需要關注技術能夠帶來代碼上的模塊化,另一方面我們又要思考如何使用一個良好的風格架構起項目中的CSS。CSS除了代碼外,另一個很重要的就是CSS選擇標記。而CSS選擇器的命名空間是全局的,并沒有局部的概念,因而如何利用好這個全局的空間,選擇良好的結構風格,也是在開發(fā)過程中必須考慮的。
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; }
根據(jù)此原則,我們需要對公用的皮膚進行提取并分離,如下。
.button{ padding:10px; }.widget{ overflow:auto; }.skin{ box-shadow:rgba(0, 0, 0, .5)2px 2px5px; }
分離容器和內容(Separate Container an Content)
打破容器內元素對于容器的依賴,元素樣式應該獨立存在。如下面示例。
《divclass=“container”》《h2》xxx 《/h2》《/div》.container h2 { 。。.}
上面的h2元素依賴于父元素container,對應此原則,h2元素需要使用一個單獨的選擇器,如下。
《divclass=“container”》《h2class=“category”》xxx 《/h2》《/div》.category { 。。.}
從實踐中看出,使用OOSCC范式,遵守了DRY的原則,能夠大量減少重復的樣式代碼,提高代碼復用;同時,視覺元素可以靈活組合各個類名,展示不同的效果,豐富的類名也同時使得元素有著更好的可讀性;另一方面,由于容器和內容的分離,CSS完成了與HTML結構解耦。
但同時也會帶來一些缺點,抽象復用會使class越來越多,極端情況下可能會產(chǎn)生很多原子類,這對于那些偏向于“單一來源原則”的開發(fā)者來說并不受歡迎。
SMACSS
SMACSS(Scalable and Modular Architecture for CSS)即模塊化架構的可擴展CSS,它主要將規(guī)則分為五類。
基礎(Base)
tag select的樣式, 定義最基礎全局樣式, 如CSS REST。
html, body, form{ margin:0; padding:0; }a{ color:#039; }a:hover{ color:#03C; }
布局(Layout)
將頁面分為各個區(qū)域的元素塊。
.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{}
狀態(tài)(State)
狀態(tài)class一般通過Java動態(tài)掛載到元素上,可以根據(jù)狀態(tài)覆蓋元素上特定屬性。
.tab { background-color: purple; 。。.} .is-tab-active { background-color:white; }
主題(Theme)
可選的視覺外觀。一般根據(jù)需求有顏色、字體、布局等等,實現(xiàn)是將這些樣式單獨抽出來,根據(jù)外部條件(data屬性、媒體查詢等)動態(tài)設置。
SMACSS的主要優(yōu)點在于按照不同的業(yè)務邏輯,將整個CSS結構化分更加細致,約束好命名,最小化深度,在編寫的時候,使用SMACSS規(guī)范能夠更好地組織CSS文件結構和class命名。
BEM
BEM即Block Element Modifier,類名命名規(guī)則為Block__Element–Modifier。
Block所屬組件名稱
Element組件內元素名稱
Modifier元素或組件修飾符
其核心思想就是組件化。首先一個頁面可以按層級依次劃分出多個組件,其次就是單獨標記這些元素。BEM通過簡單的塊、元素、修飾符的約束規(guī)則確保類名的唯一,同時將類選擇器的語義化提升了一個新的高度。
《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通過簡單的命名規(guī)則使得關聯(lián)類名元素語義性、可讀性更強,有利于項目管理和多人協(xié)作。同時BEM方案中并沒有嵌套,所有類名最淺深度,并不會出現(xiàn)嵌套過深難以覆蓋的情況,易于維護、復用。
另一方面,BEM強調單一職責原則和單一樣式來源原則,意味著傳統(tǒng)純手工CSS可能會產(chǎn)生大量重復的代碼,但是結合各種CSS預處理和PostCSS就可以很好避免問題的產(chǎn)生。另外,雖說原則簡單,但在實際使用中,維護BEM的命名確實需要一些成本,很多時候命名反而成了一件難事。
CSS in JS
CSS in JS方案一開始是由Facebook工程師Vjeux在一次分享中提出的,針對CSS在React開發(fā)中遇到的各種問題,隨后社區(qū)涌現(xiàn)了各樣方案。
雖然以上模塊化的命名約定可以解決風格上的問題,但正如上文而言,也引入一些成本。而對于一些高復用的組件,使用以上高度語義化的方案是個很好的選擇,這種成本是必需的,但對于沒有復用的業(yè)務組件來說,顯然這種命名的成本大于收益,特別是在多人協(xié)作時候。另外,面對現(xiàn)代前端框架的發(fā)展,純靠CSS方案并不能很好地解決。
CSS Modlue
CSS Module不同于Vjeux完全放棄CSS的做法,它只是選擇了用Java來管理樣式與元素的關聯(lián),CSS Module為每個本地定義的類名動態(tài)創(chuàng)建一個全局唯一類名,然后注入到UI上,實現(xiàn)編寫樣式規(guī)則的局部模塊化。
css-loader內置支持CSS Module, 只需設置下查詢參數(shù), 即可在Java中使用CSS文件的導入。
{ loader:‘css-loader’, query: { module: true, localInentName: ‘[name]__[local]--[hash:base64:5]’// } }
在Java中導入CSS文件,最終得到的其實是一個CSS文件經(jīng)過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{}
從開發(fā)體驗上看,CSS-Module這種做法讓開發(fā)者不必在類名的命名上小心翼翼,直接使用隨機編譯生成唯一標識,讓類名成為局部變量成為了可能。但同時也因為隨機性,失去了通過此局部類名實現(xiàn)樣式覆蓋的可能性,覆蓋時不得不考慮使用其他選擇器(如屬性選擇器)。對于復用的組件而言,靈活性是必不可少的,這種局部模塊化方案并不適合這種高度抽象復用的組件,而對于一次性業(yè)務組件確實能夠提升開發(fā)效率。
同時CSS Module還支持使用composes實現(xiàn)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方案,先看語法。
// buttonimport styled from‘styled-compenents’constButton = styled.button` padding: 10px; ${props =》 props.primary ? ‘palevioletred’: ‘white’}; ` 《Button》按鈕 《/Button》《Buttonprimary》按鈕 《/Button》
其編譯后也是如同CSS-in-module一樣,隨機混淆生成全局唯一類名,對應生成CSS文件。styledcomponent的核心是“樣式即組件”,將字符串解析成CSS,并創(chuàng)建對應該樣式的JSX元素,它有著Java強大的編程能力,完全可以勝任,同時讓組件樣式與組件邏輯耦合在一起,真正做到組件緊耦合少依賴。當然有些開發(fā)不喜歡這種耦合,也完全可以將樣式組件和邏輯組件分離,而在Java中分離代碼本身也是件易事。
當然,styled-components真正的應用并不僅僅如此,它完全是一個完備的樣式解決方案,有著如擴展、主題、服務端渲染、Babel插件、ReactNative等一系列支持,也深受一些開發(fā)者歡迎。這里比較有趣的是看似奇怪的語法形式, 其實是ES6中模板字符的特性。
styled-components本身是React社區(qū)針對JSX產(chǎn)生的一種方案,當然在Vue中通過vue-styledcomponents也能使用該功能,但是使用體驗一般,無論是在模板里還是在JSX中,使用組件都需提前聲明并注入到組件構建參數(shù)中,過程十分繁瑣,而且不同于React純JSX的組件渲染語法,Vue中并不能對既有的組件使用styled語法。
但另一方面,將CSS完全寫在Java中,社區(qū)里中也有很多人持反對態(tài)度,react-css-modules的作者就專門發(fā)文表示反對styled-component這種完全拋棄CSS文件的開發(fā)模式。
總結
我們在開發(fā)之前,面對各種技術方案,一定要選取并組合出最適合自己項目的方案,是選用傳統(tǒng)的CSS預處理器,還是選用PostCSS?是全局手動維護模塊,還是完全交給程序隨機生成類名?都需要結合業(yè)務場景、團隊習慣等因素。另一方面,CSS本身并無編程特性,但在其工程化技術的發(fā)展中不乏很多優(yōu)秀的編程思想,無論是自定義DSL還是基于Java,這其中帶給我們思考的正是“編譯思想”。
非常好我支持^.^
(0) 0%
不好我反對
(0) 0%