Skip to content

Dependency Injection

Pencel uses a lightweight DI container for core compiler components only. Not for plugins or application code.

  • 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 instance
  • injectLazy<T>(ctor) – Returns function that calls inject() when invoked
  • register<T>(ctor, instance) – Manually register pre-created instance (testing)
  • clear() – Reset all instances (between test runs)

✅ Used for:

  • Program, SourceFiles, FileProcessor, Compiler
  • IRRI, Plugins, FileWriter, SourcePrinter

❌ NOT used for:

  • User plugins (receive via hook parameters)
  • Framework adapters
  • Application code

Simple and synchronous:

  • instances Map – Stores singleton instances
  • instantiating Set – Tracks services being constructed (detects cycles)
  • pending Map – Tracks async initialization

Each service creates exactly one instance per compilation, shared across all consumers.