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