Lightweight and flexible dependency injection library for JavaScript and TypeScript, w/wo ECMAScript decorators.
# npm
npm install di-wise
# Yarn
yarn add di-wise
# pnpm
pnpm add di-wise
Zero dependencies:
reflect-metadata
experimentalDecorators
requiredModern decorator implementation:
Full control over registration and caching:
Context-based DI system inspired by Angular:
@Injectable()
, @Scoped()
, @AutoRegister()
on classes to define providers@Inject()
, @InjectAll()
on class fields to inject dependenciesinject()
, injectAll()
with full type inference ✨Usage of decorators is optional:
import {Container, Inject, inject, Injectable, Scope, Scoped, Type} from "di-wise";
import {Wand} from "./weapons";
interface Spell {
cast(): void;
}
const Spell = Type<Spell>("Spell");
@Scoped(Scope.Container)
@Injectable(Spell)
class Fireball implements Spell {
cast() {
console.log("🔥");
}
}
class Wizard {
@Inject(Wand)
wand!: Wand;
// is equivalent to:
wand = inject(Wand);
constructor(spell = inject(Spell)) {
// inject() can be used anywhere during construction
this.wand.store(spell);
}
}
const container = new Container();
container.register(Fireball);
// is equivalent to:
[Fireball, Spell].forEach((token) => {
container.register(
token,
{useClass: Fireball},
{scope: Scope.Container},
);
});
const wizard = container.resolve(Wizard);
wizard.wand.activate(); // => 🔥
Flexible token-based injection:
Type.Null
and Type.Undefined
for optional dependenciesExample:
import {inject, Type} from "di-wise";
import {Wand} from "./weapons";
class Wizard {
wand = inject(Wand, Type.Null);
// ^? (property) Wizard.wand: Wand | null
}
Various injection scopes:
Inherited
(default), Transient
, Resolution
, and Container
Example:
import {Container, Scope} from "di-wise";
export const singletons = new Container({
defaultScope: Scope.Container,
autoRegister: true,
});
Inherited
will be resolved as Transient
for a top-level dependent.
Resolution
is similar to Transient
, but the same instance will be reused during a single resolution tree.
Automatic circular dependency resolution with @Inject()
or inject.by()
:
import {Container, Inject, inject} from "di-wise";
class Wand {
owner = inject(Wizard);
}
class Wizard {
@Inject(Wand)
wand!: Wand;
// is equivalent to:
wand = inject.by(this, Wand);
}
const container = new Container();
const wizard = container.resolve(Wizard);
expect(wizard.wand.owner).toBe(wizard);
Multiple provider types:
ClassProvider
, FactoryProvider
, ValueProvider
Build()
and Value()
for registering one-off providersExample:
import {Build, Container, inject, Value} from "di-wise";
import {Cloak} from "./equipments";
import {Wand} from "./weapons";
class Wizard {
equipment = inject(
Cloak,
// fallback to a default value
Value({
activate() {
console.log("👻");
},
}),
);
wand: Wand;
constructor(wand: Wand) {
this.wand = wand;
}
}
const container = new Container();
const wizard = container.resolve(
Build(() => {
// inject() can be used in factory functions
const wand = inject(Wand);
return new Wizard(wand);
}),
);
wizard.equipment.activate(); // => 👻
🏗️ WIP (PR welcome)
See API documentation.
MIT License @ 2024-Present Xuanbo Cheng