1 /**
  2  * @fileOverview use and attach mod
  3  * @author yiminghe@gmail.com,lifesinger@gmail.com
  4  */
  5 (function (S) {
  6     if (typeof require !== 'undefined') {
  7         return;
  8     }
  9 
 10     var Loader = S.Loader,
 11         data = Loader.STATUS,
 12         utils = Loader.Utils,
 13         INIT = data.INIT,
 14         IE = utils.IE,
 15         win = S.Env.host,
 16         LOADING = data.LOADING,
 17         LOADED = data.LOADED,
 18         ERROR = data.ERROR,
 19         ALL_REQUIRES = "__allRequires",
 20         CURRENT_MODULE = "__currentModule",
 21         ATTACHED = data.ATTACHED;
 22 
 23     S.augment(Loader, {
 24         /**
 25          * Start load specific mods, and fire callback when these mods and requires are attached.
 26          * @example
 27          * <code>
 28          * S.use('mod-name', callback, config);
 29          * S.use('mod1,mod2', callback, config);
 30          * </code>
 31          * @param {String|String[]} modNames names of mods to be loaded,if string then separated by space
 32          * @param {Function} callback callback when modNames are all loaded,
 33          *                   with KISSY as first argument and mod's value as the following arguments
 34          */
 35         use:function (modNames, callback) {
 36             var self = this,
 37                 SS = self.SS;
 38 
 39             modNames = utils.getModNamesAsArray(modNames);
 40             modNames = utils.normalizeModNamesWithAlias(SS, modNames);
 41 
 42             var normalizedModNames = utils.unalias(SS, modNames),
 43                 count = normalizedModNames.length,
 44                 currentIndex = 0;
 45 
 46             function end() {
 47                 var mods = utils.getModules(SS, modNames);
 48                 callback && callback.apply(SS, mods);
 49             }
 50 
 51             // 已经全部 attached, 直接执行回调即可
 52             if (utils.isAttached(SS, normalizedModNames)) {
 53                 return end();
 54             }
 55 
 56             // 有尚未 attached 的模块
 57             S.each(normalizedModNames, function (modName) {
 58                 // 从 name 开始调用,防止不存在模块
 59                 attachModByName(self, modName, function () {
 60                     currentIndex++;
 61                     if (currentIndex == count) {
 62                         end();
 63                     }
 64                 });
 65             });
 66 
 67             return self;
 68         }
 69     });
 70 
 71     // 加载指定模块名模块,如果不存在定义默认定义为内部模块
 72     function attachModByName(self, modName, callback) {
 73         var SS = self.SS, mod;
 74         utils.createModuleInfo(SS, modName);
 75         mod = SS.Env.mods[modName];
 76         if (mod.status === ATTACHED) {
 77             callback();
 78             return;
 79         }
 80         attachModRecursive(self, mod, callback);
 81     }
 82 
 83 
 84     /**
 85      * Attach a module and all required modules.
 86      */
 87     function attachModRecursive(self, mod, callback) {
 88         var SS = self.SS,
 89             r,
 90             rMod,
 91             i,
 92             callbackBeCalled = 0,
 93         // 最终有效的 require ,add 处声明为准
 94             newRequires,
 95             mods = SS.Env.mods;
 96 
 97         // 复制一份当前的依赖项出来,防止 add 后修改!
 98         // 事先配置的 require ,同 newRequires 有区别
 99         var requires = utils.normalizeModNames(SS, mod.requires, mod.name);
100 
101         /**
102          * check cyclic dependency between mods
103          * @private
104          */
105         function cyclicCheck() {
106             // one mod's all requires mods to run its callback
107             var __allRequires = mod[ALL_REQUIRES] = mod[ALL_REQUIRES] || {},
108                 myName = mod.name,
109                 rmod,
110                 r__allRequires;
111 
112             S.each(requires, function (r) {
113                 rmod = mods[r];
114                 __allRequires[r] = 1;
115                 if (rmod && (r__allRequires = rmod[ALL_REQUIRES])) {
116                     S.mix(__allRequires, r__allRequires);
117                 }
118             });
119 
120             if (__allRequires[myName]) {
121                 S.log(__allRequires, "error");
122                 var JSON = win.JSON,
123                     error = "";
124                 if (JSON) {
125                     error = JSON.stringify(__allRequires);
126                 }
127                 S.error("find cyclic dependency by mod " + myName + " between mods : " + error);
128             }
129         }
130 
131         S.log(cyclicCheck());
132 
133         // attach all required modules
134         for (i = 0; i < requires.length; i++) {
135             r = requires[i];
136             rMod = mods[r];
137             if (rMod && rMod.status === ATTACHED) {
138                 //no need
139             } else {
140                 attachModByName(self, r, fn);
141             }
142         }
143 
144         // load and attach this module
145         loadModByScript(self, mod, function () {
146 
147             // KISSY.add 可能改了 config,这里重新取下
148             newRequires = utils.normalizeModNames(SS, mod.requires, mod.name);
149 
150             var needToLoad = [];
151 
152             //本模块下载成功后串行下载 require
153             for (i = 0; i < newRequires.length; i++) {
154                 var r = newRequires[i],
155                     rMod = mods[r],
156                     inA = S.inArray(r, requires);
157                 //已经处理过了或将要处理
158                 if (rMod &&
159                     rMod.status === ATTACHED ||
160                     //已经正在处理了
161                     inA) {
162                     //no need
163                 } else {
164                     //新增的依赖项
165                     needToLoad.push(r);
166                 }
167             }
168 
169             if (needToLoad.length) {
170                 for (i = 0; i < needToLoad.length; i++) {
171                     attachModByName(self, needToLoad[i], fn);
172                 }
173             } else {
174                 fn();
175             }
176         });
177 
178         function fn() {
179             if (
180             // 前提条件,本模块 script onload 已经调用
181             // ie 下 add 与 script onload 并不连续!!
182             // attach 以 newRequires 为准
183                 newRequires &&
184                     !callbackBeCalled &&
185                     // 2012-03-16 by yiminghe@gmail.com
186                     // add 与 onload ie 下不连续
187                     // c 依赖 a
188                     // a 模块 add 时进行 attach
189                     // a add 后 c 模块 onload 触发
190                     // 检测到 a 已经 attach 则调用该函数
191                     // a onload 后又调用该函数则需要用 callbackBeCalled 来把门
192                     utils.isAttached(SS, newRequires)) {
193 
194                 utils.attachMod(SS, mod);
195 
196                 if (mod.status == ATTACHED) {
197                     callbackBeCalled = 1;
198                     callback();
199                 }
200             }
201         }
202     }
203 
204 
205     /**
206      * Load a single module.
207      */
208     function loadModByScript(self, mod, callback) {
209         var SS = self.SS,
210             charset = mod.getCharset(),
211             url = mod.getFullPath(),
212             isCss = utils.isCss(url)
213 
214         mod.status = mod.status || INIT;
215 
216         if (mod.status < LOADING) {
217             mod.status = LOADING;
218             if (IE && !isCss) {
219                 self.__startLoadModuleName = mod.name;
220                 self.__startLoadTime = Number(+new Date());
221             }
222             S.getScript(url, {
223                 // syntaxError in all browser will trigger this
224                 // same as #111 : https://github.com/kissyteam/kissy/issues/111
225                 success:function () {
226                     if (!isCss) {
227                         // 载入 css 不需要这步了
228                         // 标准浏览器下:外部脚本执行后立即触发该脚本的 load 事件,ie9 还是不行
229                         if (self[CURRENT_MODULE]) {
230                             S.log("standard browser get modname after load : " + mod.name);
231                             utils.registerModule(SS,
232                                 mod.name, self[CURRENT_MODULE].fn,
233                                 self[CURRENT_MODULE].config);
234                             self[CURRENT_MODULE] = null;
235                         }
236                     }
237                     checkAndHandle();
238                 },
239                 error:checkAndHandle,
240                 // source:mod.name + "-init",
241                 charset:charset
242             });
243         }
244         // 已经在加载中,需要添加回调到 script onload 中
245         // 注意:没有考虑 error 情形,只在第一次处理即可
246         // 交给 getScript 排队
247         else if (mod.status == LOADING) {
248             S.getScript(url, {
249                 success:checkAndHandle,
250                 // source:mod.name + "-loading",
251                 charset:charset
252             });
253         }
254         // loaded/attached/error
255         else {
256             checkAndHandle();
257         }
258 
259         function checkAndHandle() {
260             if (isCss || mod.fn) {
261                 // css 不会设置 LOADED! 必须外部设置
262                 if (isCss && mod.status != ATTACHED) {
263                     mod.status = LOADED;
264                 }
265                 callback();
266             } else {
267                 // ie will call success even when getScript error(404)
268                 _modError();
269             }
270         }
271 
272         function _modError() {
273             S.log(mod.name + ' is not loaded! can not find module in path : ' + mod['fullpath'], 'error');
274             mod.status = ERROR;
275         }
276     }
277 })(KISSY);