GithubHelp home page GithubHelp logo

wessberg / di-compiler Goto Github PK

View Code? Open in Web Editor NEW
77.0 4.0 7.0 1.3 MB

A Custom Transformer for Typescript that enables compile-time Dependency Injection

License: MIT License

TypeScript 98.42% JavaScript 1.53% Shell 0.05%
di dependency-injection ioc inversion service-container reflection singleton transient custom-transformer typescript

di-compiler's People

Contributors

cmidgley avatar m-shaka avatar wessberg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

di-compiler's Issues

JSX support for transform() API method

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!

Support for async service factories

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 there any special compiler options required?

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

Bindings not generated with Parcel

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

DI-compiler fails to build due to change in TypeScript and @wessberg/ts-config

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"
	}
}

Issues with derived classes transpiled by Typescript

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.

Does this work with @rollup/plugin-typescript ?

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

ttypescript deprecated and replaced with ts-patch

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.

Jest example

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

ReferenceError: 1 argument required, but only 0 present

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
image

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)

Esbuild plugin support

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.