You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

278 lines
8.0 KiB

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