3
\$\begingroup\$

I got bored had time to kill in work and was creating a few things using plain JS (no frameworks allowed! I wanted to get used to progressive enhancement). I realized I would run into a problem if I wanted to access things in other scopes that might be loaded after my current file, and I didn't want to have to explicitly order the script tags.

I also figured that assigning to the window is kinda 'meh' and I would like to avoid it where possible to avoid exposing global state.

As a result, I wrote a quick n dirty dependency injector for the browser. It doesn't load files asynchronously (i.e, it's not RequireJS), but it's just meant to be simple enough for a small website.

Unfortunately it will only work with ES6-compliant browsers, unless you have a Promise polyfill. I consider this a fair enough trade off since I much prefer promises to callbacks.

;(function(window, Promise, exports) {
  'use strict';
  var types = {};

  /**
   * Register a type with the injector.
   * @param  {String} name         The name of the type to register.
   * @param  {Function} constructor  The constructor for the injector. Constructors can be regular function; `this` will be bound to the constructor function itself. Constructs may register their dependencies by placing an $inject field that is a string array. Each dependency will be resolved before instantiating the constructor. If the dependency cannot be fulfilled, an Error will be thrown.
   */
  exports.register = function(name, constructor) {
    var bundle = {
      dependencies: constructor.$inject || [],
      constructor: constructor
    };
    types[name] = bundle;
  };


  var resolveQueue = [];
  /**
   * Resolve a type. Note that this function will do nothing until the DOM has been loaded (this allows for the injector to be sure it has found every dependency).
   * @param  {String} name The name of the type to resolve.
   * @return {Promise<Object>}      A promise that will resolve to the resolved object, or reject upon error.
   */
  exports.resolve = function(name) {
    return new Promise(function(resolve, reject) {
      resolveQueue.push(function() {
        var bundle = types[name];
        if(bundle === undefined) {
          reject(new Error('could not find type: '+ name));
          return;
        }
        var dependencies = [];
        for (var i = 0; i < bundle.dependencies.length; i++) {
          var dependency = exports.resolve(bundle.dependencies[i]);
          dependencies.push(dependency);
        };
        return Promise.all(dependencies)
          .then(function(resolvedDependencies) {
            resolve(bundle.constructor.apply(bundle.constructor, resolvedDependencies));
          });
      });
    });
  };

  // Only process the resolve queue after we have loaded.
  // This is because we may not have the guarantee that all of our scripts
  // (and thus types) have been loaded yet, so some scripts loaded in the incorrect
  // order may depend on other scripts that are not loaded yet.
  window.addEventListener('load', function() {
    for (var i = 0; i < resolveQueue.length; i++) {
      resolveQueue[i]();
    };
  });
})(window, Promise, window.inject = window.inject || {});

Should be fairly simple and lightweight to use: inject.register(typeName, constructor) to register a type, inject.resolve(name) to resolve a type.

Here is a working example (you may review this, although it's just here to provide an example for how I want this to be used, rather than actual review code):

;(function(inject, window) {
  'use strict';
  if(inject === undefined) {
    throw new Error('I require my dependency injector to function!');
  }

  var factories = {};

  function create(querySelector, onCreated) {
    factories[querySelector] = onCreated;
  };

  var createDirectives = function(document) {
    for (var querySelector in factories) {
      if (factories.hasOwnProperty(querySelector)) {
        var factory = factories[querySelector];
        var elements = document.querySelectorAll(querySelector);
        for (var i = 0; i < elements.length; i++) {
          factory(elements[i]);
        };
      }
    }
  };

  inject.register('directive', function() {
    return {
      create: create
    };
  });

  window.addEventListener('load', function() {
    createDirectives(document);
  })
})(window.inject, window);
\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.