Electron ist ein Open-Source-Projekt zum Erstellen von Desktop-Anwendungen mit JavaScript, HTML und CSS. Statt mehrere Applikationen für unterschiedliche Plattformen zu pflegen, kann Electron eine einzige Anwendung für Mac, Linux und Windows kompilieren. Es funktioniert mit vielen verschiedenen Frameworks und Bibliotheken, wie z.B. React, Angular und Vue.js. Dieser Artikel behandelt keine Integration dieser Frameworks, sondern vermittelt einen Einstieg in den Aufbau und die Komponenten von Electron, sowie einige plattformspezifische Beispiele.
Electron erfreut sich sehr großer Beliebtheit. Es wird unter anderem von Visual Studio Code, Facebook Messenger, Twitch, Slack und Discord genutzt, um nur die bekanntesten Anwendungen zu nennen. Durch die monatlichen Releases stehen stetig wachsende Funktionalitäten zur Verfügung.
Schnellstart: Projekt-Setup
Es gibt verschiedene Möglichkeiten, eine Grundstruktur für ein Electron-Projekt zu erstellen. In diesem Tutorial wird das Setup von Electron Forge verwendet. Mit dem Konsolenbefehl
npx create-electron-app werden alle Dateien angelegt, um direkt eine kleine Beispielanwendung starten zu können. Diese Applikation kann mit npm start oder electron . gestartet werden. Dadurch laufen das Backend und ein Browserfenster als eigenständige Applikation.
Der Kern des Setups liegt in der index.js. Hier wird das Grundgerüst für die Electron-App erstellt. Um mit Electron zu arbeiten, ist es wichtig zu wissen, dass die Applikation aus zwei Prozessen besteht.
Es gibt einen Hauptprozess, basierend auf Node.js, und beliebig viele Renderprozesse, die Chromium verwenden.
Für den Renderprozess stehen die Chrome DevTools zur Verfügung. Diese können während der Entwicklung beim Starten der Applikation direkt mit geöffnet werden.
mainWindow.webContents.openDevTools();
Für den Hauptprozess, also das Node-Backend, kann die Konfiguration ebenfalls so angepasst werden, dass die DevTools verwendet werden können.
Beim Starten kann für Node-Debugging ein http-Port angegeben werden. Dieser Port ist frei wählbar. Es empfiehlt sich jedoch, diesen oberhalb von Portnummer 1024 zu wählen, damit es keine Überschneidungen zu den standardisierten System-Ports gibt. Passend zum Jahreswechsel ist das Beispiel mit Port 2021 bestückt.
Jetzt kann die Electron-Applikation mit dem Debug-Port wie folgt gestartet werden: electron –inspect=2021 .
Die App startet normal und in der Konsole befindet sich ein Hinweis, dass der Debugger auf den angegebenen Port hört. Im tatsächlichen Chrome-Browser (nicht in unserer Chromium-basierten App) können unter chrome://inspect die Devices-DevTools geöffnet werden.
Unter dem Punkt Configure kann nun die angegebene Portkonfiguration hinterlegt werden, in diesem Fall also „localhost:2021“. Nach dem Speichern wird die Applikation unter den Remote Targets aufgeführt.
Über den inspect-Link kann nun ein DevTools-Fenster für den Hauptprozess geöffnet werden. Sobald sich dieses neue Fenster geöffnet hat, erscheint in der Konsolenausgabe, in der die App gestartet wurde, der Hinweis, dass der Debugger verbunden wurde.
app.on('window-all-closed', () => { debugger if (process.platform !== 'darwin') { app.quit(); } });
Eine schnelle Möglichkeit, Breakpoints zu setzen, ist der Befehl debugger. Dieser muss einfach in eine eigene Zeile geschrieben werden. In diesem konkreten Beispiel stoppt die Anwendung beim Schließen des Fensters.
Das Beispiel zeigt außerdem, dass ein bestimmtes Verhalten für spezifische Systeme hinterlegt werden kann. Diese automatisch erstellte Schließfunktion enthält eine Ausnahme für die Plattform darwin. Dahinter verbirgt sich das Mac-Betriebssystem. Das übliche Verhalten ist hier, dass geschlossene Fenster noch in der Menüleiste zur Verfügung stehen und nicht komplett beendet werden. Es empfiehlt sich, ein stabiles, erwartetes Verhalten für das jeweilige System einzubinden, damit sich die App intuitiv für die Benutzer der jeweiligen Plattform verhält.
Durch das Electron Forge-Setup sind bereits alle Abhängigkeiten unter den DevDependencies einsortiert. Wird das Projekt samt Dependencies händisch erstellt, sollte darauf geachtet werden, dass alles rund um Electron in den DevDependencies landet. Da sie alleinig zum Verpacken der Anwendung genutzt werden, braucht der Endnutzer diese nicht mitausgeliefert zu bekommen. Alle anderen Dependencies können wie gewohnt hinzugefügt werden. Sollte es dennoch zu Fehlern kommen, ist eine häufige Ursache, dass die neu installierte Dependency eine andere Node-Version erwartet als Electron verwendet.
Bei solchen Fehlern empfiehlt es sich, die DevDependencies um electron-rebuild zu erweitern. Danach kann electron-rebuild <Fehlerhaftes Modul> ausgeführt werden, um alle Versionen anzugleichen.
Der Renderprozess kann normal wie ein Browserfenster neu geladen werden. Im Hauptprozess ist dies nicht so einfach möglich. Damit bei Code-Änderungen nicht jedes Mal neu gestartet werden muss, kann nodemon verwendet werden. Hierfür muss nur in der package.json unter devDependencies nodemon eingefügt werden.
"devDependencies": { "@electron-forge/cli": "^6.0.0-beta.54", "@electron-forge/maker-deb": "^6.0.0-beta.54", "@electron-forge/maker-rpm": "^6.0.0-beta.54", "@electron-forge/maker-squirrel": "^6.0.0-beta.54", "@electron-forge/maker-zip": "^6.0.0-beta.54", "electron": "11.0.2", "electron-rebuild": "^2.3.2", "nodemon": "^2.0.6" }
Danach kann in der package.json bei den scripts eine neue Start-Funktion angelegt werden. Dafür wird nodemon –exec mit dem Inhalt des Start-Skriptes verkettet. Wurde das Projekt durch create-electron-app erstellt ist die Ergänzung also electron-forge start . Das neue Skript kann jetzt mit npm run withReload ausgeführt werden.
"withReload": "nodemon --exec electron-forge start"
Basiswissen: Electron
Electron liefert eine umfangreiche Bibliothek an Funktionen zum Steuern des App-Verhaltens und verschiedener Komponenten. Es ist wichtig zu wissen, dass nicht jede Electron-Funktionalität sowohl im Haupt- als auch im Renderprozess funktioniert. Module, die in beiden Prozessen verwendet werden können, werden Shared Modules genannt. In der Electron-Dokumentation ist immer mit angegeben, für welchen Prozess was gedacht ist.
Das Kernstück für den Hauptprozess ist die App und ein (oder mehrere) BrowserWindow(s). Mit app.on kann ein Listener für einen bestimmtes Event erstellt werden. Wird das Event ausgelöst, kann eingefügter Code ausgeführt oder zum Beispiel das Standardverhalten abgebrochen werden. Hierbei lohnt es sich, die Liste mit allen möglichen Events zu studieren, da es die unterschiedlichsten Möglichkeiten zur Umsetzung gibt. Außerdem sind wichtige Informationen erkenntlich, zum Beispiel auf welchem Betriebssystem diese Events angesteuert werden können. Ist keine Plattform angegeben, wird die Funktionalität überall unterstützt.
Mit dem BrowserWindow-Modul können beliebig viele Renderprozesse erstellt werden. Jeder davon öffnet sich in einem neuen Fenster. Diese Fenster können miteinander verbunden, aber auch jedes für sich konfiguriert werden. Beim Starten können zum Beispiel die Größe und Position des Fensters mitgegeben werden. Außerdem kann auch eine Mindestgröße festgelegt werden, sodass die App nicht in eine unsinnig kleine Darstellung gebracht werden kann.
const createWindow = () => { mainWindow = new BrowserWindow({ width: 2600, height: 600, minWidth: 500, minHeight: 300 }); };
In jedes dieser Fenster wird immer ein Webinhalt geladen. Diese WebContents sind, wie auch die Module BrowserWindow und App, Event Emitter. Das bedeutet, dass sie Events auslösen und ermöglichen, zu diesem Zeitpunkt Funktionen auszuführen. Wird in der Electron-App mit externen URLs gearbeitet, kann durch das new-window-Event gesteuert werden, ob diese sich zum Beispiel generell in einem neuen Renderprozess öffnen dürfen oder nicht.
Es kann passieren, dass HTML-Element eingebunden werden, bei denen nicht sichergestellt werden kann, wie sich zum Beispiel ein Link verhält. In diesem Beispiel wird sich ein zweites App-Fenster öffnen.
<a href="https://www.electronjs.org" target="_blank">Electron Website!</a> Zum Code
Soll das verhindert werden, kann das Event abgefragt werden, das beim Erzeugen eines neuen Fensters ausgelöst wird. Mit preventDefault wird das Standardverhalten unterbunden. In diesem Fall wird also kein neues Fenster geöffnet. Stattdessen kann die URL aus dem Event mit loadURL im bereits existierenden Fenster angezeigt werden.
mainWindow.webContents.on("new-window", (event, url) => { event.preventDefault(); mainWindow.loadURL(url); })
Eine Session speichert die Daten eines WebContents. Standardmäßig erstellt Electron eine Default-Session. Diese wird von allen Renderprozessen verwendet. Die Default-Session ist persistent, was bedeutet, dass die gespeicherten Daten auch nach einem Neustart der Electron-App wieder abgerufen werden können. Der Default kann explizit überschrieben werden, sodass bei mehreren Fenstern verschiedene Sessions gehalten werden. Selbst erstellte Sessions können entweder auf die Festplatte geschrieben oder in-memory gehalten werden. Letzteres bedeutet, dass ein Neustart der App alle Daten zurücksetzt. Soll für ein BrowserWindow eine eigene persistente Session angelegt werden, die sich von den anderen Sessions unterscheidet, kann dies beim Initialisieren gemacht werden. Wichtig beim Erstellen ist das Stichwort persist: ohne wird eine in-memory Session angelegt.
mainWindow = new BrowserWindow({ width: 2600, height: 600, webPreferences: { partition: 'persist:ort' } });
Electron verfügt über sehr viele Module, Komponenten und Bibliotheken, die beim Entwickeln von Applikationen helfen. Ein Beispiel dafür ist die electron-window-state-Bibliothek, welche es ermöglicht, Benutzerkonfigurationen an Applikationsgröße und Positionierung zu speichern und beim nächsten Starten wiederherzustellen.
Alternativ kann die Electron-Applikation, wie gewohnt, durch JavaScript und CSS um Styling und Verhalten ergänzt werden. Es empfiehlt sich zum Beispiel abzustellen, dass der Benutzer die ganze Applikation markieren kann. Hierfür kann im Body das Styling auf user-select:none gesetzt werden. Es empfiehlt sich dann, bestimmte Elemente wie etwa Adressen, Telefonnummern und IBANs wieder auf markierbar zu schalten.
<body style="user-select: none; background-color: lightslategrey">
Electron bietet unter anderem verschiedene Komponenten für die TouchBar eines MacBooks an. Diese können schnell und einfach zusammengefügt werden, um die Leiste mit den gewünschten Funktionalitäten zu füllen. Wird von anderen Betriebssystemen oder älteren Geräten ohne die TouchBar entwickelt, kann über eine Simulation das Verhalten überprüft werden. Die Simulation zeigt auf dem Monitor an, was in der Leiste zu diesem Zeitpunkt zu sehen wäre.
Das Modul TouchBar enthält sehr viele Komponenten, mit denen die Leiste gefüllt werden kann. Zunächst muss diese durch ein require eingebunden werden. Danach kann die Klasse wie gewohnt verwendet werden.
const {TouchBar} = require('electron');
Um zu zeigen, wie leicht es ist, eine Electron-Komponente anzulegen und zu integrieren, wird ein ColorPicker in die TouchBar gelegt. Bei der Initialisierung des Objekts können die wählbaren Farben über die availableColors vorgegeben werden. Wird nichts angegeben, werden die Standardwerte als Auswahl dargestellt. Eine mehrfache Angabe von Farben ist hierbei nicht möglich und hindert die Applikation dadurch am Starten.
const colorPicker = new TouchBar.TouchBarColorPicker({ availableColors: ['#cceb34', '#2de3d1', '#2d4fd1', '#2dbce3'] });
Der erstellte ColorPicker kann dann der Leiste zugewiesen werden. Die Komponenten erscheinen in der TouchBar in derselben Reihenfolge, in der sie bei der Initialisierung angegeben wurden.
const touchBar = new TouchBar({ items: [colorPicker] });
Die TouchBar kann voll konfiguriert werden. Es besteht sogar die Möglichkeit die Escape-Taste zu überschreiben. Da nur MacBooks über eine TouchBar verfügen, soll sie nur auf dieser Plattform ein Teil der Applikation werden. Wie bereits erwähnt, kann das Betriebssystem, auf dem die Anwendung ausgeführt wird, abgerufen werden. Mit Hilfe des Schlüsselwortes darwin kann nach macOs gefiltert werden. In diesem Fall wird für den Renderprozess das TouchBar-Element, mit der eigenen Konfiguration, gesetzt.
if(process.platform === 'darwin'){ mainWindow.setTouchBar(touchBar); }
Bei Testdurchläufen ist zu beachten, dass die TouchBar diese Einstellungen nur anzeigt, wenn die Electron-Applikation gestartet und im Fokus ist.
App mit konfigurierter TouchBar
Electron Packager
Wird das Projekt mit create-electron-app erstellt, dann ist automatisch eine Dependency für den Electron-Packager enthalten. Dieser erlaubt es, die erstellte App für beliebige Plattformen zu verpacken. Hierbei kann aus vielen Varianten für die Verteilung gewählt werden, zum Beispiel als .exe oder dmg. In diesem Beispiel verwenden wir electron-installer-dmg für eine macOS DMG Datei. Folgende Konfigurationen stehen hierbei zur Verfügung:
electron-packager <anwendungspfad> <anwendungsname> –platform=<platform> electron-packager .
Mit –all kann ein Gesamtpaket an allen gültigen Kombinationen erstellt werden.
Um für Windows als nicht-Windows-Plattform zu bauen, muss zunächst Wine installiert werden. Unter Mac kann mittels Homebrew zunächst installiert werden:
- brew cask install xquartz
- brew tap homebrew/cask-versions
- brew cask install –no-quarantine wine-stable
Danach kann für Windows verpackt werden:
electron-packager . –platform=win32
Gegebenenfalls meldet sich Wine während des ersten Mals, dass weitere Packages nachgeladen werden müssen. Danach befindet sich unter dem angegebenem Pfad ein neuer Ordner, der die erstellte exe-Datei für die App enthält.
Plattformübergreifende Desktop-Anwendungen mit dem JavaScript-Framework Electron – Fazit
Mit Hilfe von create-electron-app kann in wenigen Sekunden die Basis für ein Projekt geschaffen werden. Wird wirklich für mehrere Plattformen entwickelt, sollte man einen gewissen Erfahrungswert über das native Verhalten einer App in diesem Kontext haben oder aufbauen.
Es gibt viele Konfigurationsmöglichkeiten, die schnell und einfach dafür sorgen, dass sich die App für den Benutzer professionell verhält. Hierbei lohnt es sich, die Electron-Dokumentation genauer unter die Lupe zu nehmen. Oftmals merkt man erst, dass man ein bestimmtes Verhalten möchtet oder erwartet, wenn man den jeweiligen Eintrag in der Dokumentation dazu findet. Außerdem ist man dadurch immer im Klaren darüber, welche Funktionalität von welcher Plattform unterstützt ist. Leider ist die deutsche Dokumentation sehr mit englischen Sätzen durchwachsen, sodass es öfter passiert, dass in einem Absatz mehrfach die Sprache wechselt. Daher würde ich empfehlen, die Dokumentation direkt im Englischen anzugehen.
Electron bietet durch seine große Anzahl an Komponenten sehr schnell Ergebnisse. Selbst kompliziert erscheinende Vorgänge, wie das Programmieren der Mac-TouchBar, sind innerhalb von wenigen Zeilen Code umsetzbar.
- Selbstorganisation im IT-Alltag mit Notion - 18. Oktober 2023
- Erste Schritte für den Einstieg in die Softwarearchitektur - 10. Juli 2023
- Vuex vs. Pinia: Ein direkter Vergleich - 16. November 2022