A fast dependency injection framework and codegen engine for Dart.
Example:
import 'package:cork/cork.dart';
// "Inject" means that something can be created by an Injector.
@Inject()
class Foo {}
class CustomFooImpl implements Foo {}
// "Module" is a collection of Inject-ables, and other Modules.
// It can also provide custom factory functions.
@Module(const [Foo])
class FooModule {
// Instead of using the default factory for Foo, use this function.
@Provide(Foo)
static Foo getFoo() => new CustomFooImpl();
}
Cork is built with static analysis and tree-shaking in mind. As such, unlike package:di, all bindings are declared via Dart metadata annotations.
It is possible to run Cork with either runtime or compile-time analysis, depending on your target platform, and even mix and match (i.e. use runtime reflection for development, and compile-time for production).
Suitable for use in the Dart VM (e.g. server-side), or within Dartium for develpopment only.
import 'package:cork/dynamic.dart';
// Assume same file as above.
import 'foo.dart';
void main() {
var injector = createInjector(FooModule);
var foo = injector.get(Foo);
assert(foo.runtimeType == CustomFooImpl)
}
Disable reflection and use Cork in a production/static mode by analyzing your app and generating bindings.
NOTE: This is still experimental.
There are a few strategies:
To maintain API compatibility, it is possible to manually create an Injector
with generated bindings:
import 'package:cork/cork.dart';
import 'foo.dart';
import 'foo_generated.dart' as generated;
void main() {
var injector = new Injector(generated.bindingsForFooModule);
var foo = injector.get(Foo);
assert(foo.runtimeType == CustomFooImpl)
}
The reflective mode of Cork uses mirrors to generate Binding
objects:
abstract class Binding {
/// Provider tuple for [T].
final Provider<T> provider;
/// The type of [T].
final Type token;
}
Simply put, a Binding
is a tuple between a Provider
(factory) and a Type
(token).
The Cork binding generator can analyze an @Entrypoint
, and generate a full list of bindings:
var foo = Uri.parse('package:cork/testing/integration/spec.dart');
// The dart file generated.
var generator = new BindingGenerator(anthology);
var result = await generator.generate(uri, 'SingleModuleEntrypoint');
// result.toSource() is a formatted file that looks like the code blob below.
final bindingsForSingleModuleEntrypoint = <Binding>[
new Binding(Foo, new Provider((args) => new Foo(), const [])),
new Binding(Bar, new Provider((args) => new Bar(args[0]), const [Foo]))
];
NOTE: A transformer will be available in an upcoming version. Right now it requires manual work.
It is easier to use the dynamic Injector
, either with mirrors or with the binding generator, in most applications, because you are able to at runtime determine what to create a new instance of. However, there are performance penalities involved:
- dart2js will only be able to treeshake method bodies, at best, because you refer to every injectable class
- Factory functions are megamorphic.
For scenarios where you are able to take full advantage of a completely typed and static injector, Cork will help generate a typed injector specifically for your application. For example:
Generates a class called SingleModuleEntrypointInjector
:
var foo = Uri.parse('package:cork/testing/integration/spec.dart');
// The dart file generated.
var generator = new ClassGenerator(anthology);
var result = await generator.generate(uri, 'SingleModuleEntrypoint');
// result.toSource() is a formatted file that looks like the code blob below.
class SingleModuleEntrypointInjector implements Injector {
Foo _foo;
Bar _bar;
get(_) {
throw new UnsupportedError(
'Generated injector does not support dynamic get.');
}
Foo getFoo() {
if (_foo == null) {
_foo = new Foo();
}
return _foo;
}
Bar getBar() {
if (_bar == null) {
_bar = new Bar(getFoo());
}
return _bar;
}
}
It's possible to use this injector directly like any other class in your code:
var injector = new SingleModuleEntrypointInjector();
var foo = injector.get1();
assert(foo.runtimeType == Foo);
Ultimately, Cork will also supply source generation helpers to rewrite parts of your application to take use of the statically defined methods. Something like below is planned - it will run using mirrors in development mode.
@Entrypoint()
class Foo {
final Injector _injector;
Foo(this._injector);
Bar getBar() => _injector.get(Bar);
}
void main() {
var injector = createInjector(Foo);
injector.getFoo().getBar();
}
And in static mode will be rewritten:
class Foo {
final $GeneratedInjector _injector;
Foo(this._injector);
Bar getBar() => _injector.get2();
}
void main() {
var injector = new $GeneratedInjector();
injector.get1().getBar();
}
There are also adapters planned to use Cork in Angular 1.0 and Angular 2.0 applications.