eQuantic.UIeQuantic.UI
Docs
Playground
GitHub
DocsCompilación
Resolución de tipos externos en el compilador de eQuantic.UI
Edit this page
2 min read
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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)
1
2
3
4
5
6
7
8
9
10
11
12
13
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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):
1
compiler.ClearProjectCompilation();
Cómo funciona
Arquitectura
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────┐
│ 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:
1
2
3
4
5
6
7
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[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):
1
2
3
4
5
6
7
8
9
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:
1
2
3
4
5
6
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
}
Models/Customer.cs:
1
2
3
4
5
6
public class Customer
{
public string Name { get; set; }
public string Email { get; set; }
public Address ShippingAddress { get; set; }
}
Pages/CheckoutPage.cs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[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.
1
2
var compilation = await ProjectCompilationHelper.GetProjectCompilationAsync(projectPath);
compiler.SetProjectCompilation(compilation);