This is surely acceptable on case-by-case basis. Static or prototype someModule
property will be more efficient, but on the other hand, this will require to restore it after mocking in tests.
On the regular basis this pattern may become cumbersome, in this case DI container may be more convenient. There are many of them in Node realm, e.g. injection-js
that was extracted from Angular DI.
In its most simple form it can be a purely singleton container that doesn't create instances by itself but stores existing values (module exports) under random tokens:
class Container extends Map {
get(key) {
if (this.has(key)) {
return super.get(key);
} else {
throw new Error('Unknown dependency token ' + String(key));
}
}
set(key, val) {
if (key == null) {
throw new Error('Nully dependency token ' + String(key));
} else if (arguments.length == 1) {
super.set(key, key);
} else {
super.set(key, val);
}
}
}
const container = new Container;
The dependencies can be registered and retrieved directly from the container:
const foo = Symbol('foo');
container.set(foo, require('foo'));
container.set('bar', require('bar'));
container.set(require('baz'));
...
const { foo } = require('./common-deps');
class Qux {
constructor() {
this.foo = container.get(foo);
...
}
}
Additionally, the injector can embrace the container:
class DI {
constructor(container) {
this.container = container;
}
new(Fn) {
if (!Array.isArray(Fn.annotation)) {
throw new Error(Fn + ' is not annotated');
}
return new Fn(...Fn.annotation.map(key => this.container.get(key)));
}
call(fn) {
if (!Array.isArray(fn.annotation)) {
throw new Error(fn + ' is not annotated');
}
return fn(...fn.annotation.map(key => this.container.get(key)));
}
}
const di = new DI(container);
And take care of DI in annotated classes and functions (on annotation, see this explanation):
class Qux {
constructor(foo, bar) {
this.foo = foo;
...
}
}
Qux.annotation = [foo, 'bar', require('baz')];
quuxFactory.annotation = [require('baz')]
function quuxFactory(baz) { ... }
const qux = di.new(Qux);
const quux = di.call(quuxFactory);