TypeLITE: generator C# => TypeScript

W projekcie Webowym z bogatą logiką FrontEndową zawsze w pewnym momencie stajemy przed problemem synchronizacji klas, które mamy w C# z tymi po stronie FE. Jednym z ułatwień jest korzystanie z TypeScript, który udostępnia silne typowanie po stronie FE. Ciągle jednak może być tak, że klasa w C# rozjedzie się z klasą w TypeScript (np. po refaktoringu).

TypeLITE

TypeLITE is an utility, that generates TypeScript definitions from .NET classes. It supports all major features of the current TypeScript specification, including modules and inheritance.

TypeLITE generuje C# => TypeScript. Oczywiście tylko definicje właściwości/pól, ick implementacja nie miałaby sensu.

C#:

public class Person {
    public string Name { get; set; }
    public List<address> Addresses { get; set; }
}
  
public class Employee : Person {
    public decimal Salary { get; set; }
}
  
public class Address {
    public string Street { get; set; }
}

TypeScript:

interface Person {
    Name: string;
    Addresses: Address[];
}
  
interface Employee extends Person {
    Salary: number;
}
  
interface Address {
    Street: string;
}

Przykład użycia

Poniżej opiszę przykład z mojego aktualnego projektu. W trakcie implementacji pojawiło się kilka problemów.

  • TypeLITE uruchamia się w momencie wykonywania „Run custom tool” (albo zapisujemy) na pliku TypeLite.Net4.tt i generuje klasy do plików TypeLite.Net4.d.ts oraz Enums.ts. Takie ustawienie było domyślnie i tak zostało. Przy takim ustawieniu czasami można zapomnieć wygenerować wszystko TypeLITEm i poprawić definicje w FE. Wyjdzie w trakcie CodeReview lub w Runtime
  • Jest kilka sposobów na wskazanie klas z C#, które mają być wygenerowane. Te fajne (otagowanie atrybutem) jakoś nie zadziałały. Więc w pliku konfiguracji TypeLite.Net4.tt zdefiniowałem je explicite:
    //...	
    <# var ts = TypeScript.Definitions()
    		.For<KMorcinek.Examples.UserDto>()
    		.For<KMorcinek.Examples.Features.ListAdultUsersQuery>()
    //...	
    

    Nie musiałem wskazywać każdej klasy, wystarczy że podałem te „ostateczne”. Gdy UserDto posiada AddressDto to AddressDto również zostanie wygenerowane.

  • Następnie explicite podałem, z które projekty należy przeszukiwać. Początek pliku w moim przypadku wyglądał tak:
    <#@ template debug="false" hostspecific="True" language="C#" #>
    <#@ assembly name="$(TargetDir)TypeLite.dll" #>
    <#@ assembly name="$(TargetDir)TypeLite.Net4.dll" #>
    <#@ assembly name="$(TargetDir)$(TargetFileName)" #> // means current WebApp project
    <#@ assembly name="$(TargetDir)YourProject.Domain.dll" #>
    <#@ assembly name="$(TargetDir)YourProject.Application.dll" #>
    
  • skorzystałem z customizacji, która sprawiała że nazwy w TS są camelCase
    <# var ts = TypeScript.Definitions()
    //...
        .WithMemberFormatter(identifier => 
            Char.ToLower(identifier.Name[0]) + identifier.Name.Substring(1)
        );
    

    podejście dosyć proste (zmienia tylko pierwszą literę), ale działa. Jeśli chcesz aby „VAT” zostało zmienione na „VAT” zamiast „vAT” to trzeba chyba podpiąć kod podobny do JsonNetSerializera.

  • kolejna customizacja polegała na podmianie namespace na coś krótszego.
    <# var ts = TypeScript.Definitions()
    //...	
        .WithModuleNameFormatter(module => { 
            return module.Name == "System" ? "System" : "YouApplicationName"; 
        })
    

    Dla Guid’ów (w namespace System) był System, dla pozostałych „YouApplicationName”. Nie chcę się bawić ze zbyt długimi namespace’ami.

  • Konfiguracja generowania podczas procesu buildowania – nie powiodła się, chociaż poszło na to kilka godzin. Należało doinstalować dodatkowy pakiet do VS, ale było to zbyt kruche rozwiązane i nie miałem czasu, żeby to rozkminić. Na razie zostawiłem jak jest, poczekam na lepsze czasy.

Problem zapętlenia budowania C# i TypeScript’a

Narzędzie TypeLITE potrzebuje do prawidłowego działania zbudowanych dll’ek projektów, z których pobiera klasy/modele.

Problem #1:

  1. Załóżmy, że zmieniamy coś w definicji klasy, np. dodaliśmy nową

    właściwość

  2. Wykonujemy „Run custom tool” (albo zapisujemy) na TypeLite.Net4.tt
  3. Ale … coś poszło źle, generator miał błędy i wygenerowany plik TypeLite.Net4.d.ts jest pusty
  4. W związku z powyższym nie możemy przebudować całej solucji ponieważ TypeScript rzuca błędami. Z tego miejsca już nie da się ładnie wrócić

Rozwiązanie:

  1. git checkout -- *.Net4.d.ts wracamy do poprzedniej wersji wygenerowanego pliku, teraz FE się builduje prawidłowo
  2. Poprawiamy w C# to co było źle
  3. Przebudowujemy wszystko
  4. Wykonujemy „Run custom tool” (albo zapisujemy) TypeLite.Net4.tt, co powoduje wygenerowanie poprawnego pliku TypeLite.Net4.d.ts

Problem #2:

W przypadku dodania nowej klasy do C# i dodania do niej ścieżki w pliku TypeLite.Net4.tt, i nie przebudowanu całej solucji przed regenerowaniem aktualnego TypeScriptu znowu może pojawić się się problem z brakiem definicji w plikach TypeLite.Net4.d.ts oraz Enums.ts.

Rozwiązanie:

Rozwiązanie dokładnie takie samo jak opisane w Problemie #1.

Inne

Jak tak o tym piszę to widzę że „Net4.” jest zbyt często powtarzane i prawdopodobnie mogę to usunąć.

Miejsce na improvement:
będę wdzięczny gdyby ktoś fajnie opisał jak debugować to coś 🙂

1 Comments on “TypeLITE: generator C# => TypeScript

  1. Pingback: dotnetomaniak.pl