Weboberflächenentwicklung mit Elm

Sollen sich Clientanwendungen für verteilte Applikation sicher und ohne Laufzeitfehler ausführen lassen, bietet sich die funktionale Programmiersprache Elm an. Eine Beispielanwendung zeigt ihr serverseitiges Zusammenspiel mit Elixir und dem Webserver Phoenix.

Sprachen  –  50 Kommentare
Weboberflächenentwicklung mit Elm

(Bild: Shutterstock)

Der Artikel "Skalierbare, robuste Webanwendungen mit Elixir und Phoenix" beschreibt, wie sich mit der funktionalen, auf Erlang-VM basierenden Programmiersprache Elixir und dem zugehörigen Webserver-Framework Phoenix der serverseitige Teil eines kleinen Spiels schreiben lässt, bei dem Spieler eine Zahl zwischen 1 und 100 erraten müssen. Dabei erhält jeder Spieler eine eigene Spielsession in einem eigenen Prozess, die unabhängig von allen anderen Sessions läuft. Phoenix sorgt dafür, dass sich die Serverschnittstellen via HTTP ansprechen lassen.

Nachfolgend wird nun beschrieben, wie sich in der Programmiersprache Elm eine Clientanwendung entwickeln lässt, die die angebotenen APIs nutzt, um Spielern sämtliche Funktionen auf einer Weboberfläche zur Verfügung zu stellen.

Funktional programmieren in Elm

Elm ist eine funktionale Programmiersprache zum Erstellen von Web-Frontends, die sich zu JavaScript transpilieren lässt. Die Sprachsyntax ist an Haskell angelehnt, der Sprachumfang aber deutlich geringer. Dadurch lässt sich Elm leichter erlernen und eignet sich auch gut als Einstieg in die funktionale Programmierung. Elm besitzt ein ausdrucksstarkes Typsystem, einen hilfreichen Compiler und eine unidirektionale Datenarchitektur ähnlich wie React Redux.

Die unidirektionale Architektur von Elm (Abb. 1)

Die Daten im Modell beschreiben den aktuellen Zustand der Anwendung, der sich mit der view-Funktion darstellen lässt. Sie löst auch Messages aus, die wiederum zusammen mit dem aktuellen Zustand in der Update-Funktion verarbeitet werden. Dadurch entsteht ein neuer, veränderter Zustand und es kommt gegebenenfalls zum Auslösen von Seiteneffekten.

In Elm werden alle Seiteneffekte als Daten beschrieben und dann an die Runtime übergeben, die sie ausführt und das Ergebnis in Form einer Message inklusive eventueller Daten wiederum an die Update-Funktion übergibt. Da alles entweder Daten oder Funktionen sind, lassen sich an jeder Stelle alle Möglichkeiten der Sprache nutzen. Der Compiler hat Zugriff auf alle Ebenen der Anwendung. Viele Fehler lassen sich dadurch ganz vermeiden oder zumindest frühzeitig entdecken. Zum Entwickeln mit Elm empfiehlt sich beispielsweise Visual Studio Code mit der elm-Extension.

Einbinden in Phoenix

Zum Einbinden von Elm in das Phoenix-Framework sind mehrere Schritte erforderlich. Zuerst muss webpack, das seit Phoenix 1.4 bevorzugte Web Asset Buildtool, um den entsprechenden Loader erweitert werden. Dies geschieht im assets-Unterordner mit cd assets && npm install --save-dev elm-webpack-loader. Als Nächstes ist dort die webpack.config.js anzupassen:

entry: {
'app': ['./js/app.js'].concat(glob.sync('./vendor/**/*.js')),
'elm': ['./js/elm.js']
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, '../priv/static/js')
},
module: {
rules: [
...
{
test: /\.elm$/,
exclude: [/elm-stuff/, /node_modules/],
use: {
loader: 'elm-webpack-loader',
options: {
debug: options.mode === "development"
}
}
}
]
}
...

Dann sollte in package.json noch der elm-Befehl als Skript hinterlegt werden, um später einfach npm run elm ausführen zu können:

"scripts": {
...
"elm": "elm"
}

Anschließend muss in /assets/js/ eine Datei namens elm.js angelegt werden, die den kompilierten Elm-Code lädt und das Elm-Programm an ein DOM-Element in der Website bindet:

import { Elm } from "../src/Main.elm";

var app = Elm.Main.init({
node: document.getElementById('elm-main')
});

Im letzten Schritt vor der eigentlichen Einbindung in Phoenix ist noch im assets-Ordner ein Elm-Projekt via npm run elm init zu initialisieren und im neu erzeugten src-Unterordner eine Main.elm-Datei anzulegen, die das spätere Programm enthalten wird. Zunächst reicht hier ein Platzhalter, um zu prüfen, ob die Integration überhaupt funktioniert:

module Main exposing (main)

import Html exposing (text)

main =
text "Elm is working!"

Damit der Elm-Code später auch ausgeführt wird, muss die entsprechende .js-Datei in einem Phoenix-Template angegeben sein. Üblicherweise befindet sich das grundlegende Seitengerüst in der Datei /lib/game_web/templates/layout/app.html.eex. Dateien mit der .eex-Endung sind Phoenix-Templates. In Templates lassen sich HTML und Elixir-Code beliebig kombinieren. Um die spätere Auslieferung zur Laufzeit zu beschleunigen, werden Templates vom Elixir-Compiler so weit wie möglich vorkompiliert. Im Beispielprojekt kann das Grundgerüst unverändert bleiben, aber der Inhalt der Index-Seite in /lib/game_web/templates/page/index.html.eex ist so anzupassen, dass das Zielelement für Elm vorhanden ist und elm.js geladen wird:

<div id="elm-main"></div>

<script type="text/javascript" src="<%= Routes.static_url(@conn, "/js/elm.js") %>"></script>

Text zwischen <%= und %> ist Elixir-Code – hier der Routes-Helper von Phoenix, der eine passende URL für die JavaScript-Datei erzeugt und ins Template einfügt. Anpassungen an der view der Startseite sind nicht erforderlich. Sie dient normalerweise dazu, die Daten aus dem Datenmodell für die Darstellung im Template aufzubereiten. Das ist hier aber nicht nötig.

Beim Aufrufen der Startseite der Applikation unter http://localhost:4000/ sollte dort unterhalb des Phoenix-Headers der Text ″Elm is working!″ erscheinen. Sonst muss gegebenenfalls der Server einmal via Strg+C beendet und mit mix phx.server neu gestartet werden.