# Svelte 04.04.2024 ## Einleitung Um Svelte als Web Framework zu testen, wurde eine ToDo-Listen-Management App geschaffen. Die Svelte Anwendung verwendet eine REST API, um ToDo-Listen abzurufen, anzulegen und zu verändern. Im Ersten Schritt soll sich nur auf die Svelte Anwendung konzentriert werden, die REST API soll vorerst als gegebne betrachtet werden. ## Entwicklungs-Setup ### Vorbedingungen #### Backend ##### Docker [Installation per `apt` Package Manager](https://docs.docker.com/engine/install/ubuntu/) (Advanced Packaging-Tool) 1. alte Pakete deinstallieren `for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done` 2. Docker apt Repository registrieren ``` # Add Docker's official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # Add the repository to Apt sources: echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update ``` 3. Docker installieren `sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin` 4. (optional) Installation prüfen durch Ausführen des Hello-World images `sudo docker run hello-world` #### Frontend ##### yarn 1. [Node.js installieren](https://github.com/nodesource/distributions) (per `apt` Package Manager) (getestet unter Ubuntu, siehe [hier](https://github.com/nodesource/distributions?tab=readme-ov-file#table-of-contents) für andere Linux Distributionen) ``` curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - &&\ sudo apt-get install -y nodejs ``` 2. Yarn Feature Flag aktivieren: ``` corepack enable ``` 3. Prüfen, ob die Installation erfolgreich war: 1. `yarn --version` ausführen => Version sollte `1.x.x` sein 2. zu `/todo-app/packages/todo-app/` navigieren 3. `yarn --version` ausführen => Version sollte `4.x.x` sein ### Projekt initialisieren und Abhängigkeiten installieren 1. zu `/todo-app/` navigieren 2. ``` yarn ``` ausführen ### Starten der Anwendung **Der Stand der reinen Svelte Anwendung mit .NET Backend befindet sich auf dem Git branch `svelte-dotnet`!** 1. zu `/todo-app/` navigieren 2. ``` yarn be:docker:create-image ``` ausführen 3. ``` yarn be:docker:run ``` ausführen 4. ``` yarn fe:build ``` ausführen 5. ``` yarn fe:run ``` ausführen 6. Anwendung öffnen [http://localhost:4173/](http://localhost:5173/) Das .NET Backend kann mit `yarn be:docker:stop` wieder gestoppt werden. ### Erläuterungen - `yarn` (alias für `yarn install`) installiert alle npm Pakete, die als Abhängigkeiten der Svelte Anwendung gelistet sind. Außerdem wird das Projekt initialisiert, sodass yarn scripte ausgeführt werden können. - `yarn be:docker:create-image` baut die .NET API in einem Docker Container und erstellt ein lokales Docker Image `dva/todo-api` - `yarn be:docker:run` verwendet Docker compose, um - einen `todo-api` Container zu starten - einen `postgres` Container zu starten - `yarn fe:build` baut die Svelte Anwendung - `yarn fe:run` startet die Svelte Anwendung ## Grundlegende Eigenschaften von Svelte Svelte ist ein freies modernes JavaScript-Framework, welches Entwicklern ermöglicht, deklarative Single-Page-Webanwendungen zu erstellen. Im Gegensatz zu Frameworks wie React oder Vue.js werden bei Svelte-Applikationen keine frameworkspezifischen Module zurückgegeben. Der Code wird zur Übersetzungszeit vom Svelte-Compiler in JavaScript-Code übersetzt. Damit ist er von keinen externen Programmbibliotheken abhängig. ### Erstellen eines Svelte Projekts Ein neues Svelte Projekt kann mit folgendem Command erzeugt werden: ``` yarn create vite --template svelte-ts ``` wobei `my-svelte-app` der Name für die zu erzeugende Anwendung ist. ### Aufbau einer Svelte-Komponente Im folgenden Codebeispiel sieht man den grundlegenden Aufbau einer Svelte-Komponente. Diese besteht meist aus 3 Teilen: * JavaScript (``) * Markup * Styling (``) Im JavaScript-Teil können unter anderem Component Properties definiert werden (export let foo). Diese Properties können beim Verwenden der folgenden FooBar-Komponente wie folgt festgelegt werden: `` In diesem Fall wäre innerhalb der Komponente foo auf den Wert "bar" gesetzt. Außerdem wird der Großteil der Logik und des Data Fetchings im Script-Teil erledigt. Hier können Variablen definiert werden, die im Markup-Teil verwendet werden können. Auf die Möglichkeiten des Templatings innerhalb des Markup-Teils wird später noch genauer eingegangen. Im Script-Teil kann neben JavaScript auch TypeScript verwendet werden, indem `` verwendet wird. Um die Komponente zu stylen, kann innerhalb des Style-Tags auf CSS zurückgegriffen werden. Styles, die hier definiert werden, sind standardmäßig gescoped auf die aktuelle Komponente. Dadurch wird verhindert, dass ungewollt Styles für die gesamte Anwendung anstatt für eine einzelne Komponente gesetzt werden. ``` ``` ### SPA [Quellen](#quellen) Single Page Applications zeichnen sich dadurch aus, dass die Navigation der Seiten durch JavaScript APIs erfolgt und dadurch beim Navigieren die Seite nicht durch einen Full Refresh neugeladen werden muss. Stattdessen wird der Inhalt der Seite dynamisch ersetzt. Beim ersten Laden der Seite muss bei einer SPA die komplette Payload der Seite inklusive aller Unterseiten an den Client geschickt werden, daher ist diese im Regelfall größer als bei regulären Webanwendungen, bei denen der Server für jede Route eine eigene HTML-Datei ausliefert. Der Vorteil der SPA ist hier aber, dass dadurch beim Navigieren durch die Seiten meist geringere Datenmengen angefragt werden müssen, da der Seitenaufbau bereits beim initialen Laden vorhanden ist und dadurch die Serverlast durch das reine Navigieren reduziert ist. Für den Benutzer fühlen sich SPAs meist schneller & dynamischer an. ### Template-Syntax Svelte ist komponentenbasiert. Daraus folgt, dass man beim Erstellen der Anwendung mehrere modulare Bausteine erstellt, die durch verschiedenste Mechanismen miteinander kommunizieren können. In diesen Svelte-Komponenten, die jeweils in einer .svelte-Datei angelegt werden, steht die Template Syntax von Svelte zur Verfügung. Diese erweiterte HTML-Syntax macht es dem Entwickler leicht, auch logische Entscheidungen im HTML Markup abzubilden. * `Each` Mithilfe von each können durch Arrays oder Objekte iteriert werden und Markup für die einzelnen Elemente erzeugt werden. ``` {#each todoLists as todoList}
  • {/each} ``` * `If-Else` Mithilfe von if/else/else if kann der Kontrollfluss kontrolliert werden und abhängig von booleschen Werten jeweils unterschiedliches Markup gerendert werden. ``` {#if editing}
    {:else}

    {name}

    {#if description}

    {description}

    {/if}
    {/if} ``` * `Await` await macht es möglich, die 3 verschiedenen States (pending, fulfilled, rejected) eines Promises innerhalb des Markups abzubilden. Im folgenden Codebeispiel wird für den Status pending ein Loading-Spinner angezeigt. Sobald das Promise in den Zustand fulfilled übergegangen ist, haben wir Daten, mit denen wir arbeiten können (in diesem Fall todoLists) und können das UI bauen, welches die Navigation und die Todo-Items enthält. Falls beim Laden der Daten ein Fehler auftreten sollte, geht das Promise in den Zustand rejected über und wir zeigen im catch-Block des folgenden Markups die jeweilige Fehlermeldung in rot an. ``` {#await todoListsPromise} {:then todoLists}
    {#if todoLists.length > 0} {/if}
    {:catch error}

    {error}

    {/await} ``` ### State Management [Quellen](#quellen) Um in Svelte reaktive Variablen zu erstellen, reicht es aus, eine einfache Variable mit `let var = "value"` zu definieren. Sobald diese geändert wird (im nachfolgenden Beispiel sobald die Funktion startEditing() aufgerufen wird), wird ein Rerender getriggert und Svelte updatet das UI. Da im folgenden Beispiel ein if-Branch erstellt wurde, der den Wert von editing prüft, wird nach der Änderung der Variable auch der jeweils andere Block angezeigt. ``` let editing = false; function startEditing() { editing = true; } {#if editing} {:else} {/if} ``` Außerdem können in Svelte Reactive Statements erstellt werden. Diese Ausdrücke reagieren auf Änderungen von Variablen innerhalb des Ausdrucks. Im folgenden Beispiel verändert sich der Wert der reaktiven Variable formIsValid sobald sich der Wert der referenzierten Variable name verändert. ``` $: formIsValid = name.length > 0; ``` ### Event Handling In Svelte können Komponenten Events erzeugen, die beim jeweiligen Parent ankommen. Diese Events können neben einem Namen des Events auch einen Payload mitliefern. Im folgenden Beispiel wird ein Todo-Item erzeugt, wobei der Server mit der neuen Liste an Todos antwortet. Diese neue Liste (gespeichert in response) wird mithilfe des erstellten Event Dispatchers dispatch im Event Payload an den Parent der aktuellen Komponente geschickt. Der Parent bindet die Komponente ein (NewTodoForm) und kann festlegen, was beim Empfangen des Events passiert. In unserem Fall geben wir mit der Syntax on:updateData das Event eine Ebene weiter nach oben. Auf dieser Ebene wird das Event dann gefangen und ein Handler wird ausgeführt (hier handleDataUpdate). ``` const dispatch = createEventDispatcher<{ updateData: ToDoListDto }>(); const response = await todoApi.putToDoItem(selectedListId, uuid, { name, description, done: false, }); dispatch('updateData', response) ``` ``` ``` ``` ``` Event-Handler handleDataUpdate: Der Event-Handler hat Zugriff auf das empfangene Event und hat in der Property event Zugriff auf die gesendeten Event-Daten. ``` function handleDataUpdate(event: CustomEvent) { const updatedList = event.detail; todosInSelectedList = updatedList.items; } ``` ## Verwendung in der ToDo-App Aufbauend auf unserer bereits bestehenden Svelte ToDo-App haben wir Capacitor in die Anwendung eingebunden. Dadurch, dass Capacitor auf der bestehenden Anwendung aufsetzt, konnten wir bestehende Funktionen wiederverwenden. Um Capacitor auszutesten, haben wir für die einzelnen Todo Cards eine Action Sheet Component eingebaut: ```js const showActionSheet = async () => { const actionSheetOptions = [ { title: 'Share', onTap: async () => { const descriptionText = description ? `(${description})` : ''; void Share.share({ text: `${name} ${descriptionText} - Shared from ToDo App` }); } }, { title: 'Edit', onTap: () => startEditing() }, { title: 'Remove', style: ActionSheetButtonStyle.Destructive, onTap: () => deleteItem() }, { title: 'Cancel', style: ActionSheetButtonStyle.Destructive, onTap: () => { } } ] const result = await ActionSheet.showActions({ title: 'Quick Actions', message: 'Select an action to perform', options: actionSheetOptions }); actionSheetOptions[result.index].onTap(); }; ``` Im eigens implementierten onTap-Callback wird jeweils die entsprechende Aktion ausgeführt, die auf bereits bestehende Funktionen zurückgreift. Wie man sieht, ist es sehr einfach, native Features in einer bestehenden Anwendung zu integrieren. Die Funktionalitäten werden dafür jeweils aus eigenen Packages importiert: ```js import {ActionSheet, ActionSheetButtonStyle} from '@capacitor/action-sheet'; import {Share} from '@capacitor/share'; ``` ### Vorteile von Svelte [Quellen](#quellen) * Svelte kompiliert die Anwendung zur Erstellungszeit zu optimalem JavaScript Code anstatt den Code zur Laufzeit zu interpretieren. Dadurch wird das erstmalige Laden der Seite beschleunigt, da keine Zusatzkosten durch die Abstraktion des Frameworks entstehen. Außerdem fällt dadurch das Virtuelle DOM weg, wodurch ebenfalls die Ladezeiten verringert werden und die Anwendung flüssiger läuft. * Die potenziell geringere Bundle-Größe kann ebenfalls für geringere Ladezeiten sorgen. * Svelte benötigt weniger Boilerplate Code als ähnliche Frameworks. * In Svelte wird kein zusätzlicher Code benötigt um eine Variable reaktiv zu machen. Jede Variable ist standardmäßig reaktiv. * Svelte ist durch die simple Syntax leicht zu lernen und verstehen. Beispiel für reaktive Variable in Svelte: ``` ``` Beispiel für reaktive Variable in React: ``` import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); function increment() { setCount(count + 1); } return ( ); } ``` ## Fazit Svelte ist ein relativ neues JavaScript Framework, welches sich durch eine einfache Syntax, kaum Boilerplate Code und eine hohe Performance von anderen JavaScript Frameworks absetzt. Svelte wird bereits in großen Unternehmen produktiv eingesetzt und wird stets weiterentwickelt. Zwar ist das Ökosystem rund um Svelte zur Zeit nicht so ausgereift, wie bei anderen Frameworks, allerdings ist die Popularität des Frameworks sehr hoch, wodurch anzunehmen ist, dass sich das Ökosystem rasant ausweiten wird. ## Quellen - SPA - [MDN](https://developer.mozilla.org/en-US/docs/Glossary/SPA) - [Hubspot](https://blog.hubspot.de/website/single-page-application) - [State Management](https://www.codecentric.de/wissens-hub/blog/state-management-in-svelte) - [Vorteile](https://kinsta.com/de/blog/svelte-vs-react/)