CSSLoader實現思路以及與JSLoader的異同

童鞋們新年好,2012年只發了兩篇水文,就這麼過去了,實在汗顏。話說大家都轉移陣地去微博和github討論問題了,誰讓iteye還不支持markdown呢 :lol: 。

今天說說我不熟悉的領域CSS,因爲手裏的項目實在需要,所以最近花了不少精力在我有限的CSS知識範圍內尋找解決方案,也經過和很多同事討論碰撞,不如記錄一下。

[b][size=large]CSS也需要模塊化[/size][/b]
CSS也需要模塊化,這個其實不需要說太多,大家也都是這麼做的。在開發階段css伴隨這個各個模塊存在。隱約記得還有不少關於面向對象CSS的討論,也有Less和SASS這種加強CSS結構化的工具。

而在上線前往往需要將各處散落的CSS文件統一合併,或更智能的實時combo。所以今天討論的CSSLoader是開發時使用。

[b][size=large]CSS模塊間存在依賴關係,但用JS模塊管理方式一併管理不夠恰當[/size][/b]
CSS模塊間存在依賴關係,常見的情況是所有css都依賴框架的reset.css,然後可能各個css也都依賴項目通用css如project.css。

但是用js模塊管理方式來管理css模塊是不夠合適的,有以下一些問題:
1. 當前的JSLoader,往往只支持,一個js模塊依賴若干css模塊,並非真正的支持css間的依賴。
2. css文件內,不支持js語法,所以無法描述它的依賴。內置的@import有它的問題,稍後講講。

在討論中,我們發現還是從CSS的特點出發,爲它量身定製Loader解決方案爲佳。


[b][size=large]CSS特點是有層次,那麼理想的項目CSS規劃怎樣[/size][/b]
CSS的全稱是層疊樣式表,這其實說明了CSS的特點是有層次的,而這一點在JS中表現的並不明顯,特別大家通過模塊化屏蔽了全局變量相互覆蓋帶來的影響之後。

CSS的層次的特點是,下層的CSS規則,可以覆蓋上層的CSS規則,所以我覺得網頁理想的CSS規劃是每一個CSS模塊都能明確其所屬層次。

這樣的網頁CSS層次如下圖所示,這也是我們當前項目中的結構:

[img]http://dl.iteye.com/upload/attachment/0079/0897/5c1b0831-2494-3901-804a-77be162fdbcc.png[/img]

1. reset.css : 底層類庫提供的通用
2. project.css :項目內通用的css,在我們的項目中可能存在其他項目的project.css,因爲我希望不同項目中間的組件可以共享
3. widget.css :組件的通用css
4. widgetInstance.css :組件實例特有的css

可以看出在css分層次規劃的背景下,css文件的依賴次序是固定的,4 -> 3 -> 2 -> 1 。 回過頭來css的加載順序應該是1、2、3、4,必須串行。

我們看到這樣的規劃下css模塊是一棵樹,如果projcet2.css依賴widget1-1.css顯然是不合理的。而js的模塊依賴是一張沒有閉環即可的圖(閉環==循環引用)。

簡單總結下[b]理想的CSS規劃策略[/b]:
[color=darkblue]
1. 爲網頁的css劃分層次,讓每個css模塊有明確的層次屬性
2. 同一層次內的不同css模塊,相互間不應該存在衝突的定義
3. 下層css模塊只可以覆蓋其所依賴的上層css模塊中的部分規則
[/color]

[b][size=large]CSSLoader的要點[/size][/b]

[b]精確的的onload事件[/b]
我們必須保證上層css加載進來、規則完全應用好之後才能加載下層css。這樣我們就需要一個精確的onload事件,在早期的getScript方法加載css文件時,只要把請求發了就立即觸發onload,以草草達到和js模塊一致的回調。而最近這方面的研究已經比較完善,kissy1.2以及seajs的很早的版本就支持真正的css文件onload回調。推薦seajs的做法,因爲404時同樣會觸發回調。代碼在這裏[url]https://github.com/seajs/seajs/blob/master/src/util-fetch.js#L82[/url],測試用例在這裏[url]http://seajs.org/tests/issues/load-css/test.html[/url],感謝玉伯。

我們注意到這裏的css加載實現是動態構建link節點,但是在IE下有效的link節點有個數限制,所以方案並不完美。那麼其實其他所有爲頁面引入css的方法都可以用,但前提是需要有靠譜的回調。甚至可以研究下,讓css文件也能將載入和規則應用分離開的高級樣式引入方式。

[b]併發處理[/b]
一定存在同時初始化多個widget的情況,那麼併發加載inst1-1-1和inst1-1-2時,要確保其共同的上層,如projcet1.css最先加載並且不能重複加載。在這一點上可以,參照JSLoader的實現模式,將已經載入的模塊記錄在案。這一點也引出了@import的問題,如果兩個inst裏邊都寫了@import project1.css,那麼可能會出現inst1-1-1覆蓋projcet1的規則又被覆蓋回去的情況。

更進一步,當一個加載任務隊列正在進行時,又開啓了另一個加載任務隊列,如果我們能按照css模塊的層次順序,將兩個隊列merge成一個隊列再繼續執行,看起來會更好些。

[b][size=large]API示意[/size][/b]
簡單構想了一下擴展後的API,一串CSS加載任務發起還是由JS模塊承擔。

基於Kissy的話,需要add方法,爲js模塊增加cssRequires配置項,區別於requires,cssRequires數組內的文件需要按順序串行加載,能夠標識css模塊的層次更好。


KISSY.add('mywidget',function(){
//...
},{
requires:['mod-a','mod-b'],
cssRequires:['reset.css:1','projcet.css:2','widget.css:3','instance.css:4']
});


在KISSY.use的時候原有邏輯負責js的加載,同時丟一個任務給新實現的cssLoader,去按層次加載css。

[b][size=large]脆弱的方案[/size][/b]
這絕對不是一個完美的解決方案,當前的實現方案基於“理想的CSS規劃策略”,需要分層、同層次不衝突、僅向依賴的上層覆蓋,這三點完全做到,而這裏最大的挑戰似乎是如何技術化的保障同一層次的css不會出現衝突。

在天然的瀏覽器環境沒有很好的提供我們需要的feature時,等待它進化還是不現實的,很多時候就是靠我們能掌控全局這一優勢,在不同環節各退一步,達成一個可以勉強運轉體系。

好在這個方案是在開發時運轉,而且明確的css規劃也爲真正線上產品的css打包或自動combo提供了便利。

[b][size=large]簡單發散一下[/size][/b]

[b]讓層次描述更靈活[/b]
約定4個層次,還是太死板了,我們可以參照zIndex的設計,讓層次數值之間存在大量空隙供擴展。

[b]其他實現方案[/b]
可能有把css寫在js裏的方案,有基於less、sass擴展的方案,有開發時跑一個本地服務的方案(類似grunt)。這些都好,但對於如今我們手上的項目來說不夠敏捷。其實沒準有人已經實現過,歡迎知道的同學推薦。


[b][size=large]最後[/size][/b]
真的不太會CSS,如果有誤請幫忙指出,如果有好的方案或者相關的研究都推薦過來吧,謝謝~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章