1 /**
  2  * @fileOverview utils for kissy loader
  3  * @author yiminghe@gmail.com
  4  */
  5 (function (S, undefined) {
  6 
  7     if (typeof require !== 'undefined') {
  8         return;
  9     }
 10 
 11     var Loader = S.Loader,
 12         ua = navigator.userAgent,
 13         startsWith = S.startsWith,
 14         data = Loader.STATUS,
 15         utils = {},
 16         host = S.Env.host,
 17         win = host,
 18         doc = host.document,
 19         loc = host.location,
 20     // 当前页面所在的目录
 21     // http://xx.com/y/z.htm#!/f/g
 22     // ->
 23     // http://xx.com/y/
 24         __pagePath = loc.href.replace(loc.hash, "").replace(/[^/]*$/i, "");
 25 
 26     // http://wiki.commonjs.org/wiki/Packages/Mappings/A
 27     // 如果模块名以 / 结尾,自动加 index
 28     function indexMap(s) {
 29         if (S.isArray(s)) {
 30             var ret = [];
 31             S.each(s, function (si) {
 32                 ret.push(indexMap(si));
 33             });
 34             return ret;
 35         }
 36         return indexMapStr(s);
 37     }
 38 
 39     function indexMapStr(s) {
 40         if (/(.+\/)(\?t=.+)?$/.test(s)) {
 41             return RegExp.$1 + "index" + RegExp.$2;
 42         } else {
 43             return s
 44         }
 45     }
 46 
 47 
 48     function getPackageInfo(self, mod) {
 49 
 50         var modName = mod.name,
 51             Env = self.Env,
 52             packages = Env.packages || {},
 53             pName = "",
 54             packageDesc;
 55 
 56         for (var p in packages) {
 57             if (packages.hasOwnProperty(p)) {
 58                 if (S.startsWith(modName, p) &&
 59                     p.length > pName.length) {
 60                     pName = p;
 61                 }
 62             }
 63         }
 64 
 65         packageDesc = packages[pName] ||
 66             Env.defaultPackage ||
 67             (Env.defaultPackage = new Loader.Package({SS:self}));
 68 
 69         mod.packageInfo = packageDesc;
 70 
 71         return packageDesc;
 72     }
 73 
 74 
 75     var isWebKit = !!ua.match(/AppleWebKit/);
 76 
 77     S.mix(utils, {
 78 
 79         docHead:function () {
 80             return doc.getElementsByTagName('head')[0] || doc.documentElement;
 81         },
 82 
 83         isWebKit:isWebKit,
 84 
 85         // like Gecko ...
 86         isGecko:!isWebKit && !!ua.match(/Gecko/),
 87 
 88         isPresto:!!ua.match(/Presto/),
 89 
 90         IE:!!ua.match(/MSIE/),
 91 
 92         isCss:function (url) {
 93             return /\.css(?:\?|$)/i.test(url);
 94         },
 95 
 96         /**
 97          * resolve relative part of path
 98          * x/../y/z -> y/z
 99          * x/./y/z -> x/y/z
100          * @param path uri path
101          * @return {string} resolved path
102          * @description similar to path.normalize in nodejs
103          */
104         normalizePath:function (path) {
105             var paths = path.split("/"),
106                 re = [],
107                 p;
108             for (var i = 0; i < paths.length; i++) {
109                 p = paths[i];
110                 if (p == ".") {
111                 } else if (p == "..") {
112                     re.pop();
113                 } else {
114                     re.push(p);
115                 }
116             }
117             return re.join("/");
118         },
119 
120         /**
121          * 根据当前模块以及依赖模块的相对路径,得到依赖模块的绝对路径
122          * @param moduleName 当前模块
123          * @param depName 依赖模块
124          * @return {string|Array} 依赖模块的绝对路径
125          * @description similar to path.resolve in nodejs
126          */
127         normalDepModuleName:function (moduleName, depName) {
128             if (!depName) {
129                 return depName;
130             }
131             if (S.isArray(depName)) {
132                 for (var i = 0; i < depName.length; i++) {
133                     depName[i] = utils.normalDepModuleName(moduleName, depName[i]);
134                 }
135                 return depName;
136             }
137             if (startsWith(depName, "../") || startsWith(depName, "./")) {
138                 var anchor = "", index;
139                 // x/y/z -> x/y/
140                 if ((index = moduleName.lastIndexOf("/")) != -1) {
141                     anchor = moduleName.substring(0, index + 1);
142                 }
143                 return normalizePath(anchor + depName);
144             } else if (depName.indexOf("./") != -1
145                 || depName.indexOf("../") != -1) {
146                 return normalizePath(depName);
147             } else {
148                 return depName;
149             }
150         },
151 
152         //去除后缀名,要考虑时间戳!
153         removePostfix:function (path) {
154             return path.replace(/(-min)?\.js[^/]*$/i, "");
155         },
156 
157         /**
158          * 路径正则化,不能是相对地址
159          * 相对地址则转换成相对页面的绝对地址
160          * 用途:
161          * package path 相对地址则相对于当前页面获取绝对地址
162          */
163         normalBasePath:function (path) {
164             path = S.trim(path);
165 
166             // path 为空时,不能变成 "/"
167             if (path &&
168                 path.charAt(path.length - 1) != '/') {
169                 path += "/";
170             }
171 
172             /**
173              * 一定要正则化,防止出现 ../ 等相对路径
174              * 考虑本地路径
175              */
176             if (!path.match(/^(http(s)?)|(file):/i) &&
177                 !startsWith(path, "/")) {
178                 path = __pagePath + path;
179             }
180 
181             if (startsWith(path, "/")) {
182                 var loc = win.location;
183                 path = loc.protocol + "//" + loc.host + path;
184             }
185 
186             return normalizePath(path);
187         },
188 
189         /**
190          * 相对路径文件名转换为绝对路径
191          * @param path
192          */
193         absoluteFilePath:function (path) {
194             path = utils.normalBasePath(path);
195             return path.substring(0, path.length - 1);
196         },
197 
198         createModulesInfo:function (self, modNames) {
199             S.each(modNames, function (m) {
200                 utils.createModuleInfo(self, m);
201             });
202         },
203 
204         createModuleInfo:function (self, modName, cfg) {
205             var mods = self.Env.mods,
206                 t,
207                 mod = mods[modName];
208 
209             if (mod) {
210                 return mod;
211             }
212 
213             // 防止 cfg 里有 tag,构建 fullpath 需要
214             mods[modName] = mod = new Loader.Module(S.mix({
215                 name:modName,
216                 SS:self
217             }, cfg));
218 
219             var packageInfo = getPackageInfo(self, mod),
220                 path = defaultComponentJsName(modName, packageInfo);
221 
222             // 用户配置的 path优先
223             S.mix(mod, {
224                 path:path,
225                 packageInfo:packageInfo
226             }, false);
227 
228             return mod;
229         },
230 
231         isAttached:function (self, modNames) {
232             return isStatus(self, modNames, data.ATTACHED);
233         },
234 
235         isLoaded:function (self, modNames) {
236             return isStatus(self, modNames, data.LOADED);
237         },
238 
239         getModules:function (self, modNames) {
240             var mods = [self];
241 
242             S.each(modNames, function (modName) {
243                 if (!utils.isCss(modName)) {
244                     mods.push(self.require(modName));
245                 }
246             });
247 
248             return mods;
249         },
250 
251         attachMod:function (self, mod) {
252 
253             if (mod.status != data.LOADED) {
254                 return;
255             }
256 
257             var fn = mod.fn,
258                 requires,
259                 value;
260 
261             // 需要解开 index,相对路径,去除 tag,但是需要保留 alias,防止值不对应
262             requires = mod.requires = utils.normalizeModNamesWithAlias(self, mod.requires, mod.name);
263 
264             if (fn) {
265                 if (S.isFunction(fn)) {
266                     // context is mod info
267                     value = fn.apply(mod, utils.getModules(self, requires));
268                 } else {
269                     value = fn;
270                 }
271                 mod.value = value;
272             }
273 
274             mod.status = data.ATTACHED;
275 
276             self.getLoader().fire("afterModAttached", {
277                 mod:mod
278             });
279         },
280 
281         getModNamesAsArray:function (modNames) {
282             if (S.isString(modNames)) {
283                 modNames = modNames.replace(/\s+/g, "").split(',');
284             }
285             return modNames;
286         },
287 
288 
289         indexMapStr:indexMapStr,
290 
291         /**
292          * Three effects:
293          * 1. add index : / => /index
294          * 2. unalias : core => dom,event,ua
295          * 3. relative to absolute : ./x => y/x
296          * @param {KISSY} self Global KISSY instance
297          * @param {String|String[]} modNames Array of module names or module names string separated by comma
298          */
299         normalizeModNames:function (self, modNames, refModName) {
300             return utils.unalias(self, utils.normalizeModNamesWithAlias(self, modNames, refModName));
301         },
302 
303         unalias:function (self, names) {
304             var ret = [].concat(names),
305                 i,
306                 m,
307                 alias,
308                 ok = 0,
309                 mods = self['Env'].mods;
310             while (!ok) {
311                 ok = 1;
312                 for (i = ret.length - 1; i >= 0; i--) {
313                     if ((m = mods[ret[i]]) && (alias = m.alias)) {
314                         ok = 0;
315                         ret.splice.apply(ret, [i, 1].concat(indexMap(alias)));
316                     }
317                 }
318             }
319             return ret;
320         },
321 
322         normalizeModNamesWithAlias:function (self, modNames, refModName) {
323             var ret = [], i, l;
324             if (modNames) {
325                 // 1. index map
326                 for (i = 0, l = modNames.length; i < l; i++) {
327                     ret.push(indexMap(modNames[i]));
328                 }
329             }
330             // 3. relative to absolute (optional)
331             if (refModName) {
332                 ret = utils.normalDepModuleName(refModName, ret);
333             }
334             return ret;
335         },
336 
337         // 注册模块,将模块和定义 factory 关联起来
338         registerModule:function (self, name, fn, config) {
339             var mods = self.Env.mods,
340                 mod = mods[name];
341 
342             if (mod && mod.fn) {
343                 S.log(name + " is defined more than once");
344                 return;
345             }
346 
347             // 没有 use,静态载入的 add 可能执行
348             utils.createModuleInfo(self, name);
349 
350             mod = mods[name];
351 
352             // 注意:通过 S.add(name[, fn[, config]]) 注册的代码,无论是页面中的代码,
353             // 还是 js 文件里的代码,add 执行时,都意味着该模块已经 LOADED
354             S.mix(mod, { name:name, status:data.LOADED });
355 
356 
357             mod.fn = fn;
358 
359             S.mix((mods[name] = mod), config);
360 
361             S.log(name + " is loaded");
362         },
363 
364         getMappedPath:function (self, path) {
365             var __mappedRules = self.Config.mappedRules || [];
366             for (var i = 0; i < __mappedRules.length; i++) {
367                 var m, rule = __mappedRules[i];
368                 if (m = path.match(rule[0])) {
369                     return path.replace(rule[0], rule[1]);
370                 }
371             }
372             return path;
373         },
374 
375         /**
376          * test3,test3/a/b => a/b
377          */
378         removePackageNameFromModName:function () {
379             var cache = {};
380             return function (packageName, modName) {
381                 if (!packageName) {
382                     return modName;
383                 }
384                 if (!S.endsWith(packageName, "/")) {
385                     packageName += "/";
386                 }
387                 var reg;
388                 if (!(reg = cache[packageName])) {
389                     reg = cache[packageName] = new RegExp("^" + S.escapeRegExp(packageName));
390                 }
391                 return modName.replace(reg, "");
392             }
393         }()
394 
395     });
396 
397     function defaultComponentJsName(m, packageInfo) {
398         var suffix = ".js",
399             match;
400         if (match = m.match(/(.+)(\.css)$/i)) {
401             suffix = match[2];
402             m = match[1];
403         }
404         var min = "-min";
405         if (packageInfo.isDebug()) {
406             min = "";
407         }
408         return m + min + suffix;
409     }
410 
411     function isStatus(self, modNames, status) {
412         var mods = self.Env.mods,
413             i;
414         modNames = S.makeArray(modNames);
415         for (i = 0; i < modNames.length; i++) {
416             var mod = mods[modNames[i]];
417             if (!mod || mod.status !== status) {
418                 return false;
419             }
420         }
421         return true;
422     }
423 
424     var normalizePath = utils.normalizePath;
425 
426     Loader.Utils = utils;
427 
428 })(KISSY);