Dependency Injection in Vue.js thanks to DIOD library.
Documentation & Examples | DIOD on GitHub
As stated by its author on GitHub, DIOD is:
A very opinionated and lightweight (under 2kB minified and gzipped) inversion of control container and dependency injector for Node.js or browser apps. It is available for vanilla Javascript usage but its true power will be shown by building Typescript apps.
Vue DIOD is made on top of DIOD and helps avoiding SOLID principles violations in Vue 3 applications. It allows to configure dependencies at the app level and / or at component level, without children knowing about the actual implementations.
Basically, it wraps DIOD functionalities in a plugin which uses Vue's provide
method to make dependencies available to subtree components, by calling inject
with the registered abstract class as key.
It also provides its builder for bootstrapping dependencies at component level, and a composable which main purpose is helping with injection key typings.
export abstract class AbstractCounter {
increment(value: number): number;
decrement(value: number): number;
}
export class Counter implements AbstractCounter {
public increment(value: number): number {
return value + 1;
}
public decrement(value: number): number {
return value - 1;
}
}
In your main.ts
file, for example.
import('reflect-metadata');
import VueDiod from 'vue-diod';
// ...
app.use(VueDiod, {
injectables: [{ register: AbstractCounter, use: Counter }],
});
// ...
Without helper
NB: Keeping your application decoupled from Vue DIOD.
In the component's <script setup>
.
// ...
import type { InjectionKey } from 'vue';
import { inject } from 'vue';
const Key = AbstractCounter as unknown;
const counter = inject<() => AbstractCounter>(
Key as InjectionKey<AbstractCounter>
/* Optional fallback */
);
const increment = counter.increment.bind(counter);
const decrement = counter.decrement.bind(counter);
// ...
<template>
<!-- Use in your template -->
</template>
With Vue DIOD helper
// ...
import { useVueDiod } from 'vue-diod';
const { injectServiceInstance } = useVueDiod();
const counter = injectServiceInstance<AbstractCounter>(
AbstractCounter
/* Optional fallback */
);
const increment = counter.increment.bind(counter);
const decrement = counter.decrement.bind(counter);
// ...
For other examples, please check the documentation that provides an example for bootstrapping the container at component level, and demonstrate DIOD's powerful autowire
for constructor's dependencies.
Considering you already have a working Vue 3 project, you'll have
to install Vue DIOD with its dependencies:
reflect-metadata
and obviously diod
.
npm install -s vue-diod diod reflect-metadata
yarn add vue-diod diod reflect-metadata
Modify your tsconfig.json
to include the following settings:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
As stated in the Vite GitHub issues (here):
The emitDecoratorMetadata flag is intentionally not supported.
To enable this support, we have to disable esbuild and use SWC instead, thanks
to the
rollup-plugin-swc3
package.
npm install --save-dev @swc/core rollup-plugin-swc3
# OR
yarn add -D @swc/core rollup-plugin-swc3
Then, in our vite.config.js
or vite.config.ts
file, we simply have to add:
import { defineConfig } from 'vite';
import swc from 'rollup-plugin-swc3';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
// ...
plugins: [
swc({
jsc: {
parser: {
syntax: 'typescript',
dynamicImport: true,
decorators: true,
},
target: 'es2021',
transform: {
decoratorMetadata: true,
},
},
}),
vue(),
],
esbuild: false,
});
- Tests
- Improve documentation
- Complete examples in the monorepo packages