eQuantic.UIeQuantic.UI
Docs
Playground
GitHub
DocsCompilation
Résolution des types externes dans le compilateur eQuantic.UI
Edit this page
3 min read
Vue d'ensemble
Le compilateur eQuantic.UI résout les types définis dans des fichiers externes au sein du même projet. Les composants référencent des modèles, des DTO et d'autres classes sans qu'elles aient à se trouver dans le même fichier : le compilateur travaille contre la Compilation Roslyn complète du projet, qui comprend tous les fichiers source et les références.
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; //
✅
Le type 'User' est entièrement résolu
protected override HtmlNode Render()
{
return Text(_currentUser.Name); //
✅
Se convertit en : this._currentUser.name
}
}
Un modèle sémantique complet compte parce que la conversion de l'accès aux membres dépend de la connaissance du type du receveur : avec lui, _currentUser.Name s'abaisse en this._currentUser.name ; sans lui, le compilateur ne peut pas distinguer une propriété d'une variable locale ni choisir le bon mappage de méthode.
Usage de l'API
Option 1 : utiliser MSBuildWorkspace (recommandé)
1
2
3
4
5
6
7
8
9
10
11
12
13
using eQuantic.UI.Compiler;
using eQuantic.UI.Compiler.Services;
// Obtenir la compilation complète du projet
var compilation = await ProjectCompilationHelper
.GetProjectCompilationAsync("path/to/MyApp.csproj");
// Créer le compilateur et poser la compilation du projet
var compiler = new ComponentCompiler();
compiler.SetProjectCompilation(compilation);
// Maintenant compilez les composants : les types externes seront résolus
var results = compiler.CompileFile("Pages/UserProfile.cs");
Option 2 : compilation manuelle depuis les sources
Utile dans des tâches MSBuild où MSBuildWorkspace peut ne pas être 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;
// Obtenir tous les fichiers .cs du projet
var sourceFiles = ProjectCompilationHelper
.GetProjectSourceFiles("path/to/MyApp");
// Obtenir les références d'assemblys
var assemblyPaths = new[]
{
"path/to/eQuantic.UI.Primitives.dll",
"path/to/other-dependencies.dll"
};
// Créer la compilation depuis les sources
var compilation = ProjectCompilationHelper.CreateCompilationFromSources(
sourceFiles,
assemblyPaths,
"MyApp");
var compiler = new ComponentCompiler();
compiler.SetProjectCompilation(compilation);
Option 3 : effacer la compilation du projet
Revenir au mode de compilation minimale (fichiers isolés) :
1
compiler.ClearProjectCompilation();
Comment cela marche
Architecture
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────┐
│ Compilation du projet MSBuild │
│ - Tous les .cs du projet │
│ - Tous les assemblys référencés │
│ - Information de types complète │
└────────────┬────────────────────────────┘
│
↓ SetProjectCompilation()
┌────────────────────────────────────────┐
│ SemanticModelProvider │
│ - Garde la compilation du projet │
│ - Renvoie un SemanticModel par fichier│
└────────────┬───────────────────────────┘
│
↓ GetSemanticModel(tree)
┌────────────────────────────────────────┐
│ CSharpToJsConverter │
│ - Utilise le SemanticModel pour les types │
│ - Convertit _currentUser.Name → name │
└────────────────────────────────────────┘
Classes clés
1.
SemanticModelProvider (SemanticModelProvider.cs)
•
SetProjectCompilation(Compilation) - pose la compilation complète du projet
•
GetSemanticModel(SyntaxTree) - renvoie le modèle sémantique avec l'information de types complète
2.
ProjectCompilationHelper (ProjectCompilationHelper.cs)
•
GetProjectCompilationAsync(string) - charge depuis le .csproj
•
CreateCompilationFromSources(...) - construit depuis des fichiers source
•
GetProjectSourceFiles(string) - trouve tous les fichiers .cs
3.
ComponentCompiler (ComponentCompiler.cs)
•
SetProjectCompilation(Compilation) - active la résolution des types externes
•
ClearProjectCompilation() - revient au mode isolé
Exemples
Exemple 1 : composant avec un modèle externe
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 généré (avec la compilation du projet) :
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')
]);
}
}
Exemple 2 : plusieurs types externes
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}")
);
}
}
Tous les accès aux propriétés sont correctement résolus et convertis en JavaScript.
Tests
Les tests vérifient que la résolution des types externes marche correctement :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[Fact]
public void SemanticModel_WithProjectCompilation_CanResolveExternalTypes()
{
// Créer la compilation avec User et le composant
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);
// Affirmer : le type User est résolu
var userType = semanticModel.Compilation.GetTypeByMetadataName("User");
userType.Should().NotBeNull();
}
Voir ExternalTypeResolutionTests.cs pour la suite de tests complète.
Modes
•
Sans SetProjectCompilation() : compilation minimale, donc chaque fichier est résolu isolément
•
Avec SetProjectCompilation() : résolution des types de tout le projet
Performance
Surcoût minimal :
•
La compilation du projet est créée une fois, au build
•
Elle est partagée par tous les fichiers de composants
•
Aucune pénalité de compilation par fichier
Économe en mémoire :
•
Une seule instance de compilation
•
Réutilise l'infrastructure Roslyn existante
Limites
1.
Demande une compilation avant la compilation des composants : le projet doit être compilé (ou au moins parsé) avant de lancer le compilateur de composants.
2.
Disponibilité de MSBuildWorkspace : GetProjectCompilationAsync demande les API MSBuild, qui peuvent ne pas être disponibles dans tous les contextes. Utilisez CreateCompilationFromSources en repli.
3.
Code généré : les fichiers auto-générés dans obj/ sont exclus pour éviter les conflits.
Résumé
•
Les composants référencent des classes d'autres fichiers ; l'information de types complète est disponible pendant la compilation, donc la génération de JavaScript pour les membres de types externes est correcte.
•
Le build du SDK alimente le compilateur avec les vraies références d'assemblys du projet (--refs), donc les types de receveur se résolvent même par-delà les frontières de paquets.
1
2
var compilation = await ProjectCompilationHelper.GetProjectCompilationAsync(projectPath);
compiler.SetProjectCompilation(compilation);