QWrap简介之:apps果实篇之:种子
就像是一棵树有很多果实一样,QWrap也有很多apps,本文讲解种子应用。
“种子”是沿用YUI3的说法,种子应用是解决模块加载问题的应用,包括:模块预加载、异步按需加载、模块应用。
或许有些同学对异步加载模块不大熟悉,没关系,我们先感性的看一下这段代码
1。如果用户浏览此网页时,只加载了一个体积很小的seed_youa.combo.js这个js(其实就是seed_youa.js),这个js经yui压缩后的大小为4K。
2。当用户点击按钮时,按钮对应的js用到jQuery与YouaCore,点击后才会去加载那两个jQuery与YouaCore对应的js,之后才有运行效果。
3。第二次点击按钮,由于前面已经加载过js,所以这次会直接运行按钮事件。
分析一下以上三步,分两种情况。
A。对于只进行了第一步操作的用户,他们用seed_youa.js替代了jQuery与YouaCore两个js的流量(这是好处甲);
B。对于进行过第二步操作的用户,由于以上流量的节约,会让用户提前能够点击按钮(这是好处乙),点击后异步加载需要的js,加载完后事件才运行,即点击与运行存在一个时间差(这是坏处甲),并且三个js都用到了,相对于传统写法,http多了一个seed_youa.combo.js(这是坏处乙)。
另外,对于页面程序员来说,他在写按钮事件时,只需要知道自己的这段js用到了哪些模块,而不用关心这些模块是否已经加载(这是好处丙),不过,他引用模块的方式有了一些变化(少键入html代码,多键入js代码)。
好处甲、好处乙、好处丙、坏处甲、坏处乙,还有些没有分析到的好处与坏处,反正有利有弊。
那么,在什么情况下利大于弊、什么情况下弊大于利,而我们该如何兴利除弊?
如果这个页面的用户100%不会点击按钮,则几乎没坏处,只有好处甲、好处丙。
如果页面100%的用户会点击按钮,则需要权衡再权衡利弊再想办法。
好的,我们先暂且搁置这些细节,因为以上内容已足够让同学们对“异步按需加载”有了感性的认识。我们把提供这种异步按需加载的js叫种子(seed)。
我们看一下seed_youa.js,它是一个组合js(即前面一篇文章所说的B类apps:开发时可以是多个文件,但上线时会先合并后上线。)
它由三个js组成:
core/core_base.js是QWrap的主干js
core/module.h.js是模块管理器,也是一个Helper,提供三个方法:addConfig、use、provide,代码参见:http://dev.qwrap.com/resource/js/core/module.h.js
youa_modules_config.js是youa项目只所使用到的常用模块配置,通过addConfig进行模块配置,代码如下:
看到这个配置文件的同学也许会问:模块添加得越来越多会怎么办?为什么不像YUI一样,在各个模块的js里进行addConfig,而要集中起来addConfig?
不错,理论上是会越来越大,但是,实际上最大能大到多少?并且不一定是所有的模块配置都要放在这里面。在页面的js里也可以QW.addConfig('MyPageJs','//my.js')的。所谓的太大,只是个理论问题,不是个实际问题。
YUI在各个模块的js里进行add,有好处,也有坏处。
好处就是多了个沙箱机制,沙箱理论看起来很严谨,可以满足YUI的严谨性洁癖----其实我一直没有理解它对实用者都有啥好处。
而坏处,也有很多。例如:
1。需要模块的代码来适应他的加载机制。----例如,jQuery也可以算是个模块,凭什么要让独立的jquery来改代码?
2。假设我改了jQuery的代码来适应这loader,那是不是也损失了jQuery的原来的用法,即预加载模式用法(在HTML的Header里引用jquery.js,在页面直接用$('#id').show())。
3。例如use('Editor',function(){})时,其实由于依赖关系,会按需加载YouaCore,Drag,Panel,Editor四个js,因为依赖关系是在各自的js里,理论上无法将四个请求合并成一个请求,也无法在请求editor.js时就并行请求drag.js。
4。如果客户端缓存了js,我们更新Editor.js后,use('Editor',function(){})无法知道是否需要在editor.js后添加版本号。
5。如果有复合模块,例如core_dom_youa_lazy.js其实是同时拥有很多小模块功能的一个js,那在drap.js里如何定义依赖?
而QWrap的“集中配置”方式,上面几条都不是什么问题。
对于第一条第二条,已经解决,即:addConfig时的loadedChecker参数。
例如,这样配置jQuery:
这个jquery引用的是博客园网站的,我们没有作任何修改。
如果页面已经预加载了jquery,运行QW.use('jQuery',function(){})也不会再次加载jquery.js。
当然,用户还可以保持习惯,用以前的预加载经典用法。
对于第三条合并请求与并行请求,QW目前还没做,不过,只需调整一下module.h.js里的某一段代码即可。在源代码里有说明:
对于第四条缓存,由于是统一配置,所以只需在发布时,在配置文件的js后面加上对应js的md5码的前N位即可。
例如,线上的配置可能是:
页面引用的种子,也是在后面加上它的md5码的前N位。
这样做的好处是:每次上线后,用户也只需下载有过修改的js。
对于第五条的复合模块问题,由于是统一配置,所以根本就不是问题。
说了seed_youa.js的这么多好处,当然还要说明一个限制:项目中需要有一个水平还过得去的js同学,来负责这个统一的模块配置文件。
关于module.h.js还有挺多内容要讲的,不过,绝大同学不用关心它的实现;关心他的实现的同学大多也看过NK行的YUI3种子,再看这个两百行的module.h.js,应该是小case,这里先略过。
小结一下,QWrap的这个种子应用是绿色版的,并且小巧、灵活,可以直接复制过去引用。不过需要注意一下你所放的路径,防止QW.PATH的值计算有误,或者强制改掉QW.PATH的值。
附: QWrap网址:http://www.qwrap.com
“种子”是沿用YUI3的说法,种子应用是解决模块加载问题的应用,包括:模块预加载、异步按需加载、模块应用。
或许有些同学对异步加载模块不大熟悉,没关系,我们先感性的看一下这段代码
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>QW应用之一:seed_youa</title>
<meta http-equiv="Content-Type" content="text/html; charset=GB2312" />
<script type="text/javascript" src="http://dev.qwrap.com/resource/js/apps/seed_youa.combo.js"></script>
</head>
<body>
<div id="div1">div1-----<span class="content">时间</span></div>
<div id="div2">div2-----<span class="content">时间</span></div>
<input type=button value="点击后按需加载后并修改div1/div2" onclick="test()"/>
<script type="text/javascript">
function test(){
QW.use('jQuery,YouaCore',function(){
$('#div1').css('color','red').find('>.content').html(new Date()+'');//这段代码用到了jquery用法
W('#div2').css('color','blue').query('.content').html(new Date().format('YYYY-MM-dd hh:mm:ss'));//这段代码用到了Youa版的QWrap
});
}
</script>
</body>
1。如果用户浏览此网页时,只加载了一个体积很小的seed_youa.combo.js这个js(其实就是seed_youa.js),这个js经yui压缩后的大小为4K。
2。当用户点击按钮时,按钮对应的js用到jQuery与YouaCore,点击后才会去加载那两个jQuery与YouaCore对应的js,之后才有运行效果。
3。第二次点击按钮,由于前面已经加载过js,所以这次会直接运行按钮事件。
分析一下以上三步,分两种情况。
A。对于只进行了第一步操作的用户,他们用seed_youa.js替代了jQuery与YouaCore两个js的流量(这是好处甲);
B。对于进行过第二步操作的用户,由于以上流量的节约,会让用户提前能够点击按钮(这是好处乙),点击后异步加载需要的js,加载完后事件才运行,即点击与运行存在一个时间差(这是坏处甲),并且三个js都用到了,相对于传统写法,http多了一个seed_youa.combo.js(这是坏处乙)。
另外,对于页面程序员来说,他在写按钮事件时,只需要知道自己的这段js用到了哪些模块,而不用关心这些模块是否已经加载(这是好处丙),不过,他引用模块的方式有了一些变化(少键入html代码,多键入js代码)。
好处甲、好处乙、好处丙、坏处甲、坏处乙,还有些没有分析到的好处与坏处,反正有利有弊。
那么,在什么情况下利大于弊、什么情况下弊大于利,而我们该如何兴利除弊?
如果这个页面的用户100%不会点击按钮,则几乎没坏处,只有好处甲、好处丙。
如果页面100%的用户会点击按钮,则需要权衡再权衡利弊再想办法。
好的,我们先暂且搁置这些细节,因为以上内容已足够让同学们对“异步按需加载”有了感性的认识。我们把提供这种异步按需加载的js叫种子(seed)。
我们看一下seed_youa.js,它是一个组合js(即前面一篇文章所说的B类apps:开发时可以是多个文件,但上线时会先合并后上线。)
它由三个js组成:
document.write('<script type="text/javascript" src="' + srcPath + 'core/core_base.js"><\/script>');
document.write('<script type="text/javascript" src="' + srcPath + 'core/module.h.js"><\/script>');
document.write('<script type="text/javascript" src="' + srcPath + 'apps/youa_modules_config.js"><\/script>');
core/core_base.js是QWrap的主干js
core/module.h.js是模块管理器,也是一个Helper,提供三个方法:addConfig、use、provide,代码参见:http://dev.qwrap.com/resource/js/core/module.h.js
youa_modules_config.js是youa项目只所使用到的常用模块配置,通过addConfig进行模块配置,代码如下:
View Code
/*Lib Module*/
QW.ModuleH.addConfig({
YouaCore: {
url: '//apps/core_dom_youa_lazy.combo.js',
loadedChecker:function(){
return !!(QW.W);
}
},
Ajax: {
url: '//components/ajax/ajax.youa.js',
requires: 'YouaCore'
},
Anim: {
url: '//components/animation/anim.js',
requires: 'YouaCore'
},
Cookie: {
url: '//components/cache/cache.js',
requires: 'YouaCore'
},
Storage: {
url: '//components/cache/cache.js',
requires: 'YouaCore'
},
Drag: {
url: '//components/drag/drag.js',
requires: 'YouaCore'
},
Editor: {
url: '//components/editor/editor.js',
requires: 'YouaCore,Panel'
},
Panel: {
url: '//components/panel/panel.js',
requires: 'YouaCore'
},
Suggest: {
url: '//components/suggest/suggest.js',
requires: 'YouaCore'
},
"Switch": {
url: '//components/switch/switch.js',
requires: 'YouaCore'
},
Tree: {
url: '//components/tree/tree.js',
requires: 'YouaCore'
},
Valid: {
url: '//components/valid/valid.js',
requires: 'YouaCore'
},
jQuery: {
url: 'http://common.cnblogs.com/script/jquery.js',
loadedChecker:function(){
return !!(window.jQuery && window.jQuery.fn);
}
}
});
/*Logic Module*/
QW.ModuleH.addConfig({
"User": {
url: '//global/userv3.js',
requires: 'YouaCore',
loadedChecker: function() {
return !!window.topbar;
}
},
ShopMap: {
url: '//sp/map/shopmap.js',
requires: 'YouaCore'
}
});
看到这个配置文件的同学也许会问:模块添加得越来越多会怎么办?为什么不像YUI一样,在各个模块的js里进行addConfig,而要集中起来addConfig?
不错,理论上是会越来越大,但是,实际上最大能大到多少?并且不一定是所有的模块配置都要放在这里面。在页面的js里也可以QW.addConfig('MyPageJs','//my.js')的。所谓的太大,只是个理论问题,不是个实际问题。
YUI在各个模块的js里进行add,有好处,也有坏处。
好处就是多了个沙箱机制,沙箱理论看起来很严谨,可以满足YUI的严谨性洁癖----其实我一直没有理解它对实用者都有啥好处。
而坏处,也有很多。例如:
1。需要模块的代码来适应他的加载机制。----例如,jQuery也可以算是个模块,凭什么要让独立的jquery来改代码?
2。假设我改了jQuery的代码来适应这loader,那是不是也损失了jQuery的原来的用法,即预加载模式用法(在HTML的Header里引用jquery.js,在页面直接用$('#id').show())。
3。例如use('Editor',function(){})时,其实由于依赖关系,会按需加载YouaCore,Drag,Panel,Editor四个js,因为依赖关系是在各自的js里,理论上无法将四个请求合并成一个请求,也无法在请求editor.js时就并行请求drag.js。
4。如果客户端缓存了js,我们更新Editor.js后,use('Editor',function(){})无法知道是否需要在editor.js后添加版本号。
5。如果有复合模块,例如core_dom_youa_lazy.js其实是同时拥有很多小模块功能的一个js,那在drap.js里如何定义依赖?
而QWrap的“集中配置”方式,上面几条都不是什么问题。
对于第一条第二条,已经解决,即:addConfig时的loadedChecker参数。
例如,这样配置jQuery:
QW.ModuleH.addConfig({
jQuery: {
url: 'http://common.cnblogs.com/script/jquery.js',
loadedChecker:function(){
return !!(window.jQuery && window.jQuery.fn);
}
}
});
这个jquery引用的是博客园网站的,我们没有作任何修改。
如果页面已经预加载了jquery,运行QW.use('jQuery',function(){})也不会再次加载jquery.js。
当然,用户还可以保持习惯,用以前的预加载经典用法。
对于第三条合并请求与并行请求,QW目前还没做,不过,只需调整一下module.h.js里的某一段代码即可。在源代码里有说明:
function loadsJsInOrder() {
//浏览器不能保证动态添加的ScriptElement会按顺序执行,所以人为来保证一下
//参见:http://www.stevesouders.com/blog/2009/04/27/loading-scripts-without-blocking/
//测试帮助:http://1.cuzillion.com/bin/resource.cgi?type=js&sleep=3&jsdelay=0&n=1&t=1294649352
//todo: 目前没有充分利用部分浏览器的并行下载功能,可以改进。
//todo: 如果服务器端能combo,则可修改以下内容以适应。
var moduleI = loadingModules[0];
function loadedDone() {
moduleI.loadStatus = 2;
var cbs = moduleI.__callbacks;
for (var i = 0; i < cbs.length; i++) {
cbs[i]();
}
isLoading = false;
loadsJsInOrder();
}
if (!isLoading && moduleI) {
//alert(moduleI.url);
isLoading = true;
loadingModules.splice(0, 1);
var checker = moduleI.loadedChecker;
if (checker && checker()) { //如果有loaderChecker,则用loaderChecker判断一下是否已经加载过
loadedDone();
} else {
loadJs(moduleI.url.replace(/^\/\//, QW.PATH), loadedDone);
}
}
}
对于第四条缓存,由于是统一配置,所以只需在发布时,在配置文件的js后面加上对应js的md5码的前N位即可。
例如,线上的配置可能是:
Ajax: {
url: '//components/ajax/ajax.youa.js?111111.js',
requires: 'YouaCore'
},
Anim: {
url: '//components/animation/anim.js?222222.js',
requires: 'YouaCore'
}
这样做的好处是:每次上线后,用户也只需下载有过修改的js。
对于第五条的复合模块问题,由于是统一配置,所以根本就不是问题。
说了seed_youa.js的这么多好处,当然还要说明一个限制:项目中需要有一个水平还过得去的js同学,来负责这个统一的模块配置文件。
关于module.h.js还有挺多内容要讲的,不过,绝大同学不用关心它的实现;关心他的实现的同学大多也看过NK行的YUI3种子,再看这个两百行的module.h.js,应该是小case,这里先略过。
小结一下,QWrap的这个种子应用是绿色版的,并且小巧、灵活,可以直接复制过去引用。不过需要注意一下你所放的路径,防止QW.PATH的值计算有误,或者强制改掉QW.PATH的值。
附: QWrap网址:http://www.qwrap.com