Skip to content

ExternalTypeResolution es

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

Resolución de tipos externos en el compilador de eQuantic.UI

🌐 Esta página en: English · Português · Español · Français · Deutsch

Panorama

El compilador de eQuantic.UI resuelve los tipos definidos en archivos externos dentro del mismo proyecto. Los componentes referencian modelos, DTOs y otras clases sin que tengan que estar en el mismo archivo: el compilador trabaja contra la Compilation completa de Roslyn del proyecto, que incluye todos los archivos fuente y las referencias.

// 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; // ✅ El tipo 'User' se resuelve por completo

    protected override HtmlNode Render()
    {
        return Text(_currentUser.Name); // ✅ Se convierte en: this._currentUser.name
    }
}

Un modelo semántico completo importa porque la conversión del acceso a miembros depende de conocer el tipo del receptor: con él, _currentUser.Name baja a this._currentUser.name; sin él, el compilador no puede distinguir una propiedad de una variable local ni elegir el mapeo de método correcto.

Uso de la API

Opción 1: usar MSBuildWorkspace (recomendado)

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

// Obtener la compilación completa del proyecto
var compilation = await ProjectCompilationHelper
    .GetProjectCompilationAsync("path/to/MyApp.csproj");

// Crear el compilador y fijar la compilación del proyecto
var compiler = new ComponentCompiler();
compiler.SetProjectCompilation(compilation);

// Ahora compila los componentes: los tipos externos se resolverán
var results = compiler.CompileFile("Pages/UserProfile.cs");

Opción 2: compilación manual a partir de las fuentes

Útil en tareas de MSBuild donde MSBuildWorkspace puede no estar disponible:

using eQuantic.UI.Compiler.Services;

// Obtener todos los archivos .cs del proyecto
var sourceFiles = ProjectCompilationHelper
    .GetProjectSourceFiles("path/to/MyApp");

// Obtener las referencias de ensamblados
var assemblyPaths = new[]
{
    "path/to/eQuantic.UI.Primitives.dll",
    "path/to/other-dependencies.dll"
};

// Crear la compilación a partir de las fuentes
var compilation = ProjectCompilationHelper.CreateCompilationFromSources(
    sourceFiles,
    assemblyPaths,
    "MyApp");

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

Opción 3: limpiar la compilación del proyecto

Volver al modo de compilación mínima (archivos aislados):

compiler.ClearProjectCompilation();

Cómo funciona

Arquitectura

┌─────────────────────────────────────────┐
│  Compilación del proyecto MSBuild       │
│  - Todos los .cs del proyecto           │
│  - Todos los ensamblados referenciados  │
│  - Información de tipos completa        │
└────────────┬────────────────────────────┘
             │
             ↓ SetProjectCompilation()
┌────────────────────────────────────────┐
│  SemanticModelProvider                 │
│  - Guarda la compilación del proyecto  │
│  - Devuelve un SemanticModel por archivo│
└────────────┬───────────────────────────┘
             │
             ↓ GetSemanticModel(tree)
┌────────────────────────────────────────┐
│  CSharpToJsConverter                   │
│  - Usa el SemanticModel para los tipos │
│  - Convierte _currentUser.Name → name  │
└────────────────────────────────────────┘

Clases clave

  1. SemanticModelProvider (SemanticModelProvider.cs)

    • SetProjectCompilation(Compilation) - fija la compilación completa del proyecto
    • GetSemanticModel(SyntaxTree) - devuelve el modelo semántico con la información de tipos completa
  2. ProjectCompilationHelper (ProjectCompilationHelper.cs)

    • GetProjectCompilationAsync(string) - carga desde el .csproj
    • CreateCompilationFromSources(...) - construye a partir de archivos fuente
    • GetProjectSourceFiles(string) - encuentra todos los archivos .cs
  3. ComponentCompiler (ComponentCompiler.cs)

    • SetProjectCompilation(Compilation) - habilita la resolución de tipos externos
    • ClearProjectCompilation() - vuelve al modo aislado

Ejemplos

Ejemplo 1: componente con un modelo externo

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")
        );
    }
}

JavaScript generado (con la compilación del proyecto):

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')
        ]);
    }
}

Ejemplo 2: varios tipos externos

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}")
        );
    }
}

Todos los accesos a propiedades se resuelven correctamente y se convierten a JavaScript.

Pruebas

Las pruebas verifican que la resolución de tipos externos funciona correctamente:

[Fact]
public void SemanticModel_WithProjectCompilation_CanResolveExternalTypes()
{
    // Crear la compilación con User y el componente
    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);

    // Afirmar: el tipo User se resuelve
    var userType = semanticModel.Compilation.GetTypeByMetadataName("User");
    userType.Should().NotBeNull();
}

Vea ExternalTypeResolutionTests.cs para la suite de pruebas completa.

Modos

  • Sin SetProjectCompilation(): compilación mínima, así que cada archivo se resuelve de forma aislada
  • Con SetProjectCompilation(): resolución de tipos de todo el proyecto

Rendimiento

Sobrecoste mínimo:

  • La compilación del proyecto se crea una vez, en tiempo de build
  • Se comparte entre todos los archivos de componente
  • Ninguna penalización de compilación por archivo

Eficiente en memoria:

  • Una única instancia de compilación
  • Reutiliza la infraestructura de Roslyn existente

Limitaciones

  1. Requiere compilar antes de compilar los componentes: el proyecto tiene que estar compilado (o al menos parseado) antes de ejecutar el compilador de componentes.

  2. Disponibilidad de MSBuildWorkspace: GetProjectCompilationAsync requiere las APIs de MSBuild, que pueden no estar disponibles en todos los contextos. Usa CreateCompilationFromSources como alternativa.

  3. Código generado: los archivos autogenerados en obj/ se excluyen para evitar conflictos.

Resumen

  • Los componentes referencian clases de otros archivos; la información de tipos completa está disponible durante la compilación, así que la generación de JavaScript para los miembros de tipos externos es correcta.
  • El build del SDK alimenta al compilador con las referencias de ensamblado reales del proyecto (--refs), así que los tipos del receptor se resuelven incluso entre fronteras de paquete.
var compilation = await ProjectCompilationHelper.GetProjectCompilationAsync(projectPath);
compiler.SetProjectCompilation(compilation);

Clone this wiki locally