BabyIoC
Infantile IoC decorator with almost no features.
BabyIoC is the smallest IoC container you'll ever see (under 50 lines of code!). It's also got the fewest toys - it's only targeted for use by GameStartr.
Key tenants:
- All
@components are members of the container class instance. - Components are stored as lazily evaluated getters: circular dependencies are fine!
- Use TypeScript.
Usage
Each @component is a member of your container class. Declare your components with their classes to have them automagically created as members of your class.
import { component } from "babyioc";
class DependencyA { }
class Container {
@component(DependencyA)
public readonly dependencyA: DependencyA;
}
const { dependencyA } = new Container();
Components receive the instance of the container as a single constructor parameter. They can use it to reference other components.
import { component } from "babyioc";
class DependencyA { }
class DependencyB {
public constructor(
public readonly instance: Container,
) { }
}
class Container {
@component(DependencyA)
private readonly dependencyA: DependencyA;
@component(DependencyB)
public readonly dependencyB: DependencyB;
}
const { dependencyB } = new Container();
const { dependencyA } = depdendencyB.instance;
Factories
Your components don't have to be direct classes with dependencies. Pass functions that take in your container as an argument. The values returned by those functions are used as the component value.
Use factory instead of component for these.
import { factory } from "babyioc";
class DependencyA {
public constructor(
public readonly member: string,
) { }
}
const createDependencyA = () => new DependencyA("value");
class Container {
@factory(createDependencyA)
public readonly dependencyA: DependencyA;
}
const { dependencyA } = new Container();
These factory functions have access to all the values on the container, including computed getters.
import { factory } from "babyioc";
class DependencyA {
public constructor(
public readonly memberA: string,
) { }
}
class DependencyB {
public constructor(
public readonly referenceA: DependencyA,
public readonly valueC: string,
) { }
}
const createDependencyA = () => new DependencyA("valueA");
const createDependencyB = (instance: Container) => new DependencyB(dependencyA, container.valueC);
class Container {
@factory(createDependencyA)
public readonly dependencyA: DependencyA;
@factory(createDependencyB)
public readonly dependencyB: DependencyB;
public readonly valueC = "valueC";
}
const { dependencyA, dependencyB } = new Container();
...and that's about it!
Technical Details
Marking a member with @component or @factory creates a double-layer getter on the class prototype.
The prototype will have a getter defined that writes a getter on the calling object.
Both getters return a new instance of the component.
For example, with this component:
import { component } from "babyioc";
class Dependency { }
class Container {
@component(Dependency)
public readonly myDependency: Dependency;
}
Container.prototype has a getter defined on "myDependency" that creates a new Dependency(this) and writes a getter on the calling scope's "myDependency" to return it.
In practical use, that means the first getter will stay on Container.prototype, and the calling scope that receives the second getter will generally be an instance of the Container class.
See index.ts.
Development
After forking the repo from GitHub:
git clone https://github.com/<your-name-here>/BabyIoC
cd BabyIoC
npm install
npm run setup
npm run verify
npm run setupcreates a few auto-generated setup files locally.npm run verifybuilds, lints, and runs tests.
Building
npm run watch
Source files are written under src/ in TypeScript and compile in-place to JavaScript files.
npm run watch will directly run the TypeScript compiler on source files in watch mode.
Use it in the background while developing to keep the compiled files up-to-date.
Running Tests
npm run test
Tests are written in Mocha and Chai.
Their files are written using alongside source files under src/ and named *.test.ts?.
Whenever you add, remove, or rename a *.test.t* file under src/, watch will re-run npm run test:setup to regenerate the list of static test files in test/index.html.
You can open that file in a browser to debug through the tests.
Philosophy
Is BabyIoC an IoC framework?
If you consider the Container classes from the samples to be equivalent to IoC containers à la Inversify, then sure.
The main difference is that components are encouraged to have knowledge of the full application type instead of just their dependencies.
Is BabyIoC a good IoC framework?
Lol, no.
Application components generally shouldn't have knowledge of the full application. BabyIoC also has almost no features. You should probably use something standard like Inversify.
Does BabyIoC violate SOLID principles?
Debatably no.
There's nothing inherently non-SOLID in components being passed the root IoC container. Such a thing happens behind the scenes in normal IoC frameworks; BabyIoC components just don't have the layer of indirection given by declaring only required parameters. Just as BabyIoC components can access anything they want, so too can traditional classes by taking in an obscene number of dependencies.