Fable: Serialisieren in Fable 2

Erstellt am 2. März 2018  ·  17Kommentare  ·  Quelle: fable-compiler/Fable

Fortsetzung der Diskussion hier gestartet https://github.com/SaturnFramework/Saturn/issues/33

In diesem Twitter-Thread gibt es auch einige interessante Kommentare zur impliziten vs. expliziten Serialisierung.

Wahrscheinlich war ich nicht gut genug, um meine Absichten in dem verlinkten Thema auszudrücken, und (wie jede bahnbrechende Veränderung) hat dies einige Kontroversen ausgelöst. Ich werde versuchen, meine aktuelle Denkweise im Folgenden darzulegen, um eine bessere Grundlage für die Diskussion zu haben :)

  • Fable 2 alpha wurde zunächst ohne Reflection-Unterstützung ausgeliefert. Ich gehe davon aus, dass dies hauptsächlich ofJson/toJson betreffen wird, da ich denke, dass es derzeit nicht viele andere Überlegungen in Fable gibt. Bitte beachten Sie, dass die Alpha-Version offensichtlich nicht für die Produktion gedacht ist, sondern für Benutzer, die es ausprobieren und Feedback geben können.

  • Warum die Reflexionsunterstützung weglassen? Nun, am Ende schreibe ich große Teile des Codes um, um ihn (hoffentlich) sauberer, wartbarer und für Mitwirkende ansprechender zu machen. Beim Refactoring habe ich festgestellt, dass das Reflexionsmodell in Fable nicht konsistent ist und sowohl den generierten JS-Code (die Reduzierung der Paketgröße ist eines der Hauptziele für Fable 2) als auch die Fable-Codebasis stark verschmutzt. Aus diesem Grund möchte ich mit Fable 2 alpha _neu anfangen_, um die tatsächlichen Bedürfnisse der Benutzer zu erkennen und es von Grund auf neu zu implementieren (oder nicht, wenn wir es nicht brauchen).

  • Wie würde Reflexion neu implementiert? Derzeit sind Serialisierungsinformationen in die Typen eingebettet. Dies lässt die Typen dicker aussehen und es ist ein Problem, wenn Leute Alternativen in der REPL vergleichen, da sie feststellen werden, dass Fable viel mehr Code für einfache Typen generiert (es ist bereits passiert). Für Fable 2 habe ich zwei Optionen in Betracht gezogen:

    • Stellen Sie Reflexionsinformationen über eine statische Methode zur Verfügung, in der Hoffnung, dass sie beim Erstellen für die Produktion durch Baumschütteln entfernt werden. Dies wäre wahrscheinlich der einfachste Weg, aber es wird immer noch der Code in der REPL angezeigt.
    • Generieren Sie Reflexionsinformationen auf der Anrufseite, um beispielsweise typeof<Foo> zu ersetzen. Ich denke, dies wird in den meisten Fällen gut sein, aber es kann Apps bestrafen, die Reflektion ausgiebig verwenden, da wahrscheinlich duplizierter Code vorhanden ist.

      • Ich habe auch eine dritte Option in Betracht gezogen: Fügen Sie eine zusätzliche Datei mit den Reflexionsinformationen hinzu, damit sie für den Benutzer verborgen bleibt und nur bei Bedarf abgerufen wird. Aber die Art und Weise, wie Fable derzeit mit JS-Bündlern und -Tools (Webpack...) interagiert, macht dies kompliziert.

  • Wie könnte die automatische Serialisierung in Fable 2 funktionieren? Datensätze werden zu einfachen JS-Objekten und Unions, JS-Arrays. In den meisten Fällen würde es also funktionieren, nur native JSON.parse/stringify aufzurufen. Das Problem Dinge sein würde , die mit dem Browser nicht kompatibel sind JSON api, wie Karten, Sets, Daten, longs, etc ... So Fable noch Informationen müssen wissen , über die Felder zur Laufzeit, um richtig aufblasen/ablassen.

  • Was mir an der aktuellen Serialisierung nicht gefällt? Es gibt ein paar Dinge

    • Es funktioniert in den meisten Fällen, aber nicht in allen Fällen, und es kann Sie zur Laufzeit überraschen, was nicht gut ist, wenn wir eine _sichere_ Sprache verkaufen.
    • Es _mirror_ Newtonsoft.Json auf der Frontend-Seite, und einige Leute erwarten, dass es Dinge wie Attribute, das Einbetten von Typinformationen in das Json usw. unterstützt.

      • Andere mir bekannte Sprachen (einschließlich F#) haben keine in das Kernsystem eingebettete Serialisierung. Dies erhöht die Wartungskosten der Fable-Codebasis und erschwert deren Refactoring (wie es bei Fable 2 der Fall war). Es würde mich sehr freuen, die Serialisierung in eine externe Bibliothek zu verschieben.

  • Welche Alternativen gibt es? Wie in der obigen Ausgabe erwähnt, ist die wichtigste Alternative, die derzeit mit Fable 2 alpha funktionieren sollte, Thot.Json . Diese Bibliothek gibt Ihnen mehr Kontrolle über das generierte JSON und eine viel bessere Validierung. Der einzige Nachteil ist, dass Sie die Decoder selbst schreiben müssen, aber es gibt bereits Arbeit, sie automatisch zu

dev2.0 discussion

Hilfreichster Kommentar

Einer der Gründe, warum ich Fable mag, ist die JSON-Unterstützung. Es wird ein harter Verkauf sein, überall Encoder/Decoder-Funktionen hinzufügen zu müssen. Ich erinnere mich, dass in sehr, sehr frühen Releases F#-Typen sehr nahe an JS-Objekten waren und man die meiste Zeit nur JSON.parse/stringify und das Wissen um diese Einschränkung bedeutete, dass ich es fast zum Laufen bringen konnte. Als Fable besser wurde, begann ich leider, Listen und DateTimes in meinem JSON zu verwenden

Wenn die Thot.Json-Codegenerierung Teil der Build-Kette für Client und Server sein könnte (in net46x - ja, ich weiß, eines Tages muss ich aktualisieren), vielleicht als eine Art Pre-Build-Ereignis, das Fake aufruft (was ich verwenden, um eine SQL-Datenbank für FSharp.Data.SqlClient bereitzustellen) dann könnte funktionieren? Oder sind MS-Build-Aufgaben/-Ziele immer noch eine Sache... Wie funktioniert die automatische Wiederherstellung von Paketen?

_Ich habe mich vor Jahren mit Newtonsoft.Json zerstritten._

Alle 17 Kommentare

Einer der Gründe, warum ich Fable mag, ist die JSON-Unterstützung. Es wird ein harter Verkauf sein, überall Encoder/Decoder-Funktionen hinzufügen zu müssen. Ich erinnere mich, dass in sehr, sehr frühen Releases F#-Typen sehr nahe an JS-Objekten waren und man die meiste Zeit nur JSON.parse/stringify und das Wissen um diese Einschränkung bedeutete, dass ich es fast zum Laufen bringen konnte. Als Fable besser wurde, begann ich leider, Listen und DateTimes in meinem JSON zu verwenden

Wenn die Thot.Json-Codegenerierung Teil der Build-Kette für Client und Server sein könnte (in net46x - ja, ich weiß, eines Tages muss ich aktualisieren), vielleicht als eine Art Pre-Build-Ereignis, das Fake aufruft (was ich verwenden, um eine SQL-Datenbank für FSharp.Data.SqlClient bereitzustellen) dann könnte funktionieren? Oder sind MS-Build-Aufgaben/-Ziele immer noch eine Sache... Wie funktioniert die automatische Wiederherstellung von Paketen?

_Ich habe mich vor Jahren mit Newtonsoft.Json zerstritten._

Nur um einen Kontrapunkt zu generieren, verwende ich derzeit Reflection in einer Fable 1-Knoten-App für die Produktion, um Nachrichtentypen in einer DU zu deserialisieren:

https://github.com/intel-hpdd/device-scanner/blob/16233ff62ad710aa02d6c8fe8acdbcad0c3e1e3e/IML.DeviceScannerDaemon/src/Main.fs#L13 -L20

und zum automatischen Bestimmen der Codierungstypen von Node-Streams, damit ich Transformationen zusammenstellen und sie wie erwartet zur Laufzeit typchecken / konfigurieren kann:

https://github.com/intel-hpdd/fable-import-node-powerpack/blob/4004f9c430517c1f26bf47f9c2f766598e500b0d/fable/Stream.fs#L99 -L120

https://github.com/intel-hpdd/device-scanner/blob/16233ff62ad710aa02d6c8fe8acdbcad0c3e1e3e/IML.DeviceScannerDaemon/src/Main.fs#L23 -L42

Von Verbrauchern dieses Dienstes wird erwartet, dass sie eine Nachricht an den Daemon senden, um gestreamte Daten zu erhalten: https://github.com/intel-hpdd/device-scanner/tree/master/IML.DeviceScannerDaemon (also stattdessen einen Datensatz / eine Zeichenfolge beibehalten) eines Arrays ist ideal), aber ich denke, ich kann nur Strings abgleichen, bevor ich versuche, eine Serialisierung durchzuführen, um das zu umgehen.

Das größere Problem für mich ist, dass ich die Möglichkeit verliere, Knotenströme basierend auf fehlenden Reflexionsinformationen zur Laufzeit automatisch zu konfigurieren, aber ich kann dies möglicherweise auch umgehen.

@davidtme Ich

Ich werde eine Ausgabe auf Thot.Json eröffnen, um meine zukünftigen Fortschritte zu diesem Thema zu verfolgen.

Zu Ihrer Information veröffentliche ich nur Thot.Json.Net , das dieselbe API wie Thot.Json für Fable bereitstellt.

Die Idee ist, dass Sie die Compiler-Direktive verwenden können, um den Code zu teilen:

// By adding this condition, you can share you code between your client and server 
#if FABLE_COMPILER
open Thot.Json
#else
open Thot.Json.Net
#endif

Dokumentation

@alfonsogarciacaro Wäre es möglich, serialization Prozess abhängig davon anzupassen?

Die Typinfo ist beim Kompilieren des Typs verfügbar, ja. Wenn wir jedoch wollen, dass _beide_ ohne Reflektion auf Typinformationen zugreifen und die Serialisierung außerhalb des Compilers verschieben, bräuchten wir eine Art Plugin (das auch in Fable 2 alpha nicht verfügbar sein wird :wink:). Aber was meinst du genau mit "Anpassung des Serialisierungsprozesses"?

TBH, ich finde es seltsam, Funktionen auszuschließen, nur um das generierte Javascript für Leute, die Fable bewerten, attraktiver zu machen. Ich weiß, es ist das Motto von Fable, schönes Javascript zu generieren, und ich mag es wirklich, aber IMO, das stärkste Verkaufsargument, das Fable als Compiler für Javascript hat, verwendet F#, die großartige Interoperabilität mit dem JS-Ökosystem und ist auf jedes Javascript anwendbar Laufzeit. Nett generiertes Javascript ist genau das: nice to have. (Wie wäre es, wenn Sie tatsächlich eine Umfrage durchführen, um Benutzer zu fragen?)

es funktioniert in den meisten Fällen, aber nicht in allen Fällen, und es kann Sie zur Laufzeit überraschen, was nicht gut ist, wenn wir eine sichere Sprache verkaufen.

Dann setzen wir die fehlgeschlagenen Ausnahmefälle um. Fehler bei der automatischen Serialisierung/Deserialisierung scheinen an Stellen aufzutreten, an denen wir zur Laufzeit nicht über genügend Metadaten verfügen.

Es spiegelt irgendwie Newtonsoft.Json auf der Frontend-Seite wider, und einige Leute erwarten, dass es Dinge wie Attribute, das Einbetten von Typinformationen in das Json usw. unterstützt.

Ich würde sagen, es bietet die gleiche Benutzerfreundlichkeit von Newtonsoft.Json und Benutzer sind bereits sehr zufrieden damit, es als Standard zu verwenden. Wenn die Leute mehr Kontrolle und Anpassungen wünschen, bieten Thot.Json oder Fable.SimpleJson das erforderliche Maß an Kontrolle.

Andere mir bekannte Sprachen (einschließlich F#) haben keine in das Kernsystem eingebettete Serialisierung. Dies erhöht die Wartungskosten der Fable-Codebasis und erschwert deren Refactoring (wie es bei Fable 2 der Fall war). Es würde mich sehr freuen, die Serialisierung in eine externe Bibliothek zu verschieben.

Ich stimme zu, dass die Wartungskosten mit ofJson<'a> und 'toJson' erhöht werden, aber es lohnt sich wirklich. Wenn wir eine externe Bibliothek erstellen möchten, muss die Reflexion gut implementiert sein, damit die Verbraucher solche Konverter schreiben können

@Zaid-Ajaj Aus meiner Sicht geht es nicht nur darum, das generierte JavaScript schön aussehen zu lassen, sondern auch um die Bundle-Größe zu reduzieren.

Wir sehen viele Leute, die sich darüber beschweren, insbesondere in Ländern, in denen die Internetverbindung noch langsam ist :)

@alfonsogarciacaro Ich habe darüber nachgedacht, Fable den Serializer-Aufruf so anzupassen, dass er Maps, Sets usw. unterstützt,

@alfonsogarciacaro Wäre es möglich, die Typenmodul" einzubetten.

Das Modul könnte alle Typinformationen der App enthalten und wenn es nicht verwendet wird, sollte Webpack es entfernen können, nein?

Vielen Dank für Ihre Kommentare. Ich verstehe, dass die aktuellen ofJson/toJson Helfer sehr praktisch sind und nicht ersetzt werden sollten, es sei denn, wir bieten etwas, das fast genauso einfach zu verwenden ist. Danke auch für die Beispiele @jgrund , es ist sehr nützlich zu sehen, wie Fable in der Produktion verwendet wird. Soweit ich sehen kann, wird Reflection nur für ofJson . Gibt es einen anderen Ort, an dem Sie typeof<'T> oder ähnliches verwenden?

Danke auch für deine Einblicke @Zaid-Ajaj. Wie Maxime sagt, geht es nicht nur darum, den Code lesbarer zu machen (eigentlich kann er an manchen Stellen _weniger_ lesbar, aber in Fable 2 optimierter werden), sondern auch die Bundle-Größen zu reduzieren und den generierten Code besser für JS-Optimierungstools geeignet zu machen. Es ist auch eine Frage des Überlebens, da wir jetzt, da wir ausgereiftere und komplexere Bibliotheken wie Fulma haben, wenn die Bundle-Größen zu groß sind, andere Optionen in Betracht ziehen können (und wir wissen, dass der Wettbewerb zwischen funktionalen Sprachen, die nach JS kompilieren, hart ist). Fable versucht, die F#-Sprache mit _die meisten_ ihrer Funktionen und FSharp.Core und _einigen_ der BCL zu kompilieren. Natürlich ist es sehr nützlich, aber ich würde gerne die Alpha-Version von Fable 2 verwenden, um die Kosten / Vorteile zu bewerten und ob wir praktikable Alternativen haben.

@MangelMaxime Ja, das ist die dritte Option, die ich oben in Betracht gezogen habe. Bei Watch-Compilations habe ich einige Zweifel, da dieses Types Modul möglicherweise in einen inkonsistenten Zustand geraten kann. Aber ich bin mir nicht sicher, ich denke, wir können es versuchen.

Nochmals vielen Dank für Ihre Kommentare, sie sind wirklich nützlich und bitte stellen Sie sicher, dass es die geringste meiner Absichten ist, etwas zu tun, das aktuellen Fable-Benutzern schaden könnte. Ich werde versuchen, eine Alpha-Version von Fable 2 zu veröffentlichen, damit es einfacher ist, die Alternativen und ihre Auswirkungen zu vergleichen :+1:

Mein wichtigster Grund für die Wahl von Fable gegenüber Elm war die Möglichkeit, F# auf beiden Seiten zu verwenden und die Typen über die Ebenen hinweg wiederzuverwenden. Ich brauche die Reflexion an sich nicht, aber wie @davidtme sagte, die frühere Implementierung hat "einfach funktioniert". Ich würde gerne einige Aspekte des Typsystems zum Zweck der JSON-Serialisierung aufgeben, aber das gilt für so ziemlich jedes plattformübergreifende Serialisierungsformat, und damit bin ich einverstanden.

Eine in ein Compiler-Plugin extrahierte Serialisierung wäre ein großartiger Kompromiss, wenn Sie es schaffen könnten;)

Gibt es einen anderen Ort, an dem Sie typeof<'T> oder ähnliches verwenden?

Sorry, habe den entsprechenden Abschnitt nicht markiert. https://github.com/intel-hpdd/fable-import-node-powerpack/blob/4004f9c430517c1f26bf47f9c2f766598e500b0d/fable/Stream.fs#L144

Auf diese Weise kann ich eine Stream-Codierung und den Status der lesbaren Seite nur aus den Informationen zur Kompilierzeit festlegen, was es mir erspart, jeden Stream manuell mit den Informationen zu konfigurieren, die bereits von den Typen codiert sind.

Auf dieser Grundlage kann ich beispielsweise Streams zusammenstellen, ohne mir vorher Gedanken über Hardcoding-Optionen machen zu müssen, was ziemlich nett ist: https://github.com/intel-hpdd/fable-import-node-powerpack/blob/4004f9c430517c1f26bf47f9c2f766598e500b0d/test/ Stream.Test.fs#L221 -L232

Eine weitere clevere Reflexionsverwendung hier:
https://github.com/Zaid-Ajaj/Fable.Remoting/blob/master/Fable.Remoting.Client/Proxy.fs#L70

und hier:
https://github.com/Zaid-Ajaj/Fable.Remoting/blob/master/Fable.Remoting.Client/Proxy.fs#L169

Die gesamte Fable.Remoting Projektidee basiert auf der Verfügbarkeit von Reflexion sowohl auf dem Server als auch auf dem Client.

Wahrscheinlich keine beliebte Anfrage - es sei denn, die Kommunikation ist latenz- oder leistungsempfindlich - aber wie wäre es mit der Unterstützung von Rohwerttypen für den Transport (mit oder ohne Zero-Copy-Unterstützung)? Zum Beispiel die Unterstützung von .NET-gepackten und nicht verwalteten ("blittable") Werttypen? Per Definition hat es überall die gleiche Darstellung, sowohl im nativen als auch im .NET. Diese Rohdarstellung könnte kritisch sein, da manchmal die Json-Serialisierung/Deserialisierung auf dem Server einfach zu CPU/memory/gc/latency intensiv ist (insbesondere bei vielen Clients und/oder hoher Nachrichtenrate). Zero-Copy auf dem Client könnte auch unterstützt werden, indem eine ArrayView/TypedArray/DataView-Unterstützung verwendet wird (die Felder werden direkt aus dem/in den Puffer gelesen/geschrieben).
Vielleicht könnte dies auch für natives NodeJS-Interop (mit ffi) verwendet werden.

[<Struct>]
[<StructLayout(LayoutKind.Sequential, Pack=1)>]
type Vector3 =
    val X: int32
    val Y: int32
    val Z: int32
    new(x,y,z) = {
      X=x;
      Y=y;
      Z=z;
    }
    new(dataview: DataView,offset) = {
    //TODO
    }

Danke für deinen Kommentar @zpodlovics. Ich muss sagen, dass ich mit dieser Art der Serialisierung nicht vertraut bin. Sprechen Sie über JSON oder binäre Serialisierung? (Ich habe in einem Projekt ein bisschen mit der binären Serialisierung mit dem Google-Flatbuffers-Protokoll gespielt, aber es war viel Boilerplate-Code enthalten.) Können Sie ein Beispiel dafür geben, wie die serialisierten Daten aussehen würden? Es kann etwas schwierig sein, sowohl in .NET als auch in JS die exakt gleiche Darstellung zu haben, da Fable einige Daten aus den Typen entfernt, um sie im JS-Code und in der Laufzeit zu optimieren. Abgesehen von den Einschränkungen in JS, wie z. B. nicht in der Lage zu sein, Wert-(Struct-)Typen zu definieren.

Wie oben erwähnt, würde ich es außerdem vorziehen, die Serialisierung vom Compiler-Kerncode getrennt zu halten. In Fable 2 werden wir wahrscheinlich eine Art Standard-JSON-Serialisierung beibehalten, wie sie es jetzt für die Benutzerfreundlichkeit gibt, aber kompliziertere Dinge sollten vorzugsweise in einem separaten Paket durchgeführt werden.

@alfonsogarciacaro Danke für den Kommentar!

Nun, es ist nicht wirklich ein Serialisierungsformat, sondern eine direkte Speicherdarstellung, wie sie in einer nativen C-Struktur dargestellt würde. Das "serialisierte" Format würde genau so aussehen wie acstruct (native) [1]. Dies ist auch für natives Interop erforderlich, da es dieselbe Speicherdarstellung wie die native Anwendung verwendet. Das frühere Beispiel verwendet 3 * 4 = 12 Byte Speicherbereich. Ein Struct könnte als Objekt mit einer Methode "emuliert" werden, die den Hintergrundspeicherbereich (.Wrap) ersetzt.

Die frühere Struktur könnte mithilfe der JS DataView API (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt32) in so etwas wie diesen Pseudocode übersetzt werden:

z.B.:

type Vector3 =
    val mutable view: DataView
    val mutable offset: int32
    static member XOffset=0
    static member YOffset=4
    static member ZOffset=8
    new(v: DataView,o:int32) = {
      view=v;
      offset=o;
    }
    member __.Wrap(v: DataView,o:int32) = {
      view=v;
      offset=o;
    }
    member __.X
        with get() = __.view.getInt32(__.offset+Vector3.XOffset)
        and set(v) = __.view.setInt32(__.offset+Vector3.XOffset,v)
    member __.Y
        with get() = __.view.getInt32(__.offset+Vector3.YOffset)
        and set(v) = __.view.setInt32(__.offset+Vector3.YOffset,v)
    member __.Z
        with get() = __.view.getInt32(__.offset+Vector3.ZOffset)
        and set(v) = __.view.setInt32(__.offset+Vector3.ZOffset,v)       

Ja, dies könnte manuell erstellt werden, aber es wäre zeitaufwändig und fehleranfällig, dies manuell zu tun. Vor allem, wenn der Compiler bereits alle benötigten Informationen hat: die Typen, das Speicherlayout, die Feldoffsets usw. Eine Art Compiler-Erweiterungsplugin/api/ast wäre auch mit benutzerdefiniertem Codegen vollkommen in Ordnung. Irgendwann wird jeder mit dem Binärproblem konfrontiert - wie muss man Binärdateien lesen/schreiben zB: Bild/Audio/Video/Dokument/Netzwerkpakete/etc.

Die eingebaute Raw-Memory-Darstellung als Serialisierungsformat für Interop (um ffi zu unterstützen) könnte für jeden einen unglaublichen Wert haben. Auf jeder Plattform haben die Entwickler zwei Möglichkeiten, die Plattform zu manipulieren: 1) den Interop-Code als plattformnativen Code schreiben (Plattform-Compiler, andere Toolchain, andere Build-/Test-/Deploy-Prozesse usw.) 2) Code schreiben, der Rohspeicher für Interop darstellt + ff. Es ist kein Zufall, dass .NET über eine integrierte Pinvoke-Unterstützung verfügt.

C:
[1] https://en.wikipedia.org/wiki/Struct_ (C_programming_language)
.NETZ
[2] https://www.developerfusion.com/article/84519/mastering-structs-in-c/
JS
[3] https://github.com/TooTallNate/ref-struct

Vielen Dank für die ausführlichere Erklärung @zpodlovics! Ich habe mich in der Vergangenheit mit Datenansichten beschäftigt, um Strukturen zu emulieren, aber ich habe nicht viel getan, weil es immer noch Einschränkungen gibt (z ). Außerdem ist es mit Fable bereits möglich, den Speicher mit numerischen Arrays besser zu steuern, da diese in typisierte Arrays übersetzt werden, aber dies hat bei den Benutzern bisher nicht viel Anklang gefunden.

Wir sollten dafür wahrscheinlich eine neue Ausgabe aufmachen, da es, wie Sie sagten, nicht direkt mit der Serialisierung zusammenhängt. Ich kann das derzeit nicht als Priorität betrachten, aber es wäre sehr schön, diesbezüglich Beiträge zu sehen. Die Plugin-Story für Fable 2 ist noch offen, aber wir könnten es so machen, dass es so etwas ermöglicht, wenn Sie daran interessiert sind, in einem solchen Plugin zu arbeiten.

Zu beachten ist jedoch, dass die WebAssembly-Unterstützung in .NET/F# eingeführt wird und die Speicherdarstellung in diesem Fall näher am .NET-Modell liegt. In diesem Fall bleibt Fable möglicherweise eine Möglichkeit, F# näher in das JS-Ökosystem zu integrieren (wie es jetzt ist) und die aktuellen Tools/Bibliotheken zu nutzen, die zum Erstellen des Frontends Ihrer Apps verfügbar sind.

@alfonsogarciacaro Können wir dieses Problem schließen?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen