Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.0 Redesign #30

Merged
merged 21 commits into from Feb 4, 2017
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
164 changes: 81 additions & 83 deletions README.md
@@ -1,5 +1,4 @@
Pluto.js
========
# Pluto.js

_"JavaScript dependency injection that's so small, it almost doesn't count."_

Expand All @@ -8,128 +7,127 @@ _"JavaScript dependency injection that's so small, it almost doesn't count."_
| Master | [![Build Status](https://travis-ci.org/ecowden/pluto.js.png?branch=master)](https://travis-ci.org/ecowden/pluto.js) [![Coverage Status](https://coveralls.io/repos/github/ecowden/pluto.js/badge.svg?branch=master)](https://coveralls.io/github/ecowden/pluto.js?branch=master) |
| All | [![Build Status](https://travis-ci.org/ecowden/pluto.js.png)](https://travis-ci.org/ecowden/pluto.js) |

What is Pluto?
--------------
## What is Pluto?

Pluto is a JavaScript dependency injection tool.

Dependency injection is a spiffy way to assemble your applications. It decouples the various bits and makes your app testable. An introduction to dependency injection principles is currently beyond the scope of this guide.

Installing Pluto
----------------
## Installing Pluto

Pluto is designed to be used with [Node](http://nodejs.org/) and [NPM](http://npmjs.org/). From the root of a Node
project, execute
project, execute:

```
npm install pluto
$ npm install pluto --save
```

Alternately, add a line to the `dependencies` section of your `package.json` and then run `npm install` in your
project directory.

```
{
"name": "my-awesome-application",
"dependencies": {
"pluto": "0.4.0"
}
}
```
## How to Pluto?

_Note: I'll try to keep the above version up to date, but you may want to check the
[Pluto NPM Page](https://npmjs.org/package/pluto) for the most recent version._
A binder is the basic unit of Pluto's dependency injection. It maps names to objects you want.

How to Pluto?
-------------
A module is the basic unit of Pluto's dependency injection. It maps names to objects you want.
Pluto's injection is done in a few steps:

Pluto's injection is done in two steps. First, create a module. When you do this, you bind names to any combination of objects, factory functions and constructor functions. Second, call module.get(...) and pass a name. Pluto will give you the thing mapped to that name. Along the way, it will inject parameters that match other names bound in the module.
1. Create bindings. When you do this, you bind names to any combination of objects, factory functions and constructor functions.
2. Optionally, call `.get(...)`. Pluto will give you the thing mapped to that name. Along the way, it will inject parameters that match other names bound in the binder and resolve Promises as appropriate.
3. Alternately, call `.bootstrap()` to run all your factory functions and constructors, and resolve all promises. This is handy if you're trying to start up an application with a bunch of moving parts, and more common than using `.get(...)` for each part individually.

There are three things you can bind to a name: an object instance, a constructor function and a factory function.

### Promises

If you pass Pluto a promise, it will resolve it. If your factory or constructor function returns a promise, Pluto will resolve it before injecting the result into other components.

### Instance Binding

The simplest binding is to bind a name to an instance:

``` js
var anInstance = {}; // can be any JavaScript object
var module = pluto.createModule(function (bind) {
bind("myInstance").toInstance(anInstance);
});

expect(module.get("myInstance")).toBe(anInstance);
const anInstance = {} // can be any JavaScript object, or a Promise
const bind = pluto()
bind('myInstance').toInstance(anInstance)

// bind.get(...) gives us a Promise that resolves to our instance
bind.get('myInstance').then((myInstance) => {
t.is(myInstance, anInstance)
})
```

You can also bind to a constructor function (i.e., a function that is meant to be used with the "new" keyword to create a new object). When you call module.get(...), Pluto will invoke the Constructor using "new" and return the result. If the constructor has any parameters, Pluto will consult its bindings and pass them into the constructor:
### Constructor Binding

``` js
var aGreeting = "Hello, world!";
var Greeter = function (greeting) {
this.greeting = greeting;
};
You can also bind to a constructor function (i.e., a function that is meant to be used with the `new` keyword to create a new object). When you call `.get(...)`, Pluto will invoke the Constructor using `new` and return the result. If the constructor has any parameters, Pluto will consult its bindings and pass them into the constructor:

Greeter.prototype.greet = function () {
return this.greeting;
};
```js
function Greeter(greeting, name) {
this.greeting = greeting
this.name = name
}

var module = pluto.createModule(function (bind) {
bind("greeting").toInstance(aGreeting);
bind("greeter").toConstructor(Greeter);
});
Greeter.prototype.greet = function () {
return `${this.greeting}, ${this.name}!`
}

var theGreeter = module.get("greeter");
const bind = pluto()
bind('greeting').toInstance('Hello')
bind('name').toInstance(Promise.resolve('World')) // A promise will work, too
bind('greeter').toConstructor(Greeter)

expect(theGreeter.greet()).toBe("Hello, world!");
bind.get('greeter').then((myGreeter) => {
t.is(myGreeter.greet(), 'Hello, World!')
})
```

Similarly, you can bind to a factory function -- that is, a function that creates some other object. When you call module.get(...), Pluto will invoke the function and return the result. Just like with a constructor, if the factory function has any parameters, Pluto will consult its bindings and pass them into the factory:
### Factory Function Binding

``` js
var aGreeting = "Hello, world!";
var greeterFactory = function (greeting) {
return function () {
return greeting;
};
};
Similarly, you can bind to a factory function -- that is, a function that creates some other object. When you call `.get(...)`, Pluto will invoke the function and return the result. Just like with a constructor, if the factory function has any parameters, Pluto will consult its bindings and pass them into the factory:

var module = pluto.createModule(function (bind) {
bind("greeting").toInstance(aGreeting);
bind("greeter").toFactory(greeterFactory);
});
```js
function greeterFactory(greeting, name) {
return function greet() {
return `${greeting}, ${name}!`
}
}

var theGreeter = module.get("greeter");
const bind = pluto()
bind('greeting').toInstance('Hello')
bind('name').toInstance(Promise.resolve('World')) // A promise will work, too
bind('greet').toFactory(greeterFactory)

expect(theGreeter()).toBe("Hello, world!");
bind.get('greet').then((greet) => {
t.is(greet(), 'Hello, World!')
})
```

Injected objects are singletons
-------------------------------
**Author's note**: _Factory functions a super useful. I find that I use them more than any other type of binding._

Note that a factory function or constructor function is only called once. Each call to `get(...)` will return the
same instance.
### Eager Bootstrapping

Remember that singletons are only singletons within a single module, though. Different module instances -- for instance,
created for separate test methods -- will each have their own singleton instance.
By default, Pluto will only create your objects lazily. That is, factory and constructor functions will only get called when you ask for them with `.get(...)`.

Lazy vs. Eager Loading
----------------------
You may instead want them to be eagerly invoked to bootstrap your project. For instance, you may have factory functions which set up Express routes or which perform other application setup.

By default, Pluto will only create your objects lazily. That is, factory and constructor functions will only get called
when you ask for them with `module.get(...)`.
Invoke `.eagerlyLoadAll()` after creating your bindings to eagerly bootstrap your application. The result is a promise which resolves to a `Map` holding all bindings by name, fully resolved and injected.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be "Invoke .bootstrap()", based on the example below?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

D'oh! Good catch! I'll fix that this evening.

Keep 'em coming!


You may instead want them to be eagerly invoked to bootstrap your project. For instance, you may have factory functions
which set up Express routes or which perform other application setup.
```js
function greeterFactory(greeting, name) {
return function greet() {
return `${greeting}, ${name}!`
}
}

Invoke `module.eagerlyLoadAll()` after creating your module to eagerly bootstrap your application.
const bind = pluto()
bind('greeting').toInstance('Hello')
bind('name').toInstance(Promise.resolve('World')) // A promise will work, too
bind('greet').toFactory(greeterFactory)

bind.bootstrap().then(app => {
const greet = app.get('greet') // Note: it's synchronous. Everything is ready.
t.is(greet(), 'Hello, World!')
})
```
var Constructor = jasmine.createSpy('test Constructor function');
var factory = jasmine.createSpy('test factory function');

var instance = pluto.createModule(function (bind) {
bind('Constructor').toConstructor(Constructor);
bind('factory').toFactory(factory);
});
### Injected Objects are Singletons

instance.eagerlyLoadAll();
Note that a factory function or constructor function is only called once. Each call to `get(...)` will return the same instance.

expect(Constructor).toHaveBeenCalled();
expect(factory).toHaveBeenCalled();
```
Remember that singletons are only singletons within a single binder, though. Different binders -- for instance, created for separate test methods -- will each have their own singleton instance.
63 changes: 42 additions & 21 deletions lib/examplesSpec.js
Expand Up @@ -6,45 +6,66 @@ const pluto = require('./pluto')

test('bind to instance', function* (t) {
const anInstance = {} // can be any JavaScript object
const module = pluto.createModule(function (bind) {
bind('myInstance').toInstance(anInstance)
})
const bind = pluto()
bind('myInstance').toInstance(anInstance)

t.is(module.get('myInstance'), anInstance)
// bind.get will return a Promise, since we may have asynchronous resolution to do
bind.get('myInstance').then((myInstance) => {
t.is(myInstance, anInstance)
})
})

test('bind to constructor', function* (t) {
function Greeter(greeting) {
function Greeter(greeting, name) {
this.greeting = greeting
this.name = name
}

Greeter.prototype.greet = function () {
return this.greeting
return `${this.greeting}, ${this.name}!`
}

const module = pluto.createModule(function (bind) {
bind('greeting').toInstance('Hello, world!')
bind('greeter').toConstructor(Greeter)
})

const theGreeter = module.get('greeter')
const bind = pluto()
bind('greeting').toInstance('Hello')
bind('name').toInstance(Promise.resolve('World')) // A promise will work, too
bind('greeter').toConstructor(Greeter)

t.is(theGreeter.greet(), 'Hello, world!')
bind.get('greeter').then((myGreeter) => {
t.is(myGreeter.greet(), 'Hello, World!')
})
})

test('bind to factory function', function* (t) {
function greeterFactory(greeting) {
return function () {
return greeting
function greeterFactory(greeting, name) {
return function greet() {
return `${greeting}, ${name}!`
}
}

const module = pluto.createModule(function (bind) {
bind('greeting').toInstance('Hello, world!')
bind('greeter').toFactory(greeterFactory)
const bind = pluto()
bind('greeting').toInstance('Hello')
bind('name').toInstance(Promise.resolve('World')) // A promise will work, too
bind('greet').toFactory(greeterFactory)

bind.get('greet').then((greet) => {
t.is(greet(), 'Hello, World!')
})
})

const theGreeter = module.get('greeter')
test('bootstrapping', function* (t) {
function greeterFactory(greeting, name) {
return function greet() {
return `${greeting}, ${name}!`
}
}

t.is(theGreeter(), 'Hello, world!')
const bind = pluto()
bind('greeting').toInstance('Hello')
bind('name').toInstance(Promise.resolve('World'))
bind('greet').toFactory(greeterFactory)

bind.bootstrap().then(app => {
const greet = app.get('greet') // Note: it's synchronous. Everything is ready.
t.is(greet(), 'Hello, World!')
})
})