Desktopanwendungen mit JavaScript entwickeln

JavaScript gilt als eierlegende Wollmilchsau unter den Programmiersprachen. Mittlerweile macht sie auch im Bereich der Desktopanwendungen eine gute Figur.

In Pocket speichern vorlesen Druckansicht 31 Kommentare lesen
Desktopanwendungen mit JavaScript entwickeln
Lesezeit: 23 Min.
Von
  • Philip Ackermann
Inhaltsverzeichnis

Webanwendungen sind dank moderner Webtechnologien in den letzten Jahren immer populärer geworden und haben in vielen Fällen klassische Desktopanwendungen verdrängt oder warten wenigstens mit gleichwertigen Lösungen auf (Stichwort Rich Internet Applications). Dennoch sind Desktop-Applikationen je nach Einsatzszenario beziehungsweise Anforderungen nach wie vor in vielen Fällen sinnvoller.

Zu den Vorteilen von Desktopanwendungen gegenüber denen fürs Web zählen unter anderem:

  • Zugriff auf native Features: Im Gegensatz zu Webanwendungen, die gar nicht oder nur eingeschränkt (beispielsweise über entsprechende Web-APIs oder Browser-Plug-ins) auf native Features und Hardwareressourcen des Rechners zugreifen können, gilt diese Einschräkung für Desktopanwendungen nicht. Klassisches Beispiel hierfür ist der Zugriff auf das Dateisystem. Während Nutzer innerhalb einer Webanwendung (über die File-API) nur Dateien nutzen können, die sie explizit ausgewählt haben, hat man innerhalb einer Desktopanwendung (entsprechende Rechte vorausgesetzt) prinzipiell Zugang zum gesamten Dateisystem.
  • Kein Aufwand bezüglich Browserversionen: Beim Entwickeln von Webanwendungen ist in der Regel ein beachtlicher Teil der Entwicklungszeit dem Thema Browserkompatibilität zu widmen. Dazu zählen Fragestellungen wie: Welches Feature wird von welchem Browser unterstützt? Und ab welcher Browserversion? Welche Besonderheiten oder Bugs gibt es in welchem Browser? Wie lassen sich Letztere beheben? Natürlich können in diesem Zusammenhang Polyfills helfen. Das sind Bibliotheken, die Features emulieren, die ein Browser nicht unterstützt. Auch Cross-Browser-Testing-Tools, die eine Webanwendung automatisch in unterschiedlichen Konstellationen aus Browser, Version und Betriebssystem testen, stellen eine enorme Hilfe bei der Entwicklung dar. Bei Desktopanwendungen allerdings fällt das Thema komplett weg, da man es erst gar nicht mit Browsern und deren Engines zu tun hat.
  • Kein Internetzugang erforderlich: Webanwendungen setzen eine Verbindung zum Internet voraus. Auch wenn sich das mit Offline-First-Mitteln wie Service Workern, IndexedDB und Web Storage weitgehend minimieren lässt, lassen sich Desktopanwendungen in der Regel deutlich einfacher so gestalten, dass sie auch ohne Internetzugang funktionieren.
  • Keine Downloadzeit: Die Komplexität von Webanwendungen und die Anzahl eingebundener Fremdbibliotheken und Frameworks wirkt sich auf die Zeit aus, die es braucht, um die Anwendung initial zu starten. Dauert der Ladevorgang lange, beeinflusst das die Nutzerfreundlichkeit negativ. Caching-Mechanismen der Browser wirken dem zwar entgegen, für Desktopanwendungen stellt sich das Problem aber erst gar nicht.
  • Performance bei hohem Nutzeraufkommen: Bei Webanwendungen können hohe Zugriffszahlen negativ auf die Performance wirken. Bei Desktopanwendungen spielt die Anzahl gleichzeitig aktiver Nutzer (zumindest für den UI-Code) keine Rolle. Lediglich wenn eine Desktopanwendung externe (Web-)Services einbindet, können sich diese zum Engpass entwickeln.

Andersherum gibt es eine Menge an Vorteilen von Web- gegenüber Desktopanwendungen, darunter ein ganz wesentlicher: Cross-Plattform-Fähigkeit, sprich die Fähigkeit einer Anwendung, auf allen Betriebssystemen inklusive der mobilen (Windows, Linux, macOS, Android OS, iOS) zu laufen, eine entsprechende Laufzeitumgebung, die in Form eines Browsers daher kommt, vorausgesetzt. Der Aufwand, plattformunabhängige Desktopanwendungen nativ zu erstellen, und die notwendigen Programmierkenntnisse sind im Vergleich entsprechend hoch.

Das ist genau der Punkt, an dem die im folgenden vorgestellten Frameworks NW.js und Electron ansetzen: sie kombinieren moderne Webtechniken mit der Möglichkeit, sie für die Programmierung von Desktopanwendungen zu verwenden. Beide Frameworks verwenden dazu einen ähnlichen Ansatz, weisen bei genauerer Betrachtung aber Unterschiede auf.

Bei NW.js handelt es sich um ein Open-Source-Framework zum Erstellen von Desktopanwendungen in HTML, CSS und JavaScript, das 2011 von Intel entwickelt wurde. Die Idee von NW.js ist es, die JavaScript-Laufzeitumgebung Node.js mit der Browserengine WebKit zu kombinieren (der ursprüngliche Name lautete daher auch node-webkit) und beides plattformunabhängig zur Verfügung zu stellen.

Das Zusammenwirken beider Tools ermöglicht es NW.js zum einen, innerhalb eines entsprechenden Anwendungsfensters Applikationen darzustellen, die in HTML, CSS und JavaScript implementiert wurden (durch WebKit), und zum anderen native Funktionen des zugrundeliegenden Betriebssystem zu nutzen (durch Node.js).

Vereinfacht gesagt können Webentwickler, die "nur" HTML, CSS und JavaScript beherrschen, mit NW.js plattformübergreifend funktionierende Desktopanwendungen erstellen. Die Bibliothek generiert dafür aus einer einzigen Codebasis (in HTML, CSS und JavaScript) Anwendungsdateien für die Betriebssysteme Windows, Linux und macOS (dazu später mehr).

Die Installation von NW.js geschieht wie für Node.js-Anwendungen gewohnt über den Node.js Package Manager (npm) via npm install -g nw (über den Parameter -g werden Module global installiert, was notwendig ist, um die zum Framework gehörenden Kommandozeilentools zu installieren).

Eine minimale NW.js-Anwendung besteht aus zwei Dateien: Mit der Manifest-Datei package.json lassen sich allgemeine Metadaten zum Modul beziehungsweise zur Anwendung verwalten, beispielsweise Name, Versionsnummer, die Angabe der Datei, die den Einstiegspunkt für die Anwendung darstellt oder externe Abhängigkeiten.

Name, Version und Einstiegspunkt sind zugleich die Minimalanforderungen an eine package.json-Datei für NW.js-Anwendungen. Die Kombination der Eigenschaften name und version dient dabei als Identifier der Anwendung, die Eigenschaft main gibt an, welche HTML-Datei beim Start zu laden ist. Eine minimale Konfigurationsdatei für eine Anwendung mit Namen helloworld in Version 1.0.0, bei der die Datei index.html den Einstiegspunkt markiert, könnte daher wie folgt aussehen

{
"name": "helloworld",
"version": "1.0.0",
"main": "index.html"
}

und der Inhalt der HTML-Datei folgendermaßen

<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
<div>
Ihr Betriebssystem lautet: <span id="platform"></span>
</div>
<script type="text/javascript" src="./scripts/main.js"></script>
</body>
</html>

Seine API stellt NW.js über ein globales Objekt nw zur Verfügung, über das sich beispielsweise UI-Komponenten wie Kontextmenüs oder ähnliches erstellen lassen. Auf die DOM-API und die unterschiedlichen Node.js-APIs lässt sich dagegen direkt zugreifen. Folgendes Beispiel zeigt eine Kombination der beiden letztgenannten: Am document-Objekt registriert das Programm einen Event-Listener für das DOMContentLoaded-Event (DOM-API), innerhalb dessen über das Node.js-Modul os die Informationen zu der verwendeten Plattform ausgelesen werden (Node.js-API). Diese Informationen werden dann (wieder mit Hilfe der DOM-API) in das Element mit der ID platform geschrieben:

'use strict'
const os = require('os');
document.addEventListener('DOMContentLoaded', () => {
let platform = os.platform();
document.getElementById('platform').textContent = platform;
});

Grundsätzlich lassen sich innerhalb einer NW.js-Anwendung die vollständige DOM-API und die vollständige Node.js-API nutzen (plus zusätzlicher Bibliotheken und Module versteht sich). Darüber hinaus erweitert NW.js die DOM-API und die Node.js-API um einige Features wie zusätzliche Eigenschaften für Texteingabefelder oder für das process-Objekt.

Beim Inhalt einer NW.js-Anwendung handelt es sich letztendlich also um Webdokumente, was bedeutet, dass sich etablierte Frameworks wie AngularJS und Bootstrap auch bei der Implementierung von NW.js-Anwendungen verwenden lassen. Ein einfaches Beispiel einer Anwendung, welche die Dateinamen innerhalb eines gegebenen Verzeichnisses ausliest und auf der Oberfläche auflistet ist in den folgenden Quellcodeauszügen zu sehen.

'use strict';
class FileController {
constructor(fileSystemService) {
this.path = '.';
this.fileSystemService = fileSystemService;
this.showFiles(path);
}
showFiles(path) {
this.files = this.fileSystemService.getFiles(this.path);
}
}

class FileSystemService {
constructor() {
this.files = [];
}
getFiles(path) {
const fs = require('fs');
this.files = fs.readdirSync(path);
return this.files;
}
}

(function(){
angular.module('filereader', [])
.controller('fileController', FileController)
.service('fileSystemService', FileSystemService);
})();

Der obige Ausschnitt zeigt den AngularJS-Code (in AngularJS 1.x) eines Controllers und eines Services, innerhalb deren Methode getFiles() über das Node.js-Modul fs die Dateinamen für ein gegebenes Verzeichnis gelesen und zurückgegeben werden.

Mit entsprechenden AngularJS-Mechanismen lässt sich auf diese Informationen dann innerhalb des HTML-Codes zugreifen (nichts Neues für AngularJS-Entwickler):

<!DOCTYPE html>
<html>
<head>
<title>File reader</title>
<link rel="stylesheet"
href="./bower_components/bootstrap/dist/css/bootstrap.min.css">
</head>
<body ng-app="filereader" ng-controller="fileController as vm">
<div class="panel panel-default">
<label for="path">Pfad: </label>
<input type="text" id="path" name="path" ng-model="vm.path">
<button ng-click="vm.showFiles()">Dateien auflisten</button>
<table class="table table-striped">
<tr ng-repeat="file in vm.files">
<td>{{file}}</td>
</tr>
</table>
</div>
<script type="text/javascript" src="./lib/jquery/dist/jquery.min.js">
</script>
<script type="text/javascript" src="./lib/bootstrap/dist/js/bootstrap.min.js"></script>
<script type="text/javascript" src="./lib/angular/angular.min.js"></script>
<script type="text/javascript" src="./scripts/main.js"></script>
</body>
</html>

Um die Anwendung zu starten, reicht es, im entsprechenden Projektverzeichnis den Befehl "nw" auszuführen. Das Ergebnis zeigt folgender Screenshot:

Beispiel für eine NW.js-Anwendung (Abb. 1)

Für das Packaging von NW.js-Anwendungen steht unter anderem das Modul nw-builder zur Verfügung. Das Modul lässt sich über den Befehl npm install -g nw-builder installieren. Anschließend kann über die Kommandozeile global der Befehl nwbuild verwendet werden. Über den Parameter --platforms beziehungsweise -p besteht die Option, eine kommaseparierte Liste von Betriebssystemen zu übergeben, für die die Anwendung zu erzeugen ist. Mögliche Werte sind win32, win64, osx32, osx64, linux32 und linux64. Um beispielsweise die Anwendung für die 64-Bit-Versionen von Windows und macOS zu erstellen, reicht folgender Befehl:

nwbuild --platforms win64,osx64

Über weitere Parameter lassen sich unter anderem die zu verwendende Version von NW.js (--version beziehungsweise -v) und der Build-Ordner (--buildDir bzw. -o) angeben.

Alternativ zu der Verwendung auf der Kommandozeile können Entwickler nwbuild auch programmatisch nutzen, beispielsweise um es in Build-Tools wie Gulp oder Grunt zu integrieren.

'use strict';
const NwBuilder = require('nw-builder');
cont nw = new NwBuilder({
files: './path/to/nwfiles/**/**',
platforms: ['win64','osx64'],
version: '0.14.6'
});
nw.on('log', console.log);
nw.build().then(() => {
console.log('all done!');
}).catch(error => {
console.error(error);
});

NW.js unterstützt das Debugging über die Chrome Developer Tools. Wichtig: Das funktioniert nur, wenn NW.js mit dem sogenannten "SDK Flavor" läuft, was standardmäßig bei der Installation über npm install -g nw nicht der Fall ist.

Möchte man NW.js entsprechend betreiben, muss man das bei der Installation konkret angeben, beispielsweise via npm install -g nw@0.17.6-sdk. Anschließend lassen sich die Chrome Developer Tools über die F12-Taste (unter Windows) beziehungsweise ⌘+⌥+i (unter Mac) oder alternativ programmatisch über win.showDevTools() öffnen.

Zum Debuggen lassen sich die Chrome Developer Tools verwenden (Abb. 2).

Das zweite populäre Framework zum Erstellen von Desktopanwendungen in JavaScript ist Electron. GitHub setzte das Projekt ursprünglich im Rahmen der Entwicklung des Code-Editors Atom ein, machte es 2013 aber als separates Framework der Öffentlichkeit zugänglich.

Vom Prinzip her ist Electron ähnlich zu NW.js. Kein Wunder: einer der urspünglichen Entwickler von NW.js, der zuvor bei Intel beschäftigt war, arbeitet mittlerweile für GitHub an Electron weiter. Dennoch gibt es einen wesentlichen Unterschied zwischen NW.js und Electron, der für Entwickler wichtig zu verstehen ist: Während sich in NW.js Node.js und WebKit einen einzelnen JavaScript-Kontext teilen, gibt es in Electron mehrere Kontexte: einen für den Hintergrundprozess, der die Anwendung steuert, und jeweils einen für jedes Anwendungsfenster (zu beidem gleich mehr).

Ein weiterer Unterschied ist die Definition des Startpunkts einer Anwendung: In NW.js ist das wie bereits beschrieben eine HTML-Datei, in Electron dagegen eine JavaScript-Datei. Darüber hinaus unterscheidet sich die Art und Weise, in der WebKit (bzw. Chromium) eingebunden ist (Details sind in der Dokumentation zu finden).

Im Gegensatz zu Electron ermöglicht NW.js zudem das Kompilieren des JavaScript-Codes in nativen Code, um zu verhindern, dass der Quelltext in lesbarer Form mit der Anwendung ausgeliefert wird. Allerdings läuft das kompilierte Ergebnis etwa 30 Prozent langsamer. Darüber hinaus verfügt NW.js über einen integrierten PDF-Viewer und eine Druckvorschau. Unter Electron dagegen muss man auf externe Bibliotheken wie pdf.js ausweichen. Ein detaillierter Vergleich beider Frameworks ist unter anderem auf TangibleJS zu finden.

Electron unterscheidet Hauptprozess und Renderer-Prozess. Der Hauptprozess (üblicherweise in einer Datei main.js enthalten) stellt den Einstiegspunkt für eine Electron-Anwendung dar und kontrolliert den Lebenszyklus einer Applikation. Aus dem Hauptprozess heraus lässt sich beispielsweise auf native Komponenten wie das Dateisystem zugreifen.

Dem gegenüber stehen die Renderer-Prozesse, die im Wesentlichen ein (Browser-)Fenster innerhalb einer Electron-Anwendung repräsentieren und die eine Kombination aus HTML, CSS und JavaScript enthalten. Folglich hat man innerhalb eines Renderer-Prozesses (beziehungsweise des entsprechenden JavaScript-Codes) Zugriff auf das DOM des entsprechenden Fensters. Außerdem kann man von dort die Node.js-API ansprechen.

Innerhalb einer Anwendung kann es daher durchaus mehrere Renderer-Prozesse geben, jedoch nur einen Hauptprozess.

Die Installation von Electron führt wie bei NW.js über npm, sprich über den Befehl npm install -g electron (zumindest, wenn man die Kommandozeilentools von Electron nutzen möchte, ansonsten ist auch eine lokale Installation möglich). Wie erwähnt, bildet eine JavaScript-Datei den Einstiegspunkt in eine Electron-Anwendung. Da es sich bei Electron-Anwendungen wie bei NW.js-Anwendungen letztendlich um Node.js-Module handelt, werden Abhängigkeiten wie üblich über die Datei package.json in der jeweiligen Anwendung verwaltet.

Der einfachste Weg, sich diese (und weitere) Dateien generieren zu lassen, führt über das Git Repository electron-quick-start, das sich über den Befehl git clone https://github.com/electron/electron-quick-start klonen lässt und eine minimale Beispielanwendung enthält, bestehend aus Meta-Dateien wie genannter package.json-, .gitignore-, Lizenz- und Readme-Datei und folgenden weiteren:

  • eine JavaScript-Datei, in der sich der Code für den Hauptprozess befindet und in dem das Hauptfenster der Anwendung erzeugt wird (main.js),
  • eine HTML-Datei, die in das Hauptfenster geladen wird (index.html) und den Code für den Renderer-Prozess des Fensters bereitstellt,
  • eine JavaScript-Datei, die von der HTML-Datei eingebunden wird (renderer.js) und den Code enthält, den der Renderer-Prozess verarbeitet (zu Beginn ist die Datei bis auf einige Kommentare noch leer).

Über ein anschließendes npm install lassen sich die benötigten Abhängigkeiten installieren, mit npm start startet die Anwendung (alternativ eignet sich dafür auch der Befehl electron im entsprechenden Anwendungsverzeichnis).

Eine etwas angepasste Version des generierten Hauptprozess-Codes sieht wie folgt aus:

'use strict';
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
let mainWindow;

function createWindow () {
mainWindow = new BrowserWindow({width: 800, height: 600})
mainWindow.loadURL(`file://${__dirname}/index.html`)
mainWindow.on('closed', () => {
mainWindow = null;
})
}
app.on('ready', createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (mainWindow === null) {
createWindow()
}
})

Um zwischen den einzelnen Prozessen, sprich dem Hauptprozess und den einzelnen Renderer-Prozessen zu kommunizieren, stehen die Objekte ipcMain und ipcRenderer zur Verfügung. Das Objekt ipcMain steuert dabei die Kommunikation auf Seiten des Hauptprozesses, während ipcRenderer den Austausch auf Seiten eines Renderer-Prozesses erledigt. Auf beiden Seiten lassen sich Events versenden (über die Methode send()) und auf sie lauschen (via on()).

Die folgenden beiden Listings zeigen dazu ein Beispiel, wobei der Ablauf folgender ist: im Renderer-Prozess wird innerhalb eines Event-Listeners, der für das click-Event an einer Schaltfläche registriert wurde, das Ereignis "example-request" mit dem Wert "Hello" an den Hauptprozess geschickt.

// Renderer-Prozess
'use strict';
const ipc = require('electron').ipcRenderer;
const button = document.getElementById('button')
button.addEventListener('click', () => {
ipc.send('example-request', 'Hello');
})
...

Innerhalb des Hauptprozesses beziehungsweise des dort für das Event example-request registrierten Event-Listeners wird ebenfalls ein Event versendet, und zwar example-response mit dem Wert "Hello World".

// Hauptprozess
'use strict';
const ipc = require('electron').ipcMain;
ipc.on('example-request', (event, arg) => {
event.sender.send('example-response', arg + ' World')
});

Der Renderer-Prozess, der auf das Event lauscht, nimmt den Wert wiederum entgegen und stellt ihn innerhalb der Oberfläche dar.

// Renderer-Prozess
...
ipc.on('example-response', (event, arg) => {
const message = `Antwort: ${arg}`
document.getElementById('response').innerHTML = message;
})

Ein weiteres Beispiel für die Anwendung der Interprozesskommunikation zeigen die folgenden Codeauszüge. In ihnen versendet das Programm auf Knopfdruck das Event open-file-dialog, woraufhin der Hauptprozess einen Dateidialog öffnet.

'use strict';
const ipc = require('electron').ipcRenderer;
const buttonSelectFile = document.getElementById('select-file');
buttonSelectFile.addEventListener('click', event => ipc.send('open-file-dialog'));
ipc.on('selected-files', (event, path) => {
/* ... */
});

Die ausgewählten Dateien beziehungsweise deren Dateinamen werden anschließend an den Renderer-Prozess zurückgeschickt und dort weiterverarbeitet:

'use strict';
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const ipc = require('electron').ipcMain
const dialog = require('electron').dialog
let mainWindow;
...
ipc.on('open-file-dialog', event => {
dialog.showOpenDialog({
properties: ['openFile', 'openDirectory']
}, files => {
if (files) {
event.sender.send('selected-files', files);
}
})
})
...

Für das Implementieren von UI-Komponenten kann man wie bei NW.js auf bestehende CSS-Frameworks wie Bootstrap oder JavaScript-Frameworks wie AngularJS zurückgreifen. Darüber hinaus stellt die Bibliothek Photon einige typische UI-Komponenten für Desktopanwendungen wie Kopf- und Fußleisten, Toolbars, Tabs und einige mehr zur Verfügung, die speziell auf die Verwendung in Electron-Anwendungen ausgelegt sind. Interessierte können die Bibliothek als ZIP-Datei von der Homepage herunterladen, eine Installation über Bower oder npm wird dagegen nicht angeboten.

Mit Hilfe des Moduls electron-packager lassen sich für Electron-Anwendungen die entsprechenden Dateien für die unterschiedlichen Betriebssysteme generieren. Das Modul lässt sich sowohl programmatisch als auch über die Kommandozeile aufrufen. Für Ersteres reicht die lokale Installation mit npm install electron-packager --save-dev, für Letzteres ist das Modul über den Befehl npm install -g electron-packager global zu installieren.

Auf der Kommandozeile können Nutzer das Packaging über electron-packager starten, wobei als Parameter das Quellverzeichnis, der Name der Anwendung, die Zielplattformen ("darwin", "linux", "mas", "win32") sowie deren Architektur ("ia32", "x64", "armv7l") anzugeben sind. Der Befehl electron-packager electron example-electron --platform=darwin --arch=x64 beispielsweise paketiert die Anwendung für macOS in 64Bit.

Programmatisch lässt sich electron-packager wie folgt verwenden. Alternativ dazu steht das Grunt-Plug-in grunt-electron zur Verfügung.

'use strict';
const packager = require('electron-packager');
const options = {
dir: './src',
platform: 'darwin',
arch: 'x64'
}
packager(options, (error, appPaths) {
if(error) {
console.error(error);
}
});

Auch Electron-basierte Anwendungen lassen sich mit den Chrome Developer Tools debuggen. Programmatisch können Entwickler die Tools über die Methode toggleDevTools() aktivieren:

...
function createWindow () {
mainWindow = new BrowserWindow({width: 800, height: 600})
mainWindow.toggleDevTools();
...
}
...
app.on('ready', createWindow);
...

Zusätzliche Funktionen für das Debugging und Monitoring stellt das Tool Devtron (http://electron.atom.io/devtron/) bereit, das sich als Plug-in in den Chrome Developer Tools einnistet. Es lässt sich mit npm install --save-dev devtron installieren und anschließend via require('devtron').install() aktivieren.

...
function createWindow () {
mainWindow = new BrowserWindow({width: 800, height: 600})
require('devtron').install();
mainWindow.toggleDevTools();
...
}
...
app.on('ready', createWindow);
...

Im Wesentlichen bietet Devtron folgende Hilfestellungen:

  • Visualisierung von Abhängigkeiten: Die Bibliotheken, von denen die aktuelle Anwendung abhängig ist, lassen sich in Form eines Graphen darstellen.
  • Übersicht Event-Listener: Über den Reiter "Event Listeners" erhalten Entwickler eine Übersicht der in einer Electron-Anwendung registrierten Event-Listener.
  • Fehlerdiagnose: Über den Reiter "Lint" lässt sich die Anwendung auf Fehler überprüfen und Entwickler können potenzielle Schwachstellen aufdecken.
  • Interprozesskommunikation: Im Reiter "IPC" lässt sich die Kommunikation zwischen Hauptprozess und Renderer-Prozessen protokollieren beziehungsweise die versendeten Nachrichten und deren Inhalte einsehen.

Devtron und die Chrome Developer Tools helfen nur beim Debuggen von Renderer-Prozessen. Um den Code des Hauptprozesses zu debuggen, muss man dagegen auf externe Debugger zurückgreifen und die Anwendung unter Angabe des Parameters --debug beziehungsweise --debug-brk starten.

Bezüglich des Testens von Electron-Anwendungen ist besonders das Tool Spectron interessant. Es nutzt ChromeDriver und WebDriver I/O, ein Selenium-2.0-Binding für Node.js und lässt sich prinzipiell mit jeder Testing-Bibliothek (wie Mocha oder Jasmine) einsetzen. Spectron wird über den Befehl npm install --save-dev spectron installiert und steht anschließend über require('spectron') zum Einbinden zur Verfügung. Folgender Quellcodeausschnitt zeigt ein Beispiel zum Einsatz:

'use strict';
const Application = require('spectron').Application
const assert = require('assert');
let app = new Application({
path: '/Applications/Example.app/Contents/MacOS/Example'
});
app.start().then(() => {
return app.browserWindow.isVisible()
}).then(isVisible => {
assert.equal(isVisible, true)
}).then(() => {
return app.client.getTitle()
}).then(title => {
assert.equal(title, 'Example')
}).then(() => {
return app.stop()
}).catch(error => {
console.error('Test fehlgeschlagen', error.message)
});

Mit Hilfe von NW.js und Electron ist es relativ einfach, plattformunabhängige Desktopanwendungen zu erstellen. Die zugrunde liegenden Technologien HTML, CSS und JavaScript erleichtern den Einstieg entsprechend. Wesentliche Unterschiede der beiden vorgestellten Frameworks NW.js und Electron sind der Einstiegspunkt einer Anwendung, die Integration von Node.js und die Anzahl der verwendeten JavaScript-Kontexte.

Eine repräsentative Auswahl von Anwendungen, die mit NW.js und Electron implementiert wurden, ist in Tabelle 1 und Tabelle 2 gesammelt. Wie man sieht, befinden sich darunter einige, die man als Webentwickler vielleicht täglich verwendet (z.B. Atom, Postman, Slack oder Visual Studio Code), ohne sich der zugrundeliegenden Technik bewusst zu sein.

Name Art der Anwendung
Facebook Messenger Desktop Messenger
Gitter Desktop Gitter Client
Mango Markdown-Editor
WhatsApp Desktop Messenger (nicht offiziell)
Tabelle 1: Anwendungen auf Basis von NW.js
Name Art der Anwendung
Abricotine
Markdown-Editor
Atom Code-Editor
Caret Markdown-Editor
Flow Task-Management
Ghost Desktop
Ghost-CMS, Desktopversion
GitKraken Git-Client
Kitematic
Docker Container Management
Min Webbrowser
Mongotron MongoDB-Management-Tool
Nocturn
Twitter-Client
Nylas N1 E-Mail-Client
Playback Video-Player
Postman REST- bzw. HTTP-Client
Slack Desktopanwendung für Slack
Space Radar Festplattenvisualisierung
Visual Studio Code
Code-Editor
Wagon SQL-Analysetool
WebTorrent Torrent-Client
WhatsApp Messenger
WordPress Desktop CMS
Tabelle 2: Anwendungen auf Basis von Electron

Nützliche Links, die den Einstieg in die beiden Frameworks erleichtern, sind in Tabelle 3 und Tabelle 4 gelistet. Prinzipiell sind aber auch die Dokumentationen sowohl von NW.js als auch von Electron sehr umfangreich und einsteigerfreundlich.

Name Beschreibung
nwjs-starter Gulp-Startprojekt für NW.js
awesome-nwjs Liste von Links zu NW.js
nw-builder Bauen von NW.js-Anwendungen
Tabelle 3: Nützliche Links zu NW.js
Name Beschreibung
Spectron Testen von Electron-Anwendungen mit ChromeDriver
DevTron Debuggen von Electron-Anwendungen auf Basis der Chrome Developer Tools
Electron API-Demos Electron-Beispielanwendung
electron-superkit
Gulp-Startprojekt für Electron
awesome-electron Liste von Links zu Electron
Tabelle 4: Nützliche Links zu Electron

Geht man nach dem Interesse auf GitHub, hat Electron – obwohl es jünger als NW.js ist – mittlerweile die Nase vorn. NW.js dürfte vor allem durch die Tatsache punkten, dass sich der JavaScript-Code in nativen Code kompilieren lässt. In jedem Fall machen beide Frameworks einen guten Eindruck und sind allemal – auch JavaScript-Kritikern – einen Blick wert.

Philip Ackermann
entwickelt seit 17 Jahren Web- und Softwareanwendungen, arbeitet beim Fraunhofer-Institut für Angewandte Informationstechnologie FIT an Tools zum teilautomatisierten Testen von Web Compliance und ist Autor von Fachbüchern über Java und JavaScript sowie zahlreicher Fachartikel.
(jul)