service-container.js
v0.3.4
Published
The Laravel container re-written in TypeScript
Downloads
4
Maintainers
Readme
service-container.js
The Laravel service container re-written in TypeScript
About
This project is still in its early stages and hasn't been extensively tested for real use purposes yet. It most likely is not production ready and you should be cautious using it as such. There are other more mature JavaScript / TypeScript DI projects around that are more reliable for any practical use cases and I would strongly suggest using one of them instead of this project for anything serious at this point. However, if you like the design of the Laravel service container and are curious about how well it translates to the world of JavaScript then this project will be interesting to give a go.
This project offers almost completely the same functionality as the original Laravel service container. It has been implemented with in mind the aim to be as close to the original version as possible. There are some differences, mainly due to the fact that TypeScript and PHP have key differences in their architectural design. Most noticeably, PHP has a quite extensive and mature reflection system, which TypeScript lacks (currently). In order to provide (almost exactly) the same dependency injection flexibility as the original Laravel service container, this project uses an approach which combines the analysis of decorator metadata using reflect-metadata and script parsing using meriyah.
Documentation for this project is rather minimal at this point. However, almost all tests from the rather extensive test suite of the original Laravel version have been ported. This should give a good indication of what is possible. There is also an example folder with some preliminary, basic examples.
Installation
This package is available through npm:
npm install --save service-container.js
In addition, it is necessary to install the reflect-metadata package:
npm install --save reflect-metadata
and import it as soon as possible in the entry point of your app (e.g. index.ts or app.ts).
Considerations
Below are some important things to consider when trying out this project for yourself.
Environment requirements
This project should work in both browser and node environments targeting ES5 or higher.
Dependency injection
In order to be able to inject any dependencies defined in a class constructor you need to decorate the relevant class definition like so:
import {injectable} from 'service-container.js';
@injectable()
class MyClass {
private _dependency: MyDependency;
public constructor(dependency: MyDependency) {
this._dependency = dependency;
}
}
This will cause reflect-metadata to emit its type metadata and also will trigger all the necessary class definition parsing behind the scenes.
Binding to an interface
Since TypeScript interfaces are compiled away and do not exist anymore at runtime, an alternative approach was needed to facilitate binding a certain implementation to a given interface / contract. The approach taken is largely borrowed from Aurelia, as this was found to be a quite elegant and minimally invasive workaround. See the test suite and examples for more details.
Dependency injection and ordinary functions
The original Laravel container does allow for this by passing a closure as the first argument to container.call()
. However, since this project relies on reflect-metada for type deduction and reflect-metada only emits metadata for decorated class definitions, dependency injection only works for class methods (i.e. not for ordinary functions).
Discerning ordinary functions from class definitions
This is a little bit tricky, because in JavaScript there really is no difference between the two at runtime. One option would be to use parsing to determine the real nature of a target. At the moment a different, more efficient, but less robust method is used. This partly relies on the convention that the first character of a class name is always in uppercase.
Short questions & answers
- Is this project ES5 compatible?
Yes. Both ESNext as well as ES5 are supported.
- Can you bind an implementation to an interface?
Yes, this is possible, albeit using a slight workaround (see the examples).
- Do I have to use TypeScript?
Yes, at the moment this project only works with TypeScript.
- Why is reflect-metadata not just imported in the source for service-container itself?
Because reflect-metadata affects things at a global scope and therefore, it should only be imported once and only once. Hence, importing reflect-metadata internally would be fine if service-container.js is your only dependency that requires its use. However, if you use service-container.js in conjunction with another package / library that requires reflect-metadata you would run into trouble.
Examples
To run the examples you'll have to perform the following steps:
- Clone this repository
- Build the project: from the root directory execute
npm run build
- Build the examples: from the /example folder execute
npm run build
The built examples now are available in the /example/dist folder. Note that running the built examples doesn't really do much. It is more meant to illustrate how you would go about building your own projects on top of service-container.js. The source code for the examples should give you an idea about some use cases for service-container.js.