wessberg / di-compiler Goto Github PK
View Code? Open in Web Editor NEWA Custom Transformer for Typescript that enables compile-time Dependency Injection
License: MIT License
A Custom Transformer for Typescript that enables compile-time Dependency Injection
License: MIT License
Hi, this library looks like a dream come true to me, I hate decorator-based DI libraries with a passion and I've wanted to get around to writing a compile-time DI implementation for literally years (in fact I tried, several times, but always fell short). So first of all, thank you from the bottom of my heart for writing and releasing this library!
I'm using MikroORM in most of my backend NodeJS apps. It's a pretty neat ORM library, I like it a lot. But its initialisation is asynchronous - and so if I want to register it as a service in a DIC, I need to either initialise it first and then register the resolved instance (which means the ORM is always initialised, even when it's not needed), or I have to type the service as a promise for the correct type. But then I want to inject this service into other services. Either I could inject the typed promise (which the dependent services cannot await
in their constructor, so they would have to store a reference to the promise instead of the actual resolved service, and await that promise each time they want to access the dependency), or I have to wrap all the dependent services in a promise as well and write an async factory function for each of those services, which would do something like return new DependentService(await container.get('async-service'))
. This is, in fact, what I'm doing right now, because it feels like the "cleanest" solution in terms of separating the actual services from the DI implementation or the fact that some services are initialised asynchronously.
My current DI implementation is basically a lot of handwritten factories which mostly do exactly what your DI library does at runtime, except it allows for services to be async. I'm not sure how the support for generics currently works in this library - from the linked issue it seems I can use generics, but maybe the generic part of the registration is ignored..? (based on the last comment from @cmidgley, which seems to suggest that stripping the type parameter is the intended behaviour?). So if this library does support generic services, then that would probably also mean promises for services (since a promise is just a generic from this point of view). Is that a correct assumption?
And, well, further on the topic, I think that one amazing feature this library could include would be support for async services out of the box - that is, I'd like to do something like this:
container.registerSingleton<Promise<ServiceOne>>(async () => createServiceOneSomehow());
container.registerSingleton<Promise<ServiceTwo>>();
class ServiceTwo {
constructor(private readonly one: ServiceOne) {}
}
// the transformer can now detect that "ServiceOne" is wrapped in a promise, so it should be possible to generate factory code for "ServiceTwo" which awaits the container.get() call in order to autowire the unwrapped ServiceOne instance instead of the promise
Maybe the container.register*
methods might not even need the type parameter wrapped in a promise - the transformer should be able to detect that the service is async based on either the passed-in factory function, or on the fact that the service depends on other services which are async. When one wants to access such a service, the transformer could detect that container.get<ServiceTwo>()
is attempting to access an async service without specifying the Promise
wrapper and throw an error (since this would break at runtime)..
I'm not sure if all of this is perhaps already supported - I can't find it anywhere in the docs or issues. If it is, then that's amazing! And if it's not: is this something you'd consider adding to the library? I would very much like to offer my help in case you would.
Thanks in any case!
is it possible to use with babel/preset-typescript ?
Hello @wessberg
Thanks for the awesome package ๐
I'm probably missing something trivial but I can't get it to work ๐
sample ts file
import { DIContainer } from "@wessberg/di";
import { LoggerServiceContract } from "../domain/contracts/services/logger.service-contract";
import { LoggerService } from "../services/logger.service";
export function registerServices(injector: DIContainer): void {
injector.registerSingleton<LoggerServiceContract, LoggerService>();
}
package usage
import { di } from "@wessberg/di-compiler";
import {
createProgram,
getDefaultCompilerOptions,
createCompilerHost,
ImportsNotUsedAsValues,
} from "typescript";
const compilerOptions = getDefaultCompilerOptions();
compilerOptions.outDir = 'lib';
compilerOptions.importsNotUsedAsValues = ImportsNotUsedAsValues.Preserve;
const compilerHost = createCompilerHost(compilerOptions);
const program = createProgram(
["./src/index.ts"],
compilerOptions,
compilerHost
);
program.emit(undefined, undefined, undefined, undefined, di({ program }));
result
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerServices = void 0;
require("@wessberg/di");
require("../domain/contracts/services/logger.service-contract");
require("../services/logger.service");
function registerServices(injector) {
/* HERE */ injector.registerSingleton(undefined, { identifier: "LoggerServiceContract", implementation: LoggerService });
}
exports.registerServices = registerServices;
when I run the app I get the following error during this call injector.registerSingleton
ReferenceError: LoggerService is not defined
Do you have any hints on what am I doing wrong?
Thanks
When typescript transpiles a constructor-less derived class, it compiles it into:
// super class
class A {
constructor (fooService)
}
// derived class
class B {
constructor () {
super(...arguments);
}
}
This breaks.
No errors while compiling.
But when run:
ReferenceError: DIContainer could not get service: No arguments were given!
How i can use DI-compiler
with Vite.js
?
Hello,
First I want to say that I am very glad you made this compile time DI library for TS! It does not have many features, it just contains what is necessary, that is perfect :)
I am trying to make the di-compiler work with Parcel (I use Parcel to set up "easily" a React project with di-compiler after that). Almost everything is working (it correctly generates the constructors data), however for some reason the bindings are not generated.
Everything works fine if I write:
container.registerSingleton(undefined, {identifier:'ServiceA', implementation:ServiceA});
container.registerSingleton(undefined, {identifier:'ServiceB', implementation:ServiceB});
const serviceB = container.get<ServiceB>({identifier: 'ServiceB'});
However, if I try to use the standard syntax:
container.registerSingleton<ServiceA>();
container.registerSingleton<ServiceB>();
const serviceB = container.get<ServiceB>();
I get a Uncaught ReferenceError: DIContainer could not get service: No arguments were given!
for the registerSingleton
calls and a Uncaught ReferenceError: DIContainer could not get service: No options was given!
for the get
call.
Do you have any idea of what is wrong or what is missing?
To make the integration work, I use parcel-plugin-ttypescript
that enables me to specify some TS transformers in the tsconfig.json
file. After that, I needed to make a small adapter between this parcel plugin and di-compiler:
import { di } from "@wessberg/di-compiler";
import * as ts from 'typescript';
export default function(program: ts.Program) {
return di({ program });
}
I created a repository with a minimal project to reproduce the issue: https://github.com/amanteaux/poc-parcel-di-compiler
The two ways of writing the bindings are in src/index.ts
.
Thank you for any help/hints to make the integration works!
Cheers
I have been trying to get the DI transformer working with ts-patch. Have raised an issue in that repository also
nonara/ts-patch#120
After quite a few hours trying to work out why its not working I am at a loss. I think I may have to create a new transformer from the examples in the ts-patch project. Thought I would check in there before I go down this path in case the solution is something that I am missing.
Hello, this project seems very interesting as the .NET reflection pattern is something I look for, tho it seems not easily doable to add this compiler as a build step process of an esbuild build process.
The readme says that it's possible, but I only see typescript programms solutions, I would gladly use this library and recommend it if you could give some examples on how to integrate it with esbuild
In a somewhat recent release of TypeScript (I believe in some version around 5.2 but not sure) the rules for module/moduleResolution were tightened up. This causes DI-compiler
to fail a build (pnpm build
) with an error that module
must be nodenext
when moduleResolution
is nodenext
when using a fresh install of DI-compiler
(latest TypeScript). The root cause is in @wessberg/ts-config
where the following is set:
"moduleResolution": "nodenext",
"module": "esnext",
A workaround that resolves this build issue for just DI-compiler
(though perhaps this is best resolved with some change to @wessberg/di-config
) is to edit tsconfig.build.json
and add moduleResolution: "node"
to the compilerOptions
:
{
"extends": "./tsconfig.json",
"include": [
"src/**/*.*"
],
"compilerOptions": {
"declaration": true,
"declarationMap": false,
"moduleResolution": "node"
}
}
Asking this since the @rollup/plugin-typescript
plugin supports transformers
option for typescript.
I tried but getting this error:
TypeError: Cannot read property 'getTypeChecker' of undefined
Hi. I followed you docs, but anyway keep getting this exception: ReferenceError: 1 argument required, but only 0 present. Note: You must use DI-Compiler (https://github.com/wessberg/di-compiler) for this library to work correctly. Please consult the readme for instructions on how to install and configure it for your project.
Here is my little test project setup:
package.json
{
"name": "di-test-ts",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@wessberg/di": "^2.1.0",
"@wessberg/di-compiler": "^3.3.0",
"tsx": "^3.12.10",
"typescript": "^5.2.2"
},
"devDependencies": {
"@types/node": "^20.6.0"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"preserveValueImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
di.ts
import { DIContainer } from "@wessberg/di";
export interface IMyService {
testMyService: (str: string) => void;
}
export class MyService implements IMyService {
testMyService(str: string) {
console.log("testMyService", str);
}
}
export const container = new DIContainer();
container.registerSingleton<IMyService, MyService>();
container.get<IMyService>().testMyService("from di.ts");
index.ts
import { container, type IMyService } from "./di";
container.get<IMyService>().testMyService("from index.ts");
Note
If i run node --loader @wessberg/di-compiler/loader --loader tsx di.ts
- everything works as expected
But, if i try to use container from another file, i get that exception:
node --loader @wessberg/di-compiler/loader --loader tsx index.ts
Is this issue on your side or something wrong with my configs? Ty for help!
p.s. I am sure this tool is absolutely underrated, and i think this is partly due to problems with setting it up to be working , to play with it and make some tests.
p.s.2. I hope it is still maintained)
I am trying to use the proposed approach from the docs with direct usage of transform(code, filename, options)
.
Unfortunately, this does not work when running TSX files as transform()
is hardcoding the script type to ScriptKind.TS
.
From a quick test it would be sufficient to simply remove this parameter, TS seems to figure it out implicitly. Alternatively, using a factory method like used in the tests would be possible as well I guess.
Is there any chance to get TSX for the transform()
method? It would be super helpful as it's a tremendous performance improvement compared to using the rollup TS plugin.
Thanks for any response and the awesome work on this piece of code!
Thank you for library its awesome. But i ran into issue cant configure with jest. Can you please provide example how to setup with jest ts-jest
For example
interface MyInterface {
load(a: generic){}
}
container.registerSingleton<MyInterface, MyClass>();
container.registerSingleton<MyInterface, MyClass>();
I cloned one of "used by" repo https://github.com/nialvi/pet-project-template, commented this line https://github.com/nialvi/pet-project-template/blob/main/src/random/composition.ts#L5 and had source code compiling (the error is run-time), however I wanted to see compilation failure. How can we achieve something like that?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.