123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- (function(root, factory) {
- if (typeof define === 'function' && define.amd) {
- define([], factory);
- } else if (typeof exports === 'object') {
- module.exports = factory();
- } else {
- root.loadjs = factory();
- }
- }(this, function() {
- /**
- * Global dependencies.
- * @global {Object} document - DOM
- */
- var devnull = function() {},
- bundleIdCache = {},
- bundleResultCache = {},
- bundleCallbackQueue = {};
- /**
- * Subscribe to bundle load event.
- * @param {string[]} bundleIds - Bundle ids
- * @param {Function} callbackFn - The callback function
- */
- function subscribe(bundleIds, callbackFn) {
- // listify
- bundleIds = bundleIds.push ? bundleIds : [bundleIds];
- var depsNotFound = [],
- i = bundleIds.length,
- numWaiting = i,
- fn,
- bundleId,
- r,
- q;
- // define callback function
- fn = function (bundleId, pathsNotFound) {
- if (pathsNotFound.length) depsNotFound.push(bundleId);
- numWaiting--;
- if (!numWaiting) callbackFn(depsNotFound);
- };
- // register callback
- while (i--) {
- bundleId = bundleIds[i];
- // execute callback if in result cache
- r = bundleResultCache[bundleId];
- if (r) {
- fn(bundleId, r);
- continue;
- }
- // add to callback queue
- q = bundleCallbackQueue[bundleId] = bundleCallbackQueue[bundleId] || [];
- q.push(fn);
- }
- }
- /**
- * Publish bundle load event.
- * @param {string} bundleId - Bundle id
- * @param {string[]} pathsNotFound - List of files not found
- */
- function publish(bundleId, pathsNotFound) {
- // exit if id isn't defined
- if (!bundleId) return;
- var q = bundleCallbackQueue[bundleId];
- // cache result
- bundleResultCache[bundleId] = pathsNotFound;
- // exit if queue is empty
- if (!q) return;
- // empty callback queue
- while (q.length) {
- q[0](bundleId, pathsNotFound);
- q.splice(0, 1);
- }
- }
- /**
- * Execute callbacks.
- * @param {Object or Function} args - The callback args
- * @param {string[]} depsNotFound - List of dependencies not found
- */
- function executeCallbacks(args, depsNotFound) {
- // accept function as argument
- if (args.call) args = {success: args};
- // success and error callbacks
- if (depsNotFound.length) (args.error || devnull)(depsNotFound);
- else (args.success || devnull)(args);
- }
- /**
- * Load individual file.
- * @param {string} path - The file path
- * @param {Function} callbackFn - The callback function
- */
- function loadFile(path, callbackFn, args, numTries) {
- var doc = document,
- async = args.async,
- maxTries = (args.numRetries || 0) + 1,
- beforeCallbackFn = args.before || devnull,
- pathname = path.replace(/[\?|#].*$/, ''),
- pathStripped = path.replace(/^(css|img)!/, ''),
- isLegacyIECss,
- e;
- numTries = numTries || 0;
- if (/(^css!|\.css$)/.test(pathname)) {
- // css
- e = doc.createElement('link');
- e.rel = 'stylesheet';
- e.href = pathStripped;
- // tag IE9+
- isLegacyIECss = 'hideFocus' in e;
- // use preload in IE Edge (to detect load errors)
- if (isLegacyIECss && e.relList) {
- isLegacyIECss = 0;
- e.rel = 'preload';
- e.as = 'style';
- }
- } else if (/(^img!|\.(png|gif|jpg|svg|webp)$)/.test(pathname)) {
- // image
- e = doc.createElement('img');
- e.src = pathStripped;
- } else {
- // javascript
- e = doc.createElement('script');
- e.src = path;
- e.async = async === undefined ? true : async;
- }
- e.onload = e.onerror = e.onbeforeload = function (ev) {
- var result = ev.type[0];
- // treat empty stylesheets as failures to get around lack of onerror
- // support in IE9-11
- if (isLegacyIECss) {
- try {
- if (!e.sheet.cssText.length) result = 'e';
- } catch (x) {
- // sheets objects created from load errors don't allow access to
- // `cssText` (unless error is Code:18 SecurityError)
- if (x.code != 18) result = 'e';
- }
- }
- // handle retries in case of load failure
- if (result == 'e') {
- // increment counter
- numTries += 1;
- // exit function and try again
- if (numTries < maxTries) {
- return loadFile(path, callbackFn, args, numTries);
- }
- } else if (e.rel == 'preload' && e.as == 'style') {
- // activate preloaded stylesheets
- return e.rel = 'stylesheet'; // jshint ignore:line
- }
-
- // execute callback
- callbackFn(path, result, ev.defaultPrevented);
- };
- // add to document (unless callback returns `false`)
- if (beforeCallbackFn(path, e) !== false) doc.head.appendChild(e);
- }
- /**
- * Load multiple files.
- * @param {string[]} paths - The file paths
- * @param {Function} callbackFn - The callback function
- */
- function loadFiles(paths, callbackFn, args) {
- // listify paths
- paths = paths.push ? paths : [paths];
- var numWaiting = paths.length,
- x = numWaiting,
- pathsNotFound = [],
- fn,
- i;
- // define callback function
- fn = function(path, result, defaultPrevented) {
- // handle error
- if (result == 'e') pathsNotFound.push(path);
- // handle beforeload event. If defaultPrevented then that means the load
- // will be blocked (ex. Ghostery/ABP on Safari)
- if (result == 'b') {
- if (defaultPrevented) pathsNotFound.push(path);
- else return;
- }
- numWaiting--;
- if (!numWaiting) callbackFn(pathsNotFound);
- };
- // load scripts
- for (i=0; i < x; i++) loadFile(paths[i], fn, args);
- }
- /**
- * Initiate script load and register bundle.
- * @param {(string|string[])} paths - The file paths
- * @param {(string|Function|Object)} [arg1] - The (1) bundleId or (2) success
- * callback or (3) object literal with success/error arguments, numRetries,
- * etc.
- * @param {(Function|Object)} [arg2] - The (1) success callback or (2) object
- * literal with success/error arguments, numRetries, etc.
- */
- function loadjs(paths, arg1, arg2) {
- var bundleId,
- args;
- // bundleId (if string)
- if (arg1 && arg1.trim) bundleId = arg1;
- // args (default is {})
- args = (bundleId ? arg2 : arg1) || {};
- // throw error if bundle is already defined
- if (bundleId) {
- if (bundleId in bundleIdCache) {
- throw "LoadJS";
- } else {
- bundleIdCache[bundleId] = true;
- }
- }
- function loadFn(resolve, reject) {
- loadFiles(paths, function (pathsNotFound) {
- // execute callbacks
- executeCallbacks(args, pathsNotFound);
-
- // resolve Promise
- if (resolve) {
- executeCallbacks({success: resolve, error: reject}, pathsNotFound);
- }
- // publish bundle load event
- publish(bundleId, pathsNotFound);
- }, args);
- }
-
- if (args.returnPromise) return new Promise(loadFn);
- else loadFn();
- }
- /**
- * Execute callbacks when dependencies have been satisfied.
- * @param {(string|string[])} deps - List of bundle ids
- * @param {Object} args - success/error arguments
- */
- loadjs.ready = function ready(deps, args) {
- // subscribe to bundle load event
- subscribe(deps, function (depsNotFound) {
- // execute callbacks
- executeCallbacks(args, depsNotFound);
- });
- return loadjs;
- };
- /**
- * Manually satisfy bundle dependencies.
- * @param {string} bundleId - The bundle id
- */
- loadjs.done = function done(bundleId) {
- publish(bundleId, []);
- };
- /**
- * Reset loadjs dependencies statuses
- */
- loadjs.reset = function reset() {
- bundleIdCache = {};
- bundleResultCache = {};
- bundleCallbackQueue = {};
- };
- /**
- * Determine if bundle has already been defined
- * @param String} bundleId - The bundle id
- */
- loadjs.isDefined = function isDefined(bundleId) {
- return bundleId in bundleIdCache;
- };
- // export
- return loadjs;
- }));
|