At my work, I need to create a script that third-party webmasters could include in their pages without need to include something else. But this script had dependencies on jQuery and some amount of their plug-ins.
On the Internet, I have found libraries that have same functionality except for an important one: they don't check are needed library already exist on a page.
Yes, I could execute the needed libraries in local scope of my script, but I've decided to reduce the number of HTTP connections and traffic with this function:
var require = function (lib, obj, libs_obj) {
// if obj is function than `require` called directly by user and we
// must transform it to object. It's for reduce number of used
// variables. When we call `require` recursively, we use this object
// instead of function
var lib_is_list = typeof(lib) === 'object';
if (typeof obj === 'function') obj = { callback: obj, count: lib_is_list ? lib.length : 1 }
if (lib_is_list) { // this is list of libs
for (var i in lib) require(lib[i], obj, libs_obj);
return;
}
var lib = libs_obj[lib];
if (lib.callbacks === undefined) lib.callbacks = [];
if (lib.check()) { if (obj.callback) obj.callback(); return; }
lib.callbacks.push(obj);
if (lib.pending) { return; }
lib.pending = true;
function ready() {
function script_downloaded() {
lib.pending = false;
var obj;
while (obj = lib.callbacks.pop()) {
obj.count--; if (obj.count == 0) obj.callback();
}
}
download_script(lib.link, script_downloaded);
}
function download_script(src, callback) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = 'async';
script.src = src;
// Based on jQuery jsonp trick
if (callback) {
script.onload = script.onreadystatechange = function() {
if (!script.readyState || /loaded|complete/.test(script.readyState)) {
script.onload = script.onreadystatechange = null;
callback();
}
};
}
document.getElementsByTagName('head')[0].appendChild(script);
}
var deps_count = lib.deps ? lib.deps.length : 0;
if (deps_count < 1) { ready(); return; }
var new_obj = { callback: ready, count: deps_count };
require(lib.deps, new_obj, libs_obj);
};
This function work in IE6+ (and, of course, in other browsers) and written in pure JS. To call this function, use syntax like this:
require(['list', 'of', 'libraries'], function () { alert 'All libs loaded'; }, libs_obj),
where libs_obj is object like this:
{
list: {
check: function() { return list_exist(); },
// function to check whether the required functionality
link: 'js/list_js_file.js',
// link to script
deps: ['libraries', 'of']
// list of dependencies of current library
// If not, library doesn't have dependencies
},
of: {
check: function() { return of_exist_and_version_is('1.2.3'); },
link: 'js/another_file.js',
},
libraries: {
check: funcion() { return libraries_exist() || analog_exist(); },
link: 'http://www.example.com/js/libraries.js',
deps: ['of']
}
}
Callback function are optional - if we don't need it, we can just type false
or undefined
. Of course, this function must be called after all third-party scripts. Bottom of page is better place to script with this function. Please tell me where I went wrong or give me useful advice.
obj
which can be a function or an object with acallback
property, andlibs_obj
can have its own callbacks (one or many per library?). In some cases these are combined, and it wasn't clear when or why. Of course, I read it last night and only got a chance to comment on it today. :) \$\endgroup\$libs_obj
doesn't have a callback for libraries - it just a object that contain info about each library - how to check its availability, path to it and list of dependencies. \$\endgroup\$libs
is the one with the optionalcallbacks
property. \$\endgroup\$