A Custom Transformer for Typescript that enables compile-time Dependency Injection
Description
This is a CustomTransformer
for Typescript that enables you to use the DI library.
This has been implemented as a TypeScript Custom Transformer in order to be so low-level that it can be used as the underlying implementation in anything you want, whether it be directly with Typescript's Compiler APIs, Webpack loaders, Rollup plugins, or something else.
Features
- Really lightweight
- Really fast
- Low-level implementation that can be used as the foundation for other tools such as Loaders, Plugins, and others.
- It doesn't ask you to reflect metadata or to annotate your classes with decorators. "It just works".
Backers
Patreon
Table of Contents
Install
npm
$ npm install @wessberg/di-compiler
Yarn
$ yarn add @wessberg/di-compiler
pnpm
$ pnpm add @wessberg/di-compiler
Peer Dependencies
@wessberg/di-compiler
depends on typescript
, so you need to manually install this as well.
Usage
Since this is a Custom Transformer, it can be used practically anywhere you use TypeScript.
The most obvious place would be to use it directly with Typescript's compiler APIs:
Usage with TypeScript's Compiler APIs
There's several ways to do this, but here's a simple example:
import {createProgram, getDefaultCompilerOptions, createCompilerHost} from "typescript";
import {di} from "@wessberg/di-compiler";
const compilerOptions = getDefaultCompilerOptions();
const compilerHost = createCompilerHost(compilerOptions);
const program = createProgram(
["my-file-1.ts", "my-file-2.ts"],
compilerOptions,
compilerHost
);
program.emit(undefined, undefined, undefined, undefined, di({program}));
Usage with ts-node
One of the simplest ways to use DI-compiler is with ts-node
:
node -r @wessberg/di-compiler/register
You can also do it programmatically. Here's an example using CommonJS:
const {di} = require("@wessberg/rollup-plugin-ts");
require("ts-node").register({
transformers: program => di({program})
});
import {createProgram, getDefaultCompilerOptions, createCompilerHost} from "typescript";
import {di} from "@wessberg/di-compiler";
const compilerOptions = getDefaultCompilerOptions();
const compilerHost = createCompilerHost(compilerOptions);
const program = createProgram(
["my-file-1.ts", "my-file-2.ts"],
compilerOptions,
compilerHost
);
program.emit(undefined, undefined, undefined, undefined, di({program}));
Usage with Rollup
There are two popular TypeScript plugins for Rollup that support Custom Transformers:
Usage with rollup-plugin-ts
import ts from "@wessberg/rollup-plugin-ts";
import {di} from "@wessberg/di-compiler";
export default {
input: "...",
output: [
],
plugins: [
ts({
transformers: [di]
})
]
};
Usage with rollup-plugin-typescript2
import ts from "rollup-plugin-typescript2";
import {di} from "@wessberg/di-compiler";
export default {
input: "...",
output: [
],
plugins: [
ts({
transformers: [service => di({program: service.getProgram()})]
})
]
};
Usage with Webpack
There are two popular TypeScript loaders for Webpack that support Custom Transformers:
Usage with awesome-typescript-loader
import {di} from "@wessberg/di-compiler";
const config = {
module: {
rules: [
{
test: /(\.mjs)|(\.[jt]sx?)$/,
loader: "awesome-typescript-loader",
options: {
getCustomTransformers: program => di({program})
}
}
]
}
};
Usage with ts-loader
import {di} from "@wessberg/di";
const config = {
module: {
rules: [
{
test: /(\.mjs)|(\.[jt]sx?)$/,
loader: "ts-loader",
options: {
getCustomTransformers: program => di({program})
}
}
]
}
};
Usage with ava
You can also use DI-compiler with the ava
test runner
with the require
property in the ava
configuration:
{
// Other options...
extensions: ["ts"],
require: ["@wessberg/di-compiler/register"]
}
Options
You can provide options to the di
Custom Transformer to configure its behavior:
Option | Description |
---|
program | A full TypeScript program (required). |
typescript (optional) | If given, the TypeScript version to use internally for all operations. |
Contributing
Do you want to contribute? Awesome! Please follow these recommendations.
Maintainers
FAQ
How does it work, exactly?
First, classes that are discovered as part of your Typescript program/bundle will be parsed for their constructor argument types and positions.
Then, instances of the DIContainer will be discovered and their expressions will be upgraded.
For example, an expression such as:
import {DIContainer} from "@wessberg/di";
import {MyInterface} from "./my-interface";
import {MyImplementation} from "./my-implementation";
const container = new DIContainer();
container.registerSingleton<MyInterface, MyImplementation>();
Will be compiled into:
container.registerSingleton(undefined, {
identifier: `MyInterface`,
implementation: MyImplementation
});
License
MIT © Frederik Wessberg (@FredWessberg) (Website)