Skip to content
This repository has been archived by the owner on Nov 14, 2022. It is now read-only.

An attempt to bring Dependency Injection to javascript via RequireJS

Notifications You must be signed in to change notification settings

Luismahou/requirejs-glue-plugin

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Build Status Dependency Status devDependency Status

Introduction

As a Java guy, one of the things that I missed more when I switch to Javascript was Google Guice.

GlueJs is an attempt to fill that gap in combination with RequireJS.

GlueJs is implemented as a RequireJS plugin.

Learn by example

RequireJS configuration:

requirejs.config({
    paths: {
        glue: '/path/to/require/glue/plugin'
    }
});

GlueJs populates the binder. The binder allows you to bind RequireJS paths.

// Gets the binder. Note that the module name starts with '#'. The hash is used to distinguish between normal modules and GlueJS ones
var binder = require('glue!#binder');

You can bind Foo into the singleton scope.

binder.bind('Foo').inSingleton();

// Every time that require('glue!Foo') is executed, it will return the same instance

Or to an instance:

var foo = createFoo();
binder.bind('Foo').toInstance(foo);

// require('glue!Foo') will return "foo"

Or to a factory method:

binder.bind('Foo').to(BarClass);

// new require('glue!Foo') will create an instance of BarClass

You can also annotate modules:

binder.bind('Foo').annotatedWith('red').toInstance(redFoo);
binder.bind('Foo').annotatedWith('blue').toInstance(blueFoo);

// require('glue!Foo@red') will return "redFoo" while require('glue!Foo@blue') will return "blueFoo"

binder.bind('Foo').annotatedWith('bar').to(BarClass);

// new require(glue!Foo@bar) will create an instance of BarClass

Constraints

Modules loaded via GlueJS must ALWAYS return a class. The magic happens when you try to create an instance of the module:

define(function(require) {
    var Foo = require('glue!Foo');

    var Bar = function() {
        // When "new" is executed GlueJS determines how to instantiate "Foo"
        var foo = new Foo();
    };

    return Bar;
});

Is Dependency Injection really necessary in a language like Javascript?

IMHO is not as important as in Java. You can definitely write a successful app without it. Besides, RequireJS brings some similar features, for example, you can convert Foo into a singleton by simply:

define(function(require) {
    var Foo = function() {
        // ...
    };
    var singleton = new Foo()
    // require('Foo') will always return a reference to 'singleton'
    return singleton;
});

The problem arises when you try to test your code. If Foo stores state, you'll need to clean it after every test to ensure that you're not leaking that state from one test to another. For example:

// Counter definition
define(function(require) {
    var Counter = function() {
        this.counter = 0;
    };
    Counter.prototype.increment = function() {
        this.counter++;
    };
    Counter.prototype.decrement = function() {
        this.counter--;
    };
    Counter.prototype.getCounter = function() {
        return this.counter;
    };

    // Returning a singleton
    return new Counter();
});

// In a test file
define(function(require) {
    var counter = require('counter');

    describe('Counter', function() {
        it('should increment counter by one', function() {
            counter.increment();
            expect(counter.getCounter()).to.equal(1);
        });
        it('should decrement counter by one', function() {
            counter.decrement();
            // It will fail, because the counter was modified in the previous test.
            expect(counter.getCounter()).to.equal(-1);
        });
    });
});

With GlueJS the test would look like:

// Counter definition
define(function(require) {
    var Counter = function() {
        this.counter = 0;
    };
    Counter.prototype.increment = function() {
        this.counter++;
    };
    Counter.prototype.decrement = function() {
        this.counter--;
    };
    Counter.prototype.getCounter = function() {
        return this.counter;
    };

    // Returning the class
    return Counter();
});

// In a test file
define(function(require) {
    var binder  = require('glue!#binder');
    var Counter = require('glue!Counter');

    describe('Counter', function() {
        beforeEach(function() {
            binder.bind('Counter').inSingleton();

            this.counter = new Counter();
        });
        afterEach(function() {
            // Utility method that removes all the references to objects
            // instantiated by GlueJS
            binder.clearBindings();
        });
        it('should increment counter by one', function() {
            this.counter.increment();
            expect(this.counter.getCounter()).to.equal(1);
        });
        it('should decrement counter by one', function() {
            this.counter.decrement();
            // Now, this test will pass, because the singleton instance is re-instantiated from test to test.
            expect(this.counter.getCounter()).to.equal(-1);
        });
    });
});

About

An attempt to bring Dependency Injection to javascript via RequireJS

Resources

Stars

Watchers

Forks

Packages

No packages published