Karriere
Wissen
Über uns
31. Okt 2024
Full-Stack und Cross-Platform im Fokus
Webframeworks sind immer noch "a dime a dozen", deshalb schauen wir uns in diesem Seminar die wichtigsten Single Page Application (SPA) Frameworks an und vergleichen sie. Wir finden heraus, was die jüngsten Entwicklungen in diesen Bereichen sind und welche Vor- und Nachteile die jeweiligen Frameworks mit sich bringen.
Auch wenn der Hype um Mobileapplikationen seit 2016 etwas abgeflacht ist, sind sie immer noch ein integraler Bestandteil unserer IT-Landschaft. Wir schauen uns deshalb die Gemeinsamkeiten zu Webapplikationen an und welche Möglichkeiten es gibt, um Synergien der beiden Technologien zu nutzen.
Wenn wir die Downloads der einzelnen Frameworks in NPM anschauen, sehen wir, dass React das mit Abstand beliebteste Framework zu sein scheint, gefolgt von Vue, welches Angular überholt hat.
Diese Zahlen dürfen aber nicht als absolut betrachtet werden, da zum Beispiel Angular sehr viel Anwendung in grossen Firmen findet, die initial einmal den Release in ihre eigene Repository ziehen, und jeder weitere Entwickler dann von dieser. Weswegen diese nicht in der Statistik auftauchen.
Eine etwas bessere Approximation ist es, Joblistings anzuschauen. Hier sehen wir, dass alle drei sehr beliebt sind und dass die Schweiz ein Spezialfall in Europa zu sein scheint mit einer sehr grossen Anzahl an Angular-Stellen.
Ebenfalls wichtig zu beachten ist, dass keines am Sterben ist und alle immer noch stetig wachsen.
Wenn wir Vue, React und Angular auf die Hauptmerkmale herunterbrechen und vergleichen, stellen wir fest, dass:
Wir könnten jetzt noch vergleichen, welches Framework die schnellste Renderzeit hat, aber in der Praxis spielt das für praktisch alle Anwendungen keine Rolle, da alle mehr als schnell genug sind.
Mit diesen Erkenntnissen kann die These aufgestellt werden, dass alle aktuellen Frameworks zu konvergieren scheinen, da alle das gleiche Problem zu lösen versuchten und dieses gelöst zu sein scheint. Dies führt dazu, dass ein "idealer" Ansatz gefunden wird, den alle umsetzen.
Echte Innovation in diesem Bereich findet also nicht mehr statt. Klar gibt es noch einzelne "Innovationen" wie Signals, welche aber die User Experience nicht wirklich massgeblich verbessern, sondern einfach eine bessere Lösung für ein bereits ausreichend gelöstes Problem darstellen.
Indikativ dafür ist ebenfalls, dass obwohl immer mehr neue Frameworks erscheinen, diese keine nennenswerte Adaption in der Industrie finden. Weder der Leidensdruck noch die Verbesserungen scheinen ausreichend gross zu sein.
Die Entscheidung, welches nun das beste Framework ist, scheint also keine rein technische Entscheidung zu sein und von anderen Faktoren abhängig zu sein, wie etwa:
Vite ist ein modernes Build-Tool und Entwicklungsserver. Es wurde von Evan You, dem Schöpfer von Vue.js, entwickelt, als er versuchte, den Entwicklungsprozess für Vue.js zu verbessern. Ihm ist dann aufgefallen, dass es mit wenigen kleinen Änderungen sehr generisch sein kann und für andere Frameworks ebenfalls verwendet werden kann. Dies hat er auch getan, und seitdem verwenden praktisch alle Frameworks Vite oder bieten die Möglichkeit an.
Angular bietet dies aber erst seit dem 23.10.2024 an, integriert das Build-Tooling jedoch noch nicht vollständig.
Anstelle das ganze JS gebündelt in einem grossen Script auszuliefern, setzt Vite auf einen unbundled workflow. Das heisst, es werden nur die nötigen Skripte geladen und ausgeliefert. Das Ganze wird in Echtzeit transformiert und granular ausgeliefert.
Dies ermöglicht, dass der Testserver immer sehr schnell starten kann, egal wie gross das Projekt ist. In der Produktion wird jedoch noch immer ein gebündeltes JS ausgeliefert.
Signal ist ein Wrapper um einen State. Wann immer man mit diesem interagieren möchte, muss dies über die Wrappermethoden geschehen. Dies ermöglicht es, eine Änderung verlässlich zu erkennen und die Objekte, die im Observer-Pattern auf den State dieses Objekts zugreifen, über die Änderung benachrichtigt zu werden.
Dies ist ein Vorteil, da hier genau bekannt ist, welche Komponenten geupdated werden müssen und nicht ein ganzer Ast des DOMs neu gescannt werden muss, wie es aktuell in Angular und anderen Frameworks der Fall ist.
Ein Beispiel an React
# old const counter = 0 counter++ # Signal const counter = signal(0) this.count.set(this.count() + 1)
Cross-Platform-Entwicklung (CP) ermöglicht es, eine Anwendung einmal zu entwickeln und auf mehreren Plattformen wie Web, Android und iOS zu veröffentlichen. Der Vorteil: weniger Entwicklungsaufwand, kürzere Zeit bis zur Marktreife und geringere Kosten.
Heute nutzen bereits viele Apps Cross-Platform-Technologien – ein Zeichen, dass die Vorteile überwiegen. Herausforderungen wie Performance-Unterschiede, plattformspezifische Einschränkungen und ein nicht ganz nativer Look-and-Feel sind bei modernen Frameworks wie Flutter und React Native meist nur noch geringfügig:
Insgesamt zeigt sich, dass CP einen effizienten Weg darstellt, um Apps schnell und mit minimalem Mehraufwand auf mehreren Plattformen zu veröffentlichen.
Die grössten X-Platform-Frameworks sind Flutter und React Native, wobei wir uns hier Flutter etwas genauer anschauen.
Flutter ist ein von Google geschriebenes Framework, das bereits 2017 erschien. Es zeichnet sich durch folgende Punkte aus:
Seine Stärken leben sich vor allem auf Mobile-Plattformen aus. Toyota setzt Flutter bereits in ihren Autos ein.
Flutter setzt auf einen modernen, deklarativen UI-Ansatz, der ohne HTML-Dateien und Stylesheets auskommt. Das gesamte User Interface wird direkt im Code definiert, was eine einheitliche Gestaltung und Kontrolle ermöglicht. Flutter ist ein Vorreiter dieses Ansatzes und bietet eine plattformübergreifende API für die meisten Funktionen. Plattform-spezifische Services können dennoch bei Bedarf über sogenannte Platform Channels eingebunden werden, um gezielt auf native Funktionen zuzugreifen.
Hier ein kleines Codebeispiel aus der Flutter-Dokumentation bezüglich Widgets mit dem Code-First-Ansatz:
Widget build(BuildContext context) { // Scaffold is a layout for // the major Material Components. return Scaffold( appBar: AppBar( leading: const IconButton( icon: Icon(Icons.menu), tooltip: 'Navigation menu', onPressed: null, ), title: const Text('Example title'), actions: const [ IconButton( icon: Icon(Icons.search), tooltip: 'Search', onPressed: null, ), ], ), // body is the majority of the screen. body: const Center( child: Text('Hello, world!'), ), floatingActionButton: const FloatingActionButton( tooltip: 'Add', // used by assistive technologies onPressed: null, child: Icon(Icons.add), ), ); }
Hybride Apps sind Browseranwendungen, verpackt in einem nativen Gewand. Flutter zum Beispiel ist ein Framework, um hybride Apps zu schreiben, denn es verwendet nicht-native UI-Elemente, sondern rendert “Kopien” von nativen Elementen auf seinem eigenen Canvas, im Gegensatz zu Kotlin Multiplatform, welches native Elemente erzeugt, z. B. einen Android-Button oder einen iOS-Button.
Dieser Ansatz gibt den Entwicklern viel Kontrolle über das Layout und ermöglicht ihnen, einen einheitlichen Look and Feel über mehrere Plattformen zu erzeugen. Ein weiterer Vorteil ist, dass, wenn die browserbasierte Nutzung im Fokus steht, die Webperformance sehr hoch ist. Ebenfalls lässt sich von bestehender Webentwicklungsexpertise im Team profitieren, da dies sehr gut im hybriden Ansatz angewendet werden kann. Mit Code Push werden sehr schnell Releases erzeugt, da diese oft nicht über Play- oder App-Store-Freigaben gehen müssen.
Typischerweise sind SPAs client-seitiges Rendering, wozu haufenweise API-Calls notwendig sind, bevor etwas gerendert werden kann. Dies führt dazu, dass die erste Ausführung, der initiale Renderschritt, wenn die Webseite aufgerufen wird, länger dauert, als wenn sie serverseitig gerendert worden wäre.
Ein weiterer Nachteil ist, dass der Webcrawler beim initialen Besuch fast nichts im initialen HTML sieht und so der Inhalt nicht indexiert werden kann. (Yay, SEO is still alive)
Hieraus könnte geschlossen werden, dass man doch besser wieder auf serverseitiges Rendering (SSR) umstellen sollte, was auch die meisten Frameworks unterstützen. Hier wird mit dem initialen Call die Page serverseitig gerendert und ausgeliefert, der Client zeigt diese an und baut im Hintergrund die Seite komplett neu und tauscht das DOM dann aus. Nachteil ist hier, dass das Ganze doppelt gerendert wird und ein kurzes Flackern auftaucht.
Meta-Frameworks sind Frameworks, die auf bestehenden Frameworks wie React aufbauen. Ein bekanntes Beispiel dafür ist Remix.
Durch Streaming Server Rendering wird HTML stückweise an den Client gesendet, ohne dass viele kleine Anfragen nötig sind. Bei der Island Architecture wird nur das notwendige JavaScript zur richtigen Zeit (zum Beispiel bei Mouse Hover) an den Client gesendet. So vermeiden wir, dass das gesamte DOM mehrmals gerendert werden muss, und bereits beim ersten Aufruf sind Informationen für Suchmaschinen vorhanden.
Mit diesen Änderungen haben wir praktisch das gesamte Frontend-Framework auf unserem Server eingebunden. Es stellt sich die Frage, ob die traditionelle Trennung von Client und Server in diesem Kontext noch sinnvoll ist.
Ein alternativer Ansatz ist der von Remix als "Fullstack Data Flow" bezeichnete:
Ein filebasiertes Routing besteht aus einem Routenmodul, das eine Loader-Komponente und eine Action-Funktion bereitstellt. Router-Dateien exportieren eine Loader-Funktion, die die Daten für die Komponente in dieser Route bereitstellt. Die Daten werden von der Komponente per useLoaderData geladen, gerendert und ausgeliefert. Die Action-Methode der Route wird ausgeführt, wenn das Formular übermittelt wird.
In diesem Ansatz wird die klassische API durch ein Routenmodul (Datei) ersetzt, und die einzelnen Calls werden in der Loader-Methode abgewickelt. Hier ein Codebeispiel:
import type { LoaderFunctionArgs } from "@remix-run/node"; // or cloudflare/deno import { json } from "@remix-run/node"; // or cloudflare/deno import { useLoaderData, Form } from "@remix-run/react"; export async function loader({ request, }: LoaderFunctionArgs) { const user = await getUser(request); return json({ displayName: user.displayName, email: user.email, }); } export default function Component() { const user = useLoaderData<typeof loader>(); return ( <Form method="post" action="/account"> <h1>Settings for {user.displayName}</h1> <input name="displayName" defaultValue={user.displayName} /> <input name="email" defaultValue={user.email} /> <button type="submit">Save</button> </Form> ); } export async function action() { // ... }
https://remix.run/docs/en/main/discussion/data-flow
React Server Components bieten eine ähnliche Lösung. Sie sind in der Lage, serverseitig zu rendern, jedoch mit der Besonderheit, dass sie den React Tree auf dem Server generieren und nur die Renderanweisungen an den Client senden.
Dies ermöglicht es, den Rendering-Prozess zu optimieren, indem Serverressourcen genutzt werden. Zudem wird paralleles Data Fetching ermöglicht, sodass bereits während des Streamings ein brauchbares Ergebnis an den Client gesendet werden kann.
Es scheint, dass in naher Zukunft ein Trend zurück zum Server zu beobachten ist, insbesondere für Anwendungen, die keinen enormen Umfang oder hohe Skalierungsanforderungen haben. Bei diesen Anwendungen ist eine starke Trennung von Frontend und Backend mittels API eher eine "accidental complexity".
Danke für Ihr Interesse an Cudos. Ihre Angaben werden vertraulich behandelt – den Newsletter können Sie jederzeit abbestellen.