|
@@ -0,0 +1,316 @@
|
|
|
+(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;
|
|
|
+
|
|
|
+}));
|