Resolución de tipos externos en el compilador de eQuantic.UIEl 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.
public string Name { get; set; }
public string Email { get; set; }
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.
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(
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();
┌─────────────────────────────────────────┐
│ 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│
└────────────┬───────────────────────────┘
┌────────────────────────────────────────┐
│ - Usa el SemanticModel para los tipos │
│ - Convierte _currentUser.Name → name │
└────────────────────────────────────────┘
•
SetProjectCompilation(Compilation) - fija la compilación completa del proyecto
•
GetSemanticModel(SyntaxTree) - devuelve el modelo semántico con la información de tipos completa
•
GetProjectCompilationAsync(string) - carga desde el .csproj
•
CreateCompilationFromSources(...) - construye a partir de archivos fuente
•
GetProjectSourceFiles(string) - encuentra todos los archivos .cs
•
SetProjectCompilation(Compilation) - habilita la resolución de tipos externos
•
ClearProjectCompilation() - vuelve al modo aislado
Ejemplo 1: componente con un modelo externo
Models/Product.cs:
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public bool InStock { get; set; }
Pages/ProductCard.cs:
public class ProductCard : StatelessComponent
public Product Item { get; set; }
protected override HtmlNode Build()
Text($"${Item.Price:F2}"),
Text(Item.InStock ? "In Stock" : "Out of Stock")
JavaScript generado (con la compilación del proyecto):
class ProductCard extends StatelessComponent {
Text(`$${this.item.price.toFixed(2)}`),
Text(this.item.inStock ? 'In Stock' : 'Out of Stock')
Ejemplo 2: varios tipos externos
Models/Address.cs:
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
Models/Customer.cs:
public string Name { get; set; }
public string Email { get; set; }
public Address ShippingAddress { get; set; }
Pages/CheckoutPage.cs:
public class CheckoutPage : StatefulComponent
private Customer _customer;
protected override HtmlNode Render()
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.
Las pruebas verifican que la resolución de tipos externos funciona correctamente:
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();
•
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
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
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.
•
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);