jaredhanson / electrolyte

Elegant dependency injection for Node.js.
MIT License
564 stars 59 forks source link

Automatically Detect Dependencies #36

Open konstantinkrassmann opened 8 years ago

konstantinkrassmann commented 8 years ago

At first, great job. This DI framework is really great.

But while working with angular, i really like to simply require a dependency by just naming the parameter correctly.

Example:

module.exports = function TimeAlarm(moment) { // Lets roll }

This removes the "@require" annotation.

Maybe my attempt is already helpful:

To get the parameter of a function, i copied the auto-annotation function from angular's di code:

function isArray(arr){
  return typeof arr.length != "undefined"
}

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function annotate(fn) {
  var $inject,
      fnText,
      argDecl,
      last;

  if (typeof fn == 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      if (fn.length) {
        fnText = fn.toString().replace(STRIP_COMMENTS, '');
        argDecl = fnText.match(FN_ARGS);
        argDecl[1].split(FN_ARG_SPLIT).forEach(function (arg) {
          arg.replace(FN_ARG, function (all, underscore, name) {
            $inject.push(name);
          });
        });
      }
      fn.$inject = $inject;
    }
  } else if (isArray(fn)) {
    last = fn.length - 1;
    // assertArgFn(fn[last], 'fn');
    $inject = fn.slice(0, last);
  } else {
    // assertArgFn(fn, 'fn', true);
  }
  return $inject;
}

This will simply give an array of the parameter names of passed function.

Example from the code:

Container.prototype._registerModule = function(id, mod, sid) {
  var dependencies = mod['@require'] || []
    , pattern = 'literal';

  if(typeof mod != "undefined" && (!mod['@require'])) {
    //Will print ["moment"] for the TimeAlarm component
    console.log(annotate(mod));  
    dependencies = annotate(mod);
  }
  if (typeof mod == 'function') {  [...]

(container.js:235)

I did some changes, but i get stuck when my application uses paths to reach the components.

I changed the registering function:

Container.prototype.register = function(comp, sid) {
  // TODO: Pass sid to constructor (??)
  comp._sid = sid;

  //This will register components defined with paths as well
  //Example: "utils/Logger" will be available under "utils/Logger" and just "Logger"
  var arrSplitPath = comp.id.split("/");
  if(typeof this._o[arrSplitPath[arrSplitPath.length-1]] != "undefined"){
    this._o[arrSplitPath[arrSplitPath.length-1]] = comp;
  }

  this._o[comp.id] = comp;
}

(container.js:132)

With this change, my annotation will inject 'Logger' correctly.

But when i use ioc.node, the application cant find the components.

Example: In my code i've got something like this:

ioc.loader("components", ioc.node("src/core/components"));

The required components is under "components/alarm/AlarmManager"

I will require "AlarmManager" by auto-annotation.

It will call the function Container.prototype._loadModule And there it fails, because i cant simply search within source.fn of each node, since it will try to load the script with scripts.resolve directly.