loadjs.umd.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. (function(root, factory) {
  2. if (typeof define === 'function' && define.amd) {
  3. define([], factory);
  4. } else if (typeof exports === 'object') {
  5. module.exports = factory();
  6. } else {
  7. root.loadjs = factory();
  8. }
  9. }(this, function() {
  10. /**
  11. * Global dependencies.
  12. * @global {Object} document - DOM
  13. */
  14. var devnull = function() {},
  15. bundleIdCache = {},
  16. bundleResultCache = {},
  17. bundleCallbackQueue = {};
  18. /**
  19. * Subscribe to bundle load event.
  20. * @param {string[]} bundleIds - Bundle ids
  21. * @param {Function} callbackFn - The callback function
  22. */
  23. function subscribe(bundleIds, callbackFn) {
  24. // listify
  25. bundleIds = bundleIds.push ? bundleIds : [bundleIds];
  26. var depsNotFound = [],
  27. i = bundleIds.length,
  28. numWaiting = i,
  29. fn,
  30. bundleId,
  31. r,
  32. q;
  33. // define callback function
  34. fn = function (bundleId, pathsNotFound) {
  35. if (pathsNotFound.length) depsNotFound.push(bundleId);
  36. numWaiting--;
  37. if (!numWaiting) callbackFn(depsNotFound);
  38. };
  39. // register callback
  40. while (i--) {
  41. bundleId = bundleIds[i];
  42. // execute callback if in result cache
  43. r = bundleResultCache[bundleId];
  44. if (r) {
  45. fn(bundleId, r);
  46. continue;
  47. }
  48. // add to callback queue
  49. q = bundleCallbackQueue[bundleId] = bundleCallbackQueue[bundleId] || [];
  50. q.push(fn);
  51. }
  52. }
  53. /**
  54. * Publish bundle load event.
  55. * @param {string} bundleId - Bundle id
  56. * @param {string[]} pathsNotFound - List of files not found
  57. */
  58. function publish(bundleId, pathsNotFound) {
  59. // exit if id isn't defined
  60. if (!bundleId) return;
  61. var q = bundleCallbackQueue[bundleId];
  62. // cache result
  63. bundleResultCache[bundleId] = pathsNotFound;
  64. // exit if queue is empty
  65. if (!q) return;
  66. // empty callback queue
  67. while (q.length) {
  68. q[0](bundleId, pathsNotFound);
  69. q.splice(0, 1);
  70. }
  71. }
  72. /**
  73. * Execute callbacks.
  74. * @param {Object or Function} args - The callback args
  75. * @param {string[]} depsNotFound - List of dependencies not found
  76. */
  77. function executeCallbacks(args, depsNotFound) {
  78. // accept function as argument
  79. if (args.call) args = {success: args};
  80. // success and error callbacks
  81. if (depsNotFound.length) (args.error || devnull)(depsNotFound);
  82. else (args.success || devnull)(args);
  83. }
  84. /**
  85. * Load individual file.
  86. * @param {string} path - The file path
  87. * @param {Function} callbackFn - The callback function
  88. */
  89. function loadFile(path, callbackFn, args, numTries) {
  90. var doc = document,
  91. async = args.async,
  92. maxTries = (args.numRetries || 0) + 1,
  93. beforeCallbackFn = args.before || devnull,
  94. pathname = path.replace(/[\?|#].*$/, ''),
  95. pathStripped = path.replace(/^(css|img)!/, ''),
  96. isLegacyIECss,
  97. e;
  98. numTries = numTries || 0;
  99. if (/(^css!|\.css$)/.test(pathname)) {
  100. // css
  101. e = doc.createElement('link');
  102. e.rel = 'stylesheet';
  103. e.href = pathStripped;
  104. // tag IE9+
  105. isLegacyIECss = 'hideFocus' in e;
  106. // use preload in IE Edge (to detect load errors)
  107. if (isLegacyIECss && e.relList) {
  108. isLegacyIECss = 0;
  109. e.rel = 'preload';
  110. e.as = 'style';
  111. }
  112. } else if (/(^img!|\.(png|gif|jpg|svg|webp)$)/.test(pathname)) {
  113. // image
  114. e = doc.createElement('img');
  115. e.src = pathStripped;
  116. } else {
  117. // javascript
  118. e = doc.createElement('script');
  119. e.src = path;
  120. e.async = async === undefined ? true : async;
  121. }
  122. e.onload = e.onerror = e.onbeforeload = function (ev) {
  123. var result = ev.type[0];
  124. // treat empty stylesheets as failures to get around lack of onerror
  125. // support in IE9-11
  126. if (isLegacyIECss) {
  127. try {
  128. if (!e.sheet.cssText.length) result = 'e';
  129. } catch (x) {
  130. // sheets objects created from load errors don't allow access to
  131. // `cssText` (unless error is Code:18 SecurityError)
  132. if (x.code != 18) result = 'e';
  133. }
  134. }
  135. // handle retries in case of load failure
  136. if (result == 'e') {
  137. // increment counter
  138. numTries += 1;
  139. // exit function and try again
  140. if (numTries < maxTries) {
  141. return loadFile(path, callbackFn, args, numTries);
  142. }
  143. } else if (e.rel == 'preload' && e.as == 'style') {
  144. // activate preloaded stylesheets
  145. return e.rel = 'stylesheet'; // jshint ignore:line
  146. }
  147. // execute callback
  148. callbackFn(path, result, ev.defaultPrevented);
  149. };
  150. // add to document (unless callback returns `false`)
  151. if (beforeCallbackFn(path, e) !== false) doc.head.appendChild(e);
  152. }
  153. /**
  154. * Load multiple files.
  155. * @param {string[]} paths - The file paths
  156. * @param {Function} callbackFn - The callback function
  157. */
  158. function loadFiles(paths, callbackFn, args) {
  159. // listify paths
  160. paths = paths.push ? paths : [paths];
  161. var numWaiting = paths.length,
  162. x = numWaiting,
  163. pathsNotFound = [],
  164. fn,
  165. i;
  166. // define callback function
  167. fn = function(path, result, defaultPrevented) {
  168. // handle error
  169. if (result == 'e') pathsNotFound.push(path);
  170. // handle beforeload event. If defaultPrevented then that means the load
  171. // will be blocked (ex. Ghostery/ABP on Safari)
  172. if (result == 'b') {
  173. if (defaultPrevented) pathsNotFound.push(path);
  174. else return;
  175. }
  176. numWaiting--;
  177. if (!numWaiting) callbackFn(pathsNotFound);
  178. };
  179. // load scripts
  180. for (i=0; i < x; i++) loadFile(paths[i], fn, args);
  181. }
  182. /**
  183. * Initiate script load and register bundle.
  184. * @param {(string|string[])} paths - The file paths
  185. * @param {(string|Function|Object)} [arg1] - The (1) bundleId or (2) success
  186. * callback or (3) object literal with success/error arguments, numRetries,
  187. * etc.
  188. * @param {(Function|Object)} [arg2] - The (1) success callback or (2) object
  189. * literal with success/error arguments, numRetries, etc.
  190. */
  191. function loadjs(paths, arg1, arg2) {
  192. var bundleId,
  193. args;
  194. // bundleId (if string)
  195. if (arg1 && arg1.trim) bundleId = arg1;
  196. // args (default is {})
  197. args = (bundleId ? arg2 : arg1) || {};
  198. // throw error if bundle is already defined
  199. if (bundleId) {
  200. if (bundleId in bundleIdCache) {
  201. throw "LoadJS";
  202. } else {
  203. bundleIdCache[bundleId] = true;
  204. }
  205. }
  206. function loadFn(resolve, reject) {
  207. loadFiles(paths, function (pathsNotFound) {
  208. // execute callbacks
  209. executeCallbacks(args, pathsNotFound);
  210. // resolve Promise
  211. if (resolve) {
  212. executeCallbacks({success: resolve, error: reject}, pathsNotFound);
  213. }
  214. // publish bundle load event
  215. publish(bundleId, pathsNotFound);
  216. }, args);
  217. }
  218. if (args.returnPromise) return new Promise(loadFn);
  219. else loadFn();
  220. }
  221. /**
  222. * Execute callbacks when dependencies have been satisfied.
  223. * @param {(string|string[])} deps - List of bundle ids
  224. * @param {Object} args - success/error arguments
  225. */
  226. loadjs.ready = function ready(deps, args) {
  227. // subscribe to bundle load event
  228. subscribe(deps, function (depsNotFound) {
  229. // execute callbacks
  230. executeCallbacks(args, depsNotFound);
  231. });
  232. return loadjs;
  233. };
  234. /**
  235. * Manually satisfy bundle dependencies.
  236. * @param {string} bundleId - The bundle id
  237. */
  238. loadjs.done = function done(bundleId) {
  239. publish(bundleId, []);
  240. };
  241. /**
  242. * Reset loadjs dependencies statuses
  243. */
  244. loadjs.reset = function reset() {
  245. bundleIdCache = {};
  246. bundleResultCache = {};
  247. bundleCallbackQueue = {};
  248. };
  249. /**
  250. * Determine if bundle has already been defined
  251. * @param String} bundleId - The bundle id
  252. */
  253. loadjs.isDefined = function isDefined(bundleId) {
  254. return bundleId in bundleIdCache;
  255. };
  256. // export
  257. return loadjs;
  258. }));