`
limu
  • 浏览: 321199 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

CSSLoader实现思路以及与JSLoader的异同

阅读更多
童鞋们新年好,2012年只发了两篇水文,就这么过去了,实在汗颜。话说大家都转移阵地去微博和github讨论问题了,谁让iteye还不支持markdown呢

今天说说我不熟悉的领域CSS,因为手里的项目实在需要,所以最近花了不少精力在我有限的CSS知识范围内寻找解决方案,也经过和很多同事讨论碰撞,不如记录一下。

CSS也需要模块化
CSS也需要模块化,这个其实不需要说太多,大家也都是这么做的。在开发阶段css伴随这个各个模块存在。隐约记得还有不少关于面向对象CSS的讨论,也有Less和SASS这种加强CSS结构化的工具。

而在上线前往往需要将各处散落的CSS文件统一合并,或更智能的实时combo。所以今天讨论的CSSLoader是开发时使用。

CSS模块间存在依赖关系,但用JS模块管理方式一并管理不够恰当
CSS模块间存在依赖关系,常见的情况是所有css都依赖框架的reset.css,然后可能各个css也都依赖项目通用css如project.css。

但是用js模块管理方式来管理css模块是不够合适的,有以下一些问题:
1. 当前的JSLoader,往往只支持,一个js模块依赖若干css模块,并非真正的支持css间的依赖。
2. css文件内,不支持js语法,所以无法描述它的依赖。内置的@import有它的问题,稍后讲讲。

在讨论中,我们发现还是从CSS的特点出发,为它量身定制Loader解决方案为佳。


CSS特点是有层次,那么理想的项目CSS规划怎样
CSS的全称是层叠样式表,这其实说明了CSS的特点是有层次的,而这一点在JS中表现的并不明显,特别大家通过模块化屏蔽了全局变量相互覆盖带来的影响之后。

CSS的层次的特点是,下层的CSS规则,可以覆盖上层的CSS规则,所以我觉得网页理想的CSS规划是每一个CSS模块都能明确其所属层次。

这样的网页CSS层次如下图所示,这也是我们当前项目中的结构:



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的模块依赖是一张没有闭环即可的图(闭环==循环引用)。

简单总结下理想的CSS规划策略

1. 为网页的css划分层次,让每个css模块有明确的层次属性
2. 同一层次内的不同css模块,相互间不应该存在冲突的定义
3. 下层css模块只可以覆盖其所依赖的上层css模块中的部分规则


CSSLoader的要点

精确的的onload事件
我们必须保证上层css加载进来、规则完全应用好之后才能加载下层css。这样我们就需要一个精确的onload事件,在早期的getScript方法加载css文件时,只要把请求发了就立即触发onload,以草草达到和js模块一致的回调。而最近这方面的研究已经比较完善,kissy1.2以及seajs的很早的版本就支持真正的css文件onload回调。推荐seajs的做法,因为404时同样会触发回调。代码在这里https://github.com/seajs/seajs/blob/master/src/util-fetch.js#L82,测试用例在这里http://seajs.org/tests/issues/load-css/test.html,感谢玉伯。

我们注意到这里的css加载实现是动态构建link节点,但是在IE下有效的link节点有个数限制,所以方案并不完美。那么其实其他所有为页面引入css的方法都可以用,但前提是需要有靠谱的回调。甚至可以研究下,让css文件也能将载入和规则应用分离开的高级样式引入方式。

并发处理
一定存在同时初始化多个widget的情况,那么并发加载inst1-1-1和inst1-1-2时,要确保其共同的上层,如projcet1.css最先加载并且不能重复加载。在这一点上可以,参照JSLoader的实现模式,将已经载入的模块记录在案。这一点也引出了@import的问题,如果两个inst里边都写了@import project1.css,那么可能会出现inst1-1-1覆盖projcet1的规则又被覆盖回去的情况。

更进一步,当一个加载任务队列正在进行时,又开启了另一个加载任务队列,如果我们能按照css模块的层次顺序,将两个队列merge成一个队列再继续执行,看起来会更好些。

API示意
简单构想了一下扩展后的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。

脆弱的方案
这绝对不是一个完美的解决方案,当前的实现方案基于“理想的CSS规划策略”,需要分层、同层次不冲突、仅向依赖的上层覆盖,这三点完全做到,而这里最大的挑战似乎是如何技术化的保障同一层次的css不会出现冲突。

在天然的浏览器环境没有很好的提供我们需要的feature时,等待它进化还是不现实的,很多时候就是靠我们能掌控全局这一优势,在不同环节各退一步,达成一个可以勉强运转体系。

好在这个方案是在开发时运转,而且明确的css规划也为真正线上产品的css打包或自动combo提供了便利。

简单发散一下

让层次描述更灵活
约定4个层次,还是太死板了,我们可以参照zIndex的设计,让层次数值之间存在大量空隙供扩展。

其他实现方案
可能有把css写在js里的方案,有基于less、sass扩展的方案,有开发时跑一个本地服务的方案(类似grunt)。这些都好,但对于如今我们手上的项目来说不够敏捷。其实没准有人已经实现过,欢迎知道的同学推荐。


最后
真的不太会CSS,如果有误请帮忙指出,如果有好的方案或者相关的研究都推荐过来吧,谢谢~
  • 大小: 98.9 KB
分享到:
评论
2 楼 gxll1314 2013-01-10  
关于CSS的加载问题,大家都喜欢构造link节点来加载CSS,对于这个方案我觉得唯一的不好就是对于load加载的精准性。其实可以将CSS看作文本节点,使用ajax作为text获取CSS,将CSS以style的形式append到页面,这样可以很方便的控制加载。
1 楼 Hafeyang 2013-01-10  
我用php写的一个模块加载器 https://github.com/hafeyang/smartcomb

相关推荐

Global site tag (gtag.js) - Google Analytics