Dependency Injection
Pencel uses a lightweight DI container for core compiler components only. Not for plugins or application code.
Why DI?
Section titled “Why DI?”- Singleton pattern – One instance per compilation
- Automatic wiring – Services find dependencies without manual setup
- Testability – Register mock instances for testing
- Circular dependency detection – Catches configuration errors early
All core services use inject() to retrieve dependencies:
import { inject } from "../core/container.ts";import { Config } from "../config.ts";
export class MyService { readonly #config = inject(Config); readonly #plugins = inject(Plugins);}Key: Private readonly fields, no constructor parameters, no decorators.
inject<T>(ctor)– Get or create singleton instanceinjectLazy<T>(ctor)– Returns function that callsinject()when invokedregister<T>(ctor, instance)– Manually register pre-created instance (testing)clear()– Reset all instances (between test runs)
✅ Used for:
Program,SourceFiles,FileProcessor,CompilerIRRI,Plugins,FileWriter,SourcePrinter
❌ NOT used for:
- User plugins (receive via hook parameters)
- Framework adapters
- Application code
Implementation
Section titled “Implementation”Simple and synchronous:
instancesMap – Stores singleton instancesinstantiatingSet – Tracks services being constructed (detects cycles)pendingMap – Tracks async initialization
Each service creates exactly one instance per compilation, shared across all consumers.