3
\$\begingroup\$

I'm working on a webapp that I use gulp to build. It uses useref to gather and concatenate/uglify the assets used in the HTML files.

In this case, as things are getting concatenated, it doesn't make much sense to use a framework like RequireJS to separate out my modules, and I think browserify is overkill as I don't need access to any node equivalents on the client side.

I have seen that some apps use a global App object that they attach things to. I wanted something slightly more robust so that I could "require" and "define" and not have the modules exposed on the App object, and be able to name them like in a require() arguments list instead of using App.component every time I wanted to reference one.

Here is my attempt for simple code that emulates a full on require/define methodology. This allows me to put the <script> tags that get concatenated in any order I want to in the HTML, and name the modules as they get passed in to definitions of other modules that require them.

'use strict';

(function(window) {

    var components = {},
        waiting = {};

    window.App = {};

    App.define = function(name, reqs, func) {
        if (typeof reqs === "function") {
            func = reqs;
            reqs = [];
        }
        App.require(reqs, func, name);
    };

    App.require = function(reqs, func, name) {
        var loaded = 0;

        if (!reqs.length) load(func, reqs, name);

        function callback() {
            if (++loaded === reqs.length) {
                load(func, reqs, name);
            }
        }

        for (var i = 0; i < reqs.length; i++) {
            if (components.hasOwnProperty(reqs[i])) {
                callback();
            } else {
                waitFor(reqs[i], callback);
            }
        }
    };

    function waitFor(name, callback) {
        if (waiting.hasOwnProperty(name)) {
            waiting[name].push(callback);
        } else {
            waiting[name] = [callback];
        }
    }

    function load(func, reqs, name) {
        var result = func.apply(window, orderedReqs(reqs));
        if (name) {
            components[name] = result;

            if (waiting.hasOwnProperty(name)) {
                for (var i = 0; i < waiting[name].length; i++) {
                    waiting[name][i](name);
                }
            }

            delete waiting[name];
        }
    }

    function orderedReqs(reqs) {
        var args = [];

        for (var i = 0; i < reqs.length; i++) {
            args.push(components[reqs[i]]);
        }

        return args;
    }

})(window);

Then I can do this in my module javascript files:

App.define('a', function() {
    console.log('a loaded');
});

App.define('b', ['a'], function(a) {
    // use a to do something if I had made it an object
    console.log('b loaded');
});

This custom solution I think is simple enough for my needs but also lends itself to some nice code with only one global and the module definitions hidden away inside of it.

What do other people do when they want something vanilla? Does this seem like a good solution? Feel free to critique my coding style, as any criticism is good criticism.

\$\endgroup\$
  • \$\begingroup\$ Welcome to CodeRievew, unilat. You have quite the interesting bit of code here, I hope you get some fine answers. \$\endgroup\$ – Legato Apr 25 '15 at 20:10
1
\$\begingroup\$

First off, you could start by naming the variables meaningfully. Your define looks like this:

App.define = function(name, reqs, func) {
    if (typeof reqs === "function") {
        func = reqs;
        reqs = [];
    }
    App.require(reqs, func, name);
};

If I were another dev (which I am), I'd wonder what reqs mean. Is it requests? Requirements? Oh wait, dependencies! That's the word. How about func? Should it be a callback? Hmm... probably moduleDefinition would be better, right? Naming them properly would allow others to review your code easily, and maybe allow you to remember your code, just in case you went on vacation for 2 weeks.

Now I know you're trying to emulate AMD, which is fine (I personally prefer it over Browserify). However, one recurring complaint from devs is the issue of the "array of dependencies". What if you had more than a handful of dependencies for the module?

App.define('MyModule',['oh','my','vegetables','thats','very','long'], function('its','very','unweildly','dont','you','say'){
  // End of ceremonious module definition.
});

I personally would recommend the "wrapped CommonJS" approach, where you pass in require into your module definition, and just have it run there. I'm assuming all your modules are loaded, nothing async and your solution is just to have them load in any order.

App.define('MyModule', function(require){
  var Foo = require('foo');
  var Bar = require('bar');
  var John = require('john');
  // and so on
});

Though technically longer in the long run, but it's more manageable and readable than an array and another list of variable names. Another advantage, unlike the real AMD (which does static analysis to fine CJS-style requires), is that your "modules" are already loaded in memory. Calling require this way, synchronously, is possible as require will merely return a reference to the loaded module.

\$\endgroup\$
  • \$\begingroup\$ Great points about naming. But I'm curious, you say you prefer AMD over browserify, is that solely because of the async or because of the style? Browserify also uses the require('module'); approach. In the second approach I'm not sure how I would start the implementation. With my current code, the dependencies are explicit and can be resolved prior to calling the definition function. With this second approach, I would have to call the definition, but postpone code after the require()s until resolution (unless I define a standard of not using any required modules in the definition itself) \$\endgroup\$ – Unilat Apr 27 '15 at 0:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.