Skip to content

ExternalTypeResolution de

Edgar Mesquita edited this page Oct 13, 2026 · 1 revision

Externe Typauflösung im eQuantic.UI-Compiler

🌐 Diese Seite auf: English · Português · Español · Français · Deutsch

Überblick

Der eQuantic.UI-Compiler löst Typen auf, die in externen Dateien innerhalb desselben Projekts definiert sind. Komponenten referenzieren Modelle, DTOs und andere Klassen, ohne dass diese in derselben Datei liegen müssen: Der Compiler arbeitet gegen die vollständige Roslyn-Compilation des Projekts, die alle Quelldateien und Referenzen enthält.

// Models/User.cs
public class User
{
    public string Name { get; set; }
    public string Email { get; set; }
}

// Pages/UserProfile.cs
[Page("/profile")]
public class UserProfile : StatefulComponent
{
    private User _currentUser; // ✅ Der Typ 'User' wird vollständig aufgelöst

    protected override HtmlNode Render()
    {
        return Text(_currentUser.Name); // ✅ Wird zu: this._currentUser.name
    }
}

Ein vollständiges semantisches Modell zählt, weil die Umwandlung des Mitgliederzugriffs davon abhängt, den Typ des Empfängers zu kennen: Mit ihm senkt sich _currentUser.Name zu this._currentUser.name ab; ohne ihn kann der Compiler eine Eigenschaft nicht von einer lokalen Variable unterscheiden und nicht die richtige Methodenabbildung wählen.

Nutzung der API

Variante 1: MSBuildWorkspace nutzen (empfohlen)

using eQuantic.UI.Compiler;
using eQuantic.UI.Compiler.Services;

// Die vollständige Compilation des Projekts holen
var compilation = await ProjectCompilationHelper
    .GetProjectCompilationAsync("path/to/MyApp.csproj");

// Compiler erzeugen und die Projekt-Compilation setzen
var compiler = new ComponentCompiler();
compiler.SetProjectCompilation(compilation);

// Nun die Komponenten kompilieren: Externe Typen werden aufgelöst
var results = compiler.CompileFile("Pages/UserProfile.cs");

Variante 2: Manuelle Compilation aus den Quellen

Nützlich in MSBuild-Tasks, wo MSBuildWorkspace möglicherweise nicht verfügbar ist:

using eQuantic.UI.Compiler.Services;

// Alle .cs-Dateien des Projekts holen
var sourceFiles = ProjectCompilationHelper
    .GetProjectSourceFiles("path/to/MyApp");

// Die Assembly-Referenzen holen
var assemblyPaths = new[]
{
    "path/to/eQuantic.UI.Primitives.dll",
    "path/to/other-dependencies.dll"
};

// Die Compilation aus den Quellen erzeugen
var compilation = ProjectCompilationHelper.CreateCompilationFromSources(
    sourceFiles,
    assemblyPaths,
    "MyApp");

var compiler = new ComponentCompiler();
compiler.SetProjectCompilation(compilation);

Variante 3: Die Projekt-Compilation löschen

Zurück zum Modus minimaler Compilation (isolierte Dateien):

compiler.ClearProjectCompilation();

Wie es funktioniert

Architektur

┌─────────────────────────────────────────┐
│  Compilation des MSBuild-Projekts       │
│  - Alle .cs-Dateien des Projekts        │
│  - Alle referenzierten Assemblys        │
│  - Vollständige Typinformation          │
└────────────┬────────────────────────────┘
             │
             ↓ SetProjectCompilation()
┌────────────────────────────────────────┐
│  SemanticModelProvider                 │
│  - Hält die Projekt-Compilation        │
│  - Gibt je Datei ein SemanticModel     │
└────────────┬───────────────────────────┘
             │
             ↓ GetSemanticModel(tree)
┌────────────────────────────────────────┐
│  CSharpToJsConverter                   │
│  - Nutzt das SemanticModel für Typen   │
│  - Wandelt _currentUser.Name → name    │
└────────────────────────────────────────┘

Wichtige Klassen

  1. SemanticModelProvider (SemanticModelProvider.cs)

    • SetProjectCompilation(Compilation) - setzt die vollständige Projekt-Compilation
    • GetSemanticModel(SyntaxTree) - gibt das semantische Modell mit vollständiger Typinformation zurück
  2. ProjectCompilationHelper (ProjectCompilationHelper.cs)

    • GetProjectCompilationAsync(string) - lädt aus der .csproj
    • CreateCompilationFromSources(...) - baut aus Quelldateien
    • GetProjectSourceFiles(string) - findet alle .cs-Dateien
  3. ComponentCompiler (ComponentCompiler.cs)

    • SetProjectCompilation(Compilation) - schaltet die externe Typauflösung ein
    • ClearProjectCompilation() - kehrt zum isolierten Modus zurück

Beispiele

Beispiel 1: Komponente mit einem externen Modell

Models/Product.cs:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public bool InStock { get; set; }
}

Pages/ProductCard.cs:

[Component]
public class ProductCard : StatelessComponent
{
    public Product Item { get; set; }

    protected override HtmlNode Build()
    {
        return Container(
            Heading(Item.Name),
            Text($"${Item.Price:F2}"),
            Text(Item.InStock ? "In Stock" : "Out of Stock")
        );
    }
}

Erzeugtes JavaScript (mit der Projekt-Compilation):

class ProductCard extends StatelessComponent {
    build() {
        return Container([
            Heading(this.item.name),
            Text(`$${this.item.price.toFixed(2)}`),
            Text(this.item.inStock ? 'In Stock' : 'Out of Stock')
        ]);
    }
}

Beispiel 2: Mehrere externe Typen

Models/Address.cs:

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string ZipCode { get; set; }
}

Models/Customer.cs:

public class Customer
{
    public string Name { get; set; }
    public string Email { get; set; }
    public Address ShippingAddress { get; set; }
}

Pages/CheckoutPage.cs:

[Page("/checkout")]
public class CheckoutPage : StatefulComponent
{
    private Customer _customer;

    protected override HtmlNode Render()
    {
        return Container(
            Text($"Customer: {_customer.Name}"),
            Text($"Email: {_customer.Email}"),
            Text($"Shipping: {_customer.ShippingAddress.City}, {_customer.ShippingAddress.ZipCode}")
        );
    }
}

Alle Zugriffe auf Eigenschaften werden korrekt aufgelöst und nach JavaScript umgewandelt.

Tests

Die Tests prüfen, dass die externe Typauflösung korrekt arbeitet:

[Fact]
public void SemanticModel_WithProjectCompilation_CanResolveExternalTypes()
{
    // Die Compilation mit User und der Komponente erzeugen
    var userTree = CSharpSyntaxTree.ParseText("public class User { ... }");
    var componentTree = CSharpSyntaxTree.ParseText("public class UserProfile { ... }");

    var compilation = CSharpCompilation.Create("Test", new[] { userTree, componentTree }, ...);

    var provider = new SemanticModelProvider();
    provider.SetProjectCompilation(compilation);

    var semanticModel = provider.GetSemanticModel(componentTree);

    // Behaupten: Der Typ User wird aufgelöst
    var userType = semanticModel.Compilation.GetTypeByMetadataName("User");
    userType.Should().NotBeNull();
}

Siehe ExternalTypeResolutionTests.cs für die vollständige Testsuite.

Modi

  • Ohne SetProjectCompilation(): minimale Compilation, jede Datei wird also isoliert aufgelöst
  • Mit SetProjectCompilation(): Typauflösung über das ganze Projekt

Performance

Minimaler Mehraufwand:

  • Die Projekt-Compilation wird einmal zur Build-Zeit erzeugt
  • Sie wird von allen Komponentendateien geteilt
  • Keine Kompilierungsstrafe je Datei

Speichereffizient:

  • Eine einzige Compilation-Instanz
  • Nutzt die bestehende Roslyn-Infrastruktur wieder

Grenzen

  1. Verlangt eine Compilation vor der Komponenten-Kompilierung: Das Projekt muss kompiliert (oder mindestens geparst) sein, bevor der Komponenten-Compiler läuft.

  2. Verfügbarkeit von MSBuildWorkspace: GetProjectCompilationAsync verlangt die MSBuild-APIs, die nicht in jedem Kontext verfügbar sind. Nutzen Sie CreateCompilationFromSources als Rückfall.

  3. Erzeugter Code: Automatisch erzeugte Dateien in obj/ werden ausgeschlossen, um Konflikte zu vermeiden.

Zusammenfassung

  • Komponenten referenzieren Klassen aus anderen Dateien; die vollständige Typinformation ist während der Kompilierung verfügbar, also ist die JavaScript-Erzeugung für Mitglieder externer Typen korrekt.
  • Der SDK-Build gibt dem Compiler die echten Assembly-Referenzen des Projekts (--refs), also lösen sich Empfängertypen auch über Paketgrenzen hinweg auf.
var compilation = await ProjectCompilationHelper.GetProjectCompilationAsync(projectPath);
compiler.SetProjectCompilation(compilation);

Clone this wiki locally