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  **/