ADR-001: AST Transformation vs Runtime Reflection
ADR-001: AST Transformation vs Runtime Reflection
Section titled “ADR-001: AST Transformation vs Runtime Reflection”Status: Active
Date: 2024
Context
Section titled “Context”TDI2 needed a way to implement dependency injection in TypeScript that would:
- Work with TypeScript interfaces (which erase at runtime)
- Provide zero runtime overhead
- Maintain perfect tree-shaking compatibility
- Support both class services and functional components
Traditional DI approaches use runtime reflection or metadata, but this creates bundle size overhead and breaks tree-shaking.
Decision
Section titled “Decision”Use compile-time AST transformation with ts-morph + ts-query to transform code during the build process.
The transformation pipeline:
- ts-query finds DI patterns using CSS-like selectors
- ts-morph performs code generation and transformation
- Generated code contains no runtime DI logic
Implementation
Section titled “Implementation”// Before transformationfunction UserProfile({ userService }: { userService: Inject<UserServiceInterface> }) { return <div>{userService.state.user.name}</div>;}
// After transformationfunction UserProfile() { const userService = container.resolve<UserServiceInterface>("UserServiceInterface"); return <div>{userService.state.user.name}</div>;}Consequences
Section titled “Consequences”Benefits
Section titled “Benefits”- 90% code reduction in transformation pipeline (from 800+ lines to 50-100 lines)
- Zero runtime overhead - no DI logic in production bundles
- Perfect tree-shaking - unused services are eliminated
- Type safety preserved - full TypeScript interface support
Trade-offs
Section titled “Trade-offs”- Build-time complexity - requires Vite plugin for transformation
- Development setup - needs proper TypeScript configuration
- Debugging complexity - generated code differs from source code
Alternatives Considered
Section titled “Alternatives Considered”- Runtime reflection (like InversifyJS) - Rejected due to bundle size and tree-shaking issues
- String-based tokens - Rejected due to lack of type safety and collision risk
- Manual factory functions - Rejected due to boilerplate and maintainability
This decision enables TDI2’s core value proposition: enterprise-grade DI with React-level performance.