4
\$\begingroup\$

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.

\$\endgroup\$
5
  • \$\begingroup\$ Parts of it are very clear, but the callback handling could use some comments or reworking. You have obj which can be a function or an object with a callback property, and libs_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\$ Commented May 10, 2011 at 1:52
  • \$\begingroup\$ Thanx. But 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\$
    – user4126
    Commented May 10, 2011 at 5:05
  • \$\begingroup\$ Right, libs is the one with the optional callbacks property. \$\endgroup\$ Commented May 10, 2011 at 6:00
  • 4
    \$\begingroup\$ I don't really understand how loading libraries asynchronously will reduce HTTP connections or even traffic. Can you clarify? \$\endgroup\$
    – user185
    Commented Jul 26, 2011 at 11:06
  • \$\begingroup\$ @EricBréchemier With this library we load needed libs only if they are not loaded later - so, this will reduce traffic and HTTP connections. As you can see in first sentence, this lib created for using on 3rd party websites by webmasters. \$\endgroup\$
    – user4126
    Commented Sep 27, 2011 at 5:05

1 Answer 1

2
\$\begingroup\$

My 2 cents:

  • please use lowercase camelcase ( lib_is_list -> libIsList )
  • The first 5 lines seem clumsy
    • Checking for an array with typeof 'object' is odd
    • Changing the type of a variable/parameter is odd and not recommended, you do this twice
    • Dont drop newlines ( for (var i in lib) require(lib[i], obj, libs_obj); )
  • You can use lib.callbacks = lib.callbacks || [] instead of if (lib.callbacks === undefined) lib.callbacks = [];
  • Why would you assign first callbacks, and then check() whether you should return
  • Your handling of obj , count and callbacks is roundabout, the lack of newlines dont help,
  • The function name ready() is unfortunate, ready is most commly used for the callback after http requests
  • You should consider merging/reworking ready/script_downloaded/download_script
  • The src parameter is unfortunate, since it does not contain the source, maybe url ?
  • I like defensive programming, but checking for callback seems much, since your code guarantees it

Overal, I have to say this seems over-engineered, I think you are trying to be too smart.

\$\endgroup\$