1 /** 2 * @fileOverview using combo to load module files 3 * @author yiminghe@gmail.com 4 */ 5 (function (S) { 6 7 if (typeof require !== 'undefined') { 8 return; 9 } 10 11 function loadScripts(urls, callback, charset) { 12 var count = urls && urls.length, 13 i, 14 url; 15 if (!count) { 16 callback(); 17 return; 18 } 19 for (i = 0; i < urls.length; i++) { 20 url = urls[i]; 21 S.getScript(url, function () { 22 if (!(--count)) { 23 callback(); 24 } 25 }, charset || "utf-8"); 26 } 27 } 28 29 var Loader = S.Loader, 30 data = Loader.STATUS, 31 utils = Loader.Utils; 32 33 /** 34 * using combo to load module files 35 * @class 36 * @param SS KISSY 37 */ 38 function ComboLoader(SS) { 39 S.mix(this, { 40 SS:SS, 41 queue:[], 42 loading:0 43 }); 44 } 45 46 S.augment(ComboLoader, 47 Loader.Target, 48 /** 49 * @lends ComboLoader# 50 */ 51 { 52 next:function () { 53 var self = this; 54 if (self.queue.length) { 55 var args = self.queue.shift(); 56 self._use(args.modNames, args.fn); 57 } 58 }, 59 60 enqueue:function (modNames, fn) { 61 var self = this; 62 self.queue.push({ 63 modNames:modNames, 64 fn:fn 65 }); 66 }, 67 68 _use:function (modNames, fn) { 69 var self = this, SS = self.SS; 70 71 self.loading = 1; 72 73 modNames = utils.getModNamesAsArray(modNames); 74 75 modNames = utils.normalizeModNamesWithAlias(SS, modNames); 76 77 var unaliasModNames = utils.unalias(SS, modNames); 78 79 var allModNames = self.calculate(unaliasModNames); 80 81 utils.createModulesInfo(SS, allModNames); 82 83 var comboUrls = self.getComboUrls(allModNames); 84 85 // load css first to avoid page blink 86 var css = comboUrls.css, 87 countCss = 0; 88 89 for (var p in css) { 90 countCss++; 91 } 92 93 if (!countCss) { 94 self._useJs(comboUrls, fn, modNames); 95 return; 96 } 97 98 for (p in css) { 99 loadScripts(css[p], function () { 100 if (!(--countCss)) { 101 S.each(unaliasModNames, function (name) { 102 utils.attachMod(self.SS, self.getModInfo(name)); 103 }); 104 self._useJs(comboUrls, fn, modNames); 105 } 106 }, css[p].charset); 107 } 108 }, 109 110 use:function (modNames, callback) { 111 var self = this, 112 fn = function () { 113 // KISSY.use in callback will be queued 114 if (callback) { 115 callback.apply(this, arguments); 116 } 117 self.loading = 0; 118 self.next(); 119 }; 120 121 self.enqueue(modNames, fn); 122 123 if (!self.loading) { 124 self.next(); 125 } 126 }, 127 128 _useJs:function (comboUrls, fn, modNames) { 129 var self = this, 130 jss = comboUrls.js, 131 countJss = 0; 132 133 134 for (var p in jss) { 135 countJss++; 136 } 137 138 if (!countJss) { 139 // 2012-05-18 bug: loaded 那么需要加载的 jss 为空,要先 attach 再通知用户回调函数 140 var unaliasModNames = utils.unalias(self.SS, modNames); 141 self.attachMods(unaliasModNames); 142 fn.apply(null, utils.getModules(self.SS, modNames)); 143 return; 144 } 145 var success = 1; 146 for (p in jss) { 147 (function (p) { 148 loadScripts(jss[p], function () { 149 var mods = jss[p].mods; 150 for (var i = 0; i < mods.length; i++) { 151 var mod = mods[i]; 152 // fix #111 153 // https://github.com/kissyteam/kissy/issues/111 154 if (!mod.fn) { 155 S.log(mod.name + ' is not loaded! can not find module in path : ' + jss[p], 'error'); 156 mod.status = data.ERROR; 157 success = 0; 158 return; 159 } 160 } 161 if (success && !(--countJss)) { 162 var unaliasModNames = utils.unalias(self.SS, modNames); 163 self.attachMods(unaliasModNames); 164 if (utils.isAttached(self.SS, unaliasModNames)) { 165 fn.apply(null, utils.getModules(self.SS, modNames)) 166 } else { 167 // new require is introduced by KISSY.add 168 // run again 169 self._use(modNames, fn) 170 } 171 } 172 }, jss[p].charset); 173 })(p); 174 } 175 }, 176 177 add:function (name, fn, config) { 178 var self = this, 179 SS = self.SS; 180 // 兼容 1.3.0pr1 181 if (S.isPlainObject(name)) { 182 return SS.config({ 183 modules:name 184 }); 185 } 186 utils.registerModule(SS, name, fn, config); 187 }, 188 189 190 attachMods:function (modNames) { 191 var self = this; 192 S.each(modNames, function (modName) { 193 self.attachMod(modName); 194 }); 195 }, 196 197 attachMod:function (modName) { 198 var SS = this.SS, 199 mod = this.getModInfo(modName); 200 if ( 201 // new require after add 202 // not register yet! 203 !mod || utils.isAttached(SS, modName)) { 204 return; 205 } 206 var requires = utils.normalizeModNames(SS, mod.requires, modName); 207 for (var i = 0; i < requires.length; i++) { 208 this.attachMod(requires[i]); 209 } 210 for (i = 0; i < requires.length; i++) { 211 if (!utils.isAttached(SS, requires[i])) { 212 return false; 213 } 214 } 215 utils.attachMod(SS, mod); 216 }, 217 218 calculate:function (modNames) { 219 var ret = {}, 220 SS = this.SS, 221 // 提高性能,不用每个模块都再次提柜计算 222 // 做个缓存,每个模块对应的待动态加载模块 223 cache = {}; 224 for (var i = 0; i < modNames.length; i++) { 225 var m = modNames[i]; 226 if (!utils.isAttached(SS, m)) { 227 if (!utils.isLoaded(SS, m)) { 228 ret[m] = 1; 229 } 230 S.mix(ret, this.getRequires(m, cache)); 231 } 232 } 233 var ret2 = []; 234 for (var r in ret) { 235 ret2.push(r); 236 } 237 return ret2; 238 }, 239 240 getComboUrls:function (modNames) { 241 var self = this, 242 i, 243 SS = self.SS, 244 Config = S.Config, 245 packageBase, 246 combos = {}; 247 248 S.each(modNames, function (modName) { 249 var mod = self.getModInfo(modName); 250 var packageInfo = mod.getPackageInfo(); 251 var packageBase = packageInfo.getBase(); 252 var type = utils.isCss(mod.path) ? "css" : "js", mods; 253 var packageName = packageInfo.getName(); 254 combos[packageBase] = combos[packageBase] || {}; 255 mods = combos[packageBase][type] = combos[packageBase][type] || []; 256 mods.combine = 1; 257 if (packageInfo.isCombine() === false) { 258 mods.combine = 0; 259 } 260 mods.tag = packageInfo.getTag(); 261 mods.charset = mod.getCharset(); 262 mods.name = packageName; 263 mods.push(mod); 264 }); 265 266 var res = { 267 js:{}, 268 css:{} 269 }, 270 t, 271 comboPrefix = Config.comboPrefix, 272 comboSep = Config.comboSep, 273 maxUrlLength = Config.comboMaxUrlLength; 274 275 for (packageBase in combos) { 276 for (var type in combos[packageBase]) { 277 t = []; 278 var jss = combos[packageBase][type], 279 packageName = jss.name, 280 packageNamePath = packageName + "/"; 281 res[type][packageBase] = []; 282 res[type][packageBase].charset = jss.charset; 283 // current package's mods 284 res[type][packageBase].mods = []; 285 // add packageName to common prefix 286 // combo grouped by package 287 var prefix = packageBase + (packageName ? packageNamePath : "") + comboPrefix, 288 path, 289 tag, 290 l = prefix.length; 291 for (i = 0; i < jss.length; i++) { 292 // remove packageName prefix from mod path 293 path = jss[i].path; 294 if (packageName) { 295 path = utils.removePackageNameFromModName(packageName, path); 296 } 297 res[type][packageBase].mods.push(jss[i]); 298 if (!jss.combine) { 299 tag = jss[i].getTag(); 300 res[type][packageBase].push(utils.getMappedPath(SS, 301 prefix + path + (tag ? ("?t=" + encodeURIComponent(tag)) : ""))); 302 continue; 303 } 304 t.push(path); 305 if (l + t.join(comboSep).length > maxUrlLength) { 306 t.pop(); 307 res[type][packageBase].push(self.getComboUrl( 308 prefix, 309 t, 310 comboSep, 311 jss.tag 312 )); 313 t = []; 314 i--; 315 } 316 } 317 if (t.length) { 318 res[type][packageBase].push(self.getComboUrl( 319 prefix, 320 t, 321 comboSep, 322 jss.tag 323 )); 324 } 325 } 326 } 327 328 return res; 329 }, 330 331 getComboUrl:function (prefix, t, comboSep, tag) { 332 return utils.getMappedPath( 333 this.SS, 334 prefix + t.join(comboSep) + (tag ? ("?t=" + 335 encodeURIComponent(tag)) : "") 336 ); 337 }, 338 339 getModInfo:function (modName) { 340 var SS = this.SS, mods = SS.Env.mods; 341 return mods[modName]; 342 }, 343 344 // get requires mods need to be loaded dynamically 345 getRequires:function (modName, cache) { 346 var self = this, 347 SS = self.SS, 348 mod = self.getModInfo(modName), 349 // 做个缓存,该模块的待加载子模块都知道咯,不用再次递归查找啦! 350 ret = cache[modName]; 351 if (ret) { 352 return ret; 353 } 354 ret = {}; 355 // if this mod is attached then its require is attached too! 356 if (mod && !utils.isAttached(SS, modName)) { 357 var requires = utils.normalizeModNames(SS, mod.requires, modName); 358 // circular dependency check 359 if (S.Config.debug) { 360 var allRequires = mod.__allRequires || (mod.__allRequires = {}); 361 if (allRequires[modName]) { 362 S.error("detect circular dependency among : "); 363 S.error(allRequires); 364 } 365 } 366 for (var i = 0; i < requires.length; i++) { 367 var r = requires[i]; 368 if (S.Config.debug) { 369 // circular dependency check 370 var rMod = self.getModInfo(r); 371 allRequires[r] = 1; 372 if (rMod && rMod.__allRequires) { 373 S.each(rMod.__allRequires, function (_, r2) { 374 allRequires[r2] = 1; 375 }); 376 } 377 } 378 // if not load into page yet 379 if (!utils.isLoaded(SS, r) && 380 // and not attached 381 !utils.isAttached(SS, r)) { 382 ret[r] = 1; 383 } 384 var ret2 = self.getRequires(r, cache); 385 S.mix(ret, ret2); 386 } 387 } 388 389 return cache[modName] = ret; 390 } 391 }); 392 393 Loader.Combo = ComboLoader; 394 395 })(KISSY); 396 /** 397 * 2012-02-20 yiminghe note: 398 * - three status 399 * 0 : initialized 400 * LOADED : load into page 401 * ATTACHED : fn executed 402 **/