Skip to content

@tdi2/vite-plugin-di Overview

The Vite plugin that transforms your TDI2 code at build time, providing automatic interface resolution, component transformation, and development debugging tools.

🎯 Plugin Features

  • Interface Resolution - Automatic service discovery from TypeScript interfaces
  • Component Transformation - Convert service props to useService hooks
  • Hot Reload - Development-friendly automatic retransformation
  • Debug Tools - Development endpoints and verbose logging

🔧 Technical Background: See ADR-001: AST Transformation and ADR-004: Build-Time DI for the design rationale.


Terminal window
# npm
npm install @tdi2/vite-plugin-di @tdi2/di-core
# bun
bun add @tdi2/vite-plugin-di @tdi2/di-core
# pnpm
pnpm add @tdi2/vite-plugin-di @tdi2/di-core

vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { diEnhancedPlugin } from '@tdi2/vite-plugin-di';
export default defineConfig({
plugins: [
diEnhancedPlugin(), // Zero config setup!
react(),
],
});
vite.config.ts
export default defineConfig({
plugins: [
diEnhancedPlugin({
// Enable all features for development
enableInterfaceResolution: true,
enableFunctionalDI: true,
verbose: true,
generateDebugFiles: true,
}),
react(),
],
});
vite.config.ts
export default defineConfig({
plugins: [
diEnhancedPlugin({
// Optimized for production
enableInterfaceResolution: true,
enableFunctionalDI: true,
verbose: false,
generateDebugFiles: false,
reuseExistingConfig: true,
}),
react(),
],
});

The plugin scans your codebase and automatically maps interfaces to implementations:

// Your code
export interface ProductServiceInterface {
loadProducts(): Promise<void>;
}
@Service()
export class ProductService implements ProductServiceInterface {
// Implementation...
}

Generated mapping:

// .tdi2/di-config.ts (auto-generated)
export const DI_CONFIG = {
services: [
{
interface: 'ProductServiceInterface',
implementation: 'ProductService',
token: 'ProductService'
}
]
};

Components with service props are automatically transformed:

Your code:

function ProductList({ productService }: {
productService: Inject<ProductServiceInterface>;
}) {
const { products, loading } = productService.state;
return <div>{/* JSX */}</div>;
}

Generated code:

function ProductList() {
// TDI2-GENERATED: Service injection
const productService = useService<ProductServiceInterface>('ProductService');
const productServiceSnap = useSnapshot(productService.state);
const { products, loading } = productServiceSnap;
return <div>{/* Your JSX unchanged */}</div>;
}

interface DIPluginOptions {
/** Source directory to scan */
srcDir?: string; // default: './src'
/** Output directory for generated files */
outputDir?: string; // default: './src/.tdi2'
/** Enable verbose logging */
verbose?: boolean; // default: false
/** Enable hot reload during development */
watch?: boolean; // default: true
/** Enable functional component transformation */
enableFunctionalDI?: boolean; // default: true
/** Enable interface-to-implementation resolution */
enableInterfaceResolution?: boolean; // default: true
}
interface DIPluginOptions {
/** Generate debug files for inspection */
generateDebugFiles?: boolean; // default: false
/** Reuse existing configurations */
reuseExistingConfig?: boolean; // default: true
/** Clean old configuration files */
cleanOldConfigs?: boolean; // default: true
/** Number of configurations to keep */
keepConfigCount?: number; // default: 3
}

During development, access these URLs for debugging:

Terminal window
# General debug information
http://localhost:5173/_di_debug
# Interface mappings
http://localhost:5173/_di_interfaces
# Configuration management
http://localhost:5173/_di_configs
# Performance statistics
http://localhost:5173/_di_performance
Terminal window
# Via API
curl -X POST http://localhost:5173/_di_regenerate
# Via CLI (if available)
npm run di:regenerate

Enable debug file generation to inspect transformations:

diEnhancedPlugin({
generateDebugFiles: true,
verbose: true
})

This creates files in .tdi2/debug/ showing:

  • Original component code
  • Transformed component code
  • Interface mappings
  • Service registrations

Use predefined configurations for common scenarios:

import { diEnhancedPlugin, createDIPluginPresets } from '@tdi2/vite-plugin-di';
const presets = createDIPluginPresets();
export default defineConfig({
plugins: [
// Choose appropriate preset
process.env.NODE_ENV === 'development'
? diEnhancedPlugin(presets.development.options)
: diEnhancedPlugin(presets.production.options),
react(),
],
});

Available presets:

  • development - Verbose logging, hot reload, debug files
  • production - Minimal logging, optimized builds
  • testing - Fast rebuilds, no debug output
  • debugging - Maximum verbosity for troubleshooting

// No manual token mapping needed!
export interface CartServiceInterface {
addItem(product: Product): void;
}
@Service()
export class CartService implements CartServiceInterface {
constructor(
@Inject() private storage: StorageService, // Auto-resolved
@Inject() private analytics?: AnalyticsService // Optional
) {}
}
// Primary implementation
@Service()
@Primary()
export class DatabaseUserRepository implements UserRepositoryInterface {}
// Alternative implementation
@Service()
@Qualifier('cache')
export class CacheUserRepository implements UserRepositoryInterface {}
@Service()
export class UserService {
constructor(
@Inject() private repo: UserRepositoryInterface, // → DatabaseUserRepository
@Inject() @Qualifier('cache') private cache: UserRepositoryInterface // → CacheUserRepository
) {}
}
export interface Repository<T> {
findById(id: string): Promise<T>;
save(entity: T): Promise<void>;
}
@Service()
export class ProductRepository implements Repository<Product> {
// Implementation...
}
// Automatic resolution with generics!
@Service()
export class ProductService {
constructor(@Inject() private repo: Repository<Product>) {}
// ↑ Automatically resolves to ProductRepository
}

  • Zero Runtime Overhead - All DI resolution happens at build time
  • Tree Shaking - Only used services included in bundles
  • Code Generation - No runtime reflection or metadata
  • Parallel Processing - Concurrent file processing for large codebases
  • Intelligent Caching - Avoid unnecessary regeneration
  • Hot Reload - Automatic retransformation on changes
  • Incremental Updates - Only rebuild changed components

Terminal window
# Check interface mappings
curl http://localhost:5173/_di_interfaces
# Force regeneration
curl -X POST http://localhost:5173/_di_regenerate
// Ensure proper implementation
@Service()
export class MyService implements MyServiceInterface {
// Must implement interface methods
}
diEnhancedPlugin({
watch: true, // Enable file watching
verbose: true // See what's happening
})

Enable maximum debugging for troubleshooting:

diEnhancedPlugin({
verbose: true,
generateDebugFiles: true,
// Force fresh start
reuseExistingConfig: false,
cleanOldConfigs: true
})

tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": false,
"target": "ES2020"
}
}
vitest.config.ts
export default defineConfig({
plugins: [
diEnhancedPlugin(presets.testing.options),
react(),
],
test: {
environment: 'jsdom',
},
});
apps/web/vite.config.ts
export default defineConfig({
plugins: [
diEnhancedPlugin({
srcDir: './src',
outputDir: './src/.tdi2',
// Scan shared packages too
advanced: {
fileExtensions: ['.ts', '.tsx'],
searchPaths: ['../../packages/*/src']
}
}),
react(),
],
});

🎯 Key Takeaway

The Vite plugin handles all the complexity of dependency injection transformation. Your code stays clean and readable while the plugin generates optimized production code.