kissyLite的包管理和无需预先注册的带依赖关系模块异步加载

现在很多同学们都在代码里模拟YUI3实现JavaScript异步模块化加载,也就是一个add,一个use..
YUI3里对模块的加载是串行模式,当没有combo配合时,看起来很不美观.
今天army8735同学提到这个事儿,发现还真是很多同学注意到了这一点.kissy本身的add use就是并行,也有同学对YUI3做了改进.

而进来我正在开发kissylite,是kissy的一个支持有限方法的子集,目标是用1.5k代码支持包管理和模块化管理,也遇到了这个问题.不如拿出来一起讨论.

我觉得kissy和yui3的问题是模块必须先注册,这样扩展性有一定的局限.
kissylite引入了包的概念,每一个包对应一个codebase,通过模块名就完全确定js所在路径.
那么在一个包内新增了模块之后,种子文件是不需要修改即可直接使用的.

这样增加循环依赖的风险a->b b->c c->a..
虽然这样循环了明显是程序逻辑错误,但是如果不加以防范,合作开发,多次上线,死循环漏到线上那打击是致命的..
我的解决办法很土..在env中有一个对象记录x=模块依赖哪些,又支持哪些..当出现x依赖x的时候就抛一个error出来好了.

以a->b b->c c->a为例use("a")时:
首先载入a,Env中:
依赖关系方面a->b,
支持关系方面b->a

然后载入b,Env中:
依赖关系方面:b->c,在通过上面的支持关系可以得到a同样依赖c,即a->b,c
支持关系方面:c->b;a,b->a

最后载入c,Env中:
依赖关系方面:c->a,再通过现有的支持关系能得到a->b,c,a 矛盾出现a依赖自己,报个错吧:循环依赖.

这种算法效率是很低的,但是先粗暴的实现一下用着,每次add可能都要访问相关依赖关系对象和支持关系对象,毕竟kissylite目标是给页面中的广告,这类小应用的,模块不会太多.算抛砖吧,求更好的算法.

另一方面这里看到a b c的加载是串行的,在开发完成后,每次use可以自动给出一个use的并行优化方案.
比如use("a"),在use成功后,可以控制台log一下,为了更好的并行加载模块,你应该use("a,b,c")..这样就可以边开发,边优化.

kissylite还解决了一个问题,就是add的时候永远不attach.
steve blog:Evolution of Script Loading给出的mobile上js加载方案,看到代码从按需加载进化到了按需执行的阶段.
对于模块化js简单一些,只要不先attach,只是一个单纯简单的add,耗费的精力少的多了.

大家可能担心use时候attach是不是慢了些,这个同样可以边开发边优化.
在开发一个页面,可以把常用的功能都跑一边,Env中包含你所有使用过的包.
那么就可以在domready后,不忙的时候把这些先use一遍...
这就有点 facebook primer的意思了,domready之前按需执行,domready之后选择性预热.

这里也说说异步执行单元的串行和并行. 这个老道正好在velocity china提到.
异步执行单元:asyncUnit(args,callback).所有可能需要等待一段时间才能收到结果的函数都可以叫异步单元.比如getScript(url,callback),use(mods,callback),还有一些动画回调,都是异步执行单元.

我们常常遇到很多异步执行单元,比如use(mods)这个场景.
按程序的业务逻辑,如果这些异步单元需要串行,则会遇到多层复杂的callback嵌套.
a(a1,function(resA){
    b(b1,function(resB){
        c(c1,function(resC){
          ///your code
        })
    })
})

这种代码不美观,越向内运行时scope链越长,性能也不好.

而按照程序业务逻辑,如果多个异步单元需要并行,则会遇到每次手动监测其他单元执行是否完成的情况
a(a1,function(resA){
    window.resA = resA
    if(window.resB){
       c();
    }
});
b(b1,function(resB){
    window.resB = resB
    if(window.resA){
      c();
    }
})
function c(){
   //your code run after resA & resB returend 
}

这种代码同样不美观,通过写死判断条件if(resA/B)每次手动检查其他相关异步单元执行情况.

kissylite增加了方法multiAsync(asyncers,callback),因为use时并发的情况较多,先解决并发这个问题,计划再增加一个参数,解决串行异步单元的问题..

最后附一下kissylite的readme,以及googlecode上临时预览地址: http://tbad.googlecode.com/svn/trunk/kslite/testcase/test_loader.html

kslite为kissy的仅支持有限方法的子集:
这些方法包括log,mix,clone,extend,add,use,getScript,substitute
kslite为所在页面引入KSLITE全局对象.

相比于kissy,发生变化的方法如下:
add:任何时候只add,不attach.
use:不用add即可直接use,详见下面的包和模块管理.

包和模块管理:
模块名由包名,路径,文件名.三部分构成
如{packagename}-{path_0}-...-{path_n}-{filename}
包类似*.jar,每个包对应一个codebase即classesroot.在S.config.lt_pkgs中配置
是一个http地址,如果没有则以为kslite.js所在地址为base.
比如:
    S.Config.lt_pkgs={
        inf:"http://a.alimama.cn/kslite/",
        test:"http://demo.taobao.com/tbad/kslite"
    }
模块"inf-a"对应地址 http://a.alimama.cn/kslite/inf/a.js
模块"test-t-1"对应地址 http://demo.taobao.com/tbad/kslite/test/t/1.js
这样根据模块名称即可定位模块地址,所以不需要add预先注册模块即可直接use.
add不执行attach.只有第一次use的时候才执行attach.
add同样支持require.可以在载入js后根据require串行加载更多模块.(已处理循环引用风险,办法很土.)
暂时不支持use外部JS文件,如use("jquery.js");

性能方面考虑:
这种模式只要use中包含的模块足够,满足所有依赖,则可以保证所有模块并行load,否则可能存在串行情况.
开发时使用小模块模式,每一个正式产出,比如生成广告投放用inf.js,广告展现用showad.js应该手动combo.
因为在use时会按照require顺序attach,所以手动combo的代码不需要关心模块间顺序.

其他配置项:
kslite相关配置项,在局部变量kslite_config中,之后mix入S.Config
lt_b:kslite的base,推荐每个产出写死一个kslite的base地址,而不是通过currentScript获得.因为kslite不一定作为<script>节点静态引入
lt_pkgs:包路径信息,如上.
lt_t:时间戳比如20101129.js
lt_v:版本 如1.1.5 计划沿用kissy版本.

关于与kissy兼容性:
在页面存在同一版本的Kissy实例时,S.app("KSLITE")构建.
同时根据当前kissy的add模式,需要额外生成一段代码,将所需模块预先注册一下.

你可能感兴趣的:(JavaScript,Module,require,CommonJS)