Desktopanwendungen mit JavaScript entwickeln

Seite 2: NW.js

Inhaltsverzeichnis

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).