11 KiB
11 KiB
Architektura CLI
Przegląd
System CLI składa się z dwóch głównych komponentów:
- Processors - Transformują kod źródłowy (template, style, DI)
- Helpers - Przetwarzają szczegółowe aspekty szablonów (atrybuty Angular)
Diagram Architektury
┌─────────────────────────────────────────────────────────────┐
│ Build Process │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Lite Transformer │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Processor Pipeline │ │
│ │ │ │
│ │ 1. ClassDecoratorProcessor │ │
│ │ └─ Process class decorators │ │
│ │ │ │
│ │ 2. SignalTransformerProcessor │ │
│ │ ├─ Transform signal calls (input, output, etc.) │ │
│ │ ├─ Add 'this' as first argument │ │
│ │ └─ Ensure _nativeElement in constructor │ │
│ │ │ │
│ │ 3. TemplateProcessor │ │
│ │ ├─ Read template file │ │
│ │ ├─ Transform control flow (@if → *ngIf) │ │
│ │ ├─ Parse HTML (TemplateParser) │ │
│ │ ├─ Process attributes (AttributeHelpers) │ │
│ │ ├─ Reconstruct HTML │ │
│ │ └─ Escape template string │ │
│ │ │ │
│ │ 4. StyleProcessor │ │
│ │ ├─ Read style files │ │
│ │ └─ Inline styles │ │
│ │ │ │
│ │ 5. DIProcessor │ │
│ │ └─ Add __di_params__ metadata │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Transformed Source │
└─────────────────────────────────────────────────────────────┘
Szczegółowy Przepływ TemplateProcessor
Template File (HTML)
│
▼
┌──────────────────────┐
│ transformControlFlow │ @if/@else → *ngIf
└──────────────────────┘
│
▼
┌──────────────────────┐
│ TemplateParser │ HTML → Element Tree
└──────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Attribute Processing Loop │
│ │
│ For each element: │
│ For each attribute: │
│ ┌────────────────────────────────────────────┐ │
│ │ Find matching AttributeHelper │ │
│ │ │ │
│ │ • StructuralDirectiveHelper (*ngIf) │ │
│ │ • InputBindingHelper ([property]) │ │
│ │ • OutputBindingHelper ((event)) │ │
│ │ • TwoWayBindingHelper ([(model)]) │ │
│ │ • TemplateReferenceHelper (#ref) │ │
│ └────────────────────────────────────────────┘ │
│ │
│ Process attribute → Update element tree │
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────┐
│ reconstructTemplate │ Element Tree → HTML
└──────────────────────┘
│
▼
┌──────────────────────┐
│ escapeTemplateString │ Escape \, `, $
└──────────────────────┘
│
▼
Inline Template String
Hierarchia Klas
Processors
BaseProcessor (abstract)
├── ClassDecoratorProcessor
├── SignalTransformerProcessor
├── TemplateProcessor
│ ├── parser: TemplateParser
│ └── attributeHelpers: BaseAttributeHelper[]
├── StyleProcessor
└── DIProcessor
Helpers
BaseAttributeHelper (abstract)
├── StructuralDirectiveHelper
│ └── Handles: *ngIf, *ngFor, *ngSwitch
├── InputBindingHelper
│ └── Handles: [property]
├── OutputBindingHelper
│ └── Handles: (event)
├── TwoWayBindingHelper
│ └── Handles: [(model)]
└── TemplateReferenceHelper
└── Handles: #ref
Interfejsy Danych
ProcessorContext
{
filePath: string; // Ścieżka do pliku źródłowego
fileDir: string; // Katalog pliku
source: string; // Zawartość pliku
}
ParsedElement
{
tagName: string; // Nazwa tagu HTML
attributes: ParsedAttribute[]; // Lista atrybutów
children: (ParsedElement | ParsedTextNode)[]; // Elementy potomne i text nodes
textContent?: string; // Zawartość tekstowa (legacy)
}
ParsedTextNode
{
type: 'text'; // Identyfikator typu
content: string; // Zawartość tekstowa z białymi znakami
}
ParsedAttribute
{
name: string; // Nazwa atrybutu
value: string; // Wartość atrybutu
type: AttributeType; // Typ atrybutu
}
AttributeType (enum)
STRUCTURAL_DIRECTIVE // *ngIf, *ngFor
INPUT_BINDING // [property]
OUTPUT_BINDING // (event)
TWO_WAY_BINDING // [(model)]
TEMPLATE_REFERENCE // #ref
REGULAR // class, id, etc.
Rozszerzalność
Dodawanie Nowego Processora
export class CustomProcessor extends BaseProcessor {
get name(): string {
return 'custom-processor';
}
async process(context: ProcessorContext): Promise<ProcessorResult> {
// Implementacja transformacji
return { source: context.source, modified: false };
}
}
Dodawanie Nowego Helpera
export class CustomAttributeHelper extends BaseAttributeHelper {
get supportedType(): string {
return 'custom';
}
canHandle(attribute: ParsedAttribute): boolean {
// Logika wykrywania
return attribute.name.startsWith('custom-');
}
process(context: AttributeProcessingContext): AttributeProcessingResult {
// Logika przetwarzania
return { transformed: true };
}
}
Wzorce Projektowe
Strategy Pattern
- BaseAttributeHelper - Interfejs strategii
- Concrete Helpers - Konkretne strategie dla różnych typów atrybutów
- TemplateProcessor - Kontekst używający strategii
Visitor Pattern
- TemplateParser.traverseElements() - Odwiedzanie elementów drzewa
- Callback function - Visitor wykonujący operacje na elementach
Pipeline Pattern
- Processor chain - Sekwencyjne przetwarzanie przez kolejne procesory
- Każdy processor otrzymuje wynik poprzedniego
Factory Pattern
- Constructor TemplateProcessor - Tworzy instancje wszystkich helperów
- Centralizacja tworzenia zależności
Wydajność
Optymalizacje
- Single-pass parsing - Jeden przebieg przez szablon
- Lazy evaluation - Przetwarzanie tylko zmodyfikowanych plików
- Reverse iteration - Unikanie problemów z offsetami przy zamianie
Złożoność
- Parsing: O(n) gdzie n = długość szablonu
- Attribute processing: O(m × h) gdzie m = liczba atrybutów, h = liczba helperów
- Reconstruction: O(e + t) gdzie e = liczba elementów, t = liczba text nodes
Obsługa Text Nodes
Parser automatycznie wykrywa i zachowuje text nodes jako osobne węzły:
- Text nodes mogą występować na poziomie root lub jako dzieci elementów
- Zachowują wszystkie białe znaki (spacje, nowe linie, tabulatory)
- Są pomijane przez
traverseElements()(tylko elementy HTML) - Są prawidłowo rekonstruowane podczas budowania HTML
- Używają type guard
isTextNode()do rozróżnienia od elementów
Testowanie
Unit Tests
// Test parsera
const parser = new TemplateParser();
const result = parser.parse('<div [title]="value"></div>');
expect(result[0].attributes[0].type).toBe(AttributeType.INPUT_BINDING);
// Test helpera
const helper = new InputBindingHelper();
expect(helper.canHandle({ name: '[title]', value: 'x', type: AttributeType.INPUT_BINDING })).toBe(true);
Integration Tests
// Test całego procesora
const processor = new TemplateProcessor();
const result = await processor.process({
filePath: '/test/component.ts',
fileDir: '/test',
source: 'templateUrl = "./template.html"'
});
expect(result.modified).toBe(true);
Dokumentacja
- processors/README.md - Dokumentacja procesorów
- helpers/README.md - Dokumentacja helperów
- helpers/example.md - Przykłady użycia