Progressive Web-Apps mit React erstellen

PWAs mit React: Die nächste Stufe der App-Revolution
Keine Kommentare

Progressive Web-Apps sind in aller Munde. Sie stellen die nächste Evolutionsstufe des Webs dar und vereinen die Vorteile von Webseiten mit denen von nativen und hybriden Apps. Im folgenden Artikel nutzen wir das leistungsfähige Framework React, um eine progressive Web-App zu erstellen.

Steve Jobs war seiner Zeit weit voraus. Als er vor zehn Jahren, im Jahr 2007, das erste iPhone vorstellte, hatte er eine Vision, wie Apps funktionieren sollen. Nach seiner Vorstellung sollten Web-Apps komplett im Safari-Browser laufen und per JavaScript und Ajax das Verhalten von nativen Apps imitieren. Der Plan ging jedoch nicht auf. Das erste iPhone war für Web-Apps zu langsam, viele Schnittstellen zur Hardware fehlten, und die User Experience war den nativen Apps massiv unterlegen. Entwickler fühlten sich von Apple bevormundet und forderten die Möglichkeit, native Apps zu schreiben. Mit den daraus resultierenden App Stores entstand ein Markt mit milliardenschweren Umsätzen. Zehn Jahre später sind Apps ein fester Bestandteil von iOS, Android und Windows Phone. Dennoch gibt es einen Trend, der die Idee der ursprünglichen Web-Apps aufgreift und in ein modernes Gewand packt.

Viele Gründe, die Web-Apps vor zehn Jahren scheitern ließen, sind heute nicht mehr aktuell. Smartphones haben heutzutage die Rechenleistung von Desktopcomputern, und Standards wie HTML5, CSS3 und JavaScript haben sich deutlich weiterentwickelt. Im Jahr 2015 prägte Google den Begriff Progressive Web-Apps (PWAs). PWAs sind Webanwendungen, die sich wie Apps anfühlen, aber über den Browser und ohne den Umweg über die App-Store-Installation aufgerufen werden können. Google hat genaue Vorstellungen, was eine Webseite zur Progressive Web-App macht. PWAs sollen sich für Nutzer wie native Apps anfühlen: Sofortiges Laden von Inhalten ohne spürbare Verzögerung, Offlinefähigkeit und die Möglichkeit, die App auf dem Homescreen zu installieren. Es geht sogar noch weiter. Progressive bedeutet in diesem Kontext, dass eine grundlegende Version der Seite auch auf Browsern funktioniert, die nicht die modernsten Standards unterstützen. Somit schließen PWAs die Lücke zwischen nativen Apps und mobilen Seiten. Für ältere Browser wird die Seite zumindest rudimentär angezeigt. Unterstützt ein Browser alle notwendigen Features, wird dem Nutzer ein App-ähnliches Erlebnis geboten. Um dieses Ziel umzusetzen, sind einige neue Konzepte notwendig, die im Folgenden besprochen werden.

Service Worker

Service Worker sind lichtscheu. Als Nutzer einer Web-App hat man normalerweise keinen direkten Kontakt zu ihnen. Dennoch sind sie ein wichtiger Teil der Magie, die dafür sorgt, dass eine PWA offlinefähig ist oder Notifications anzeigen kann. Um Service Worker weiter zu entzaubern: Sie sind schlichtweg JavaScript-Programme, die im Browser laufen.

Argwöhnische Leser mögen nun fragen, wie sich Service Worker von normalen Skripten auf Webseiten unterscheiden. Der größte Unterschied liegt im Kontext der Service Worker: Es handelt sich um globale Skripte, die nicht an eine bestimmte Seite gebunden sind. Sie gelten generell für einen gesamten Ressourcenpfad einer Domain und können auch ohne eine Webseite ausgeführt werden. Durch den globalen Skriptkontext haben sie zudem keinen Zugriff auf das DOM (Document Object Model) der jeweiligen Seiten. Ganz bildlich kann man sich Service Worker als Schnittstelle zwischen der Webseite und dem Internet vorstellen. Derzeit spielen vor allem die drei folgenden Aspekte eine besondere Rolle.

Offlinefähigkeit

Service Worker können Ressourcenaufrufe der jeweiligen Seite übernehmen und durch gecachte oder modifizierte Responses ersetzen. Dem sicherheitsorientierten Leser mag es beim Gedanken an durch Dritte modifizierte Responses nun kalt den Rücken hinunterlaufen. Doch keine Sorge, um Service Worker einzusetzen, ist HTTPS Pflicht, um eventuelle Man-in-the-Middle-Angriffe zu verhindern.

Background-Sync

Stellen Sie sich folgende Situation vor: Sie nutzen den Webmailclient ihres E-Mail-Anbieters und tippen auf dem Smartphone eine lange E-Mail. Just in dem Moment, in dem Sie auf Absenden drücken, verliert Ihr Smartphone die Mobilfunkverbindung und sowohl Ihre eingegebene E-Mail als auch Ihre gute Laune sind verschwunden. Mit einer Background-Sync-Implementierung wäre das nicht passiert. Background-Sync erlaubt es, Aktionen dann auszuführen, wenn das Netzwerk verfügbar ist. Das funktioniert sogar, wenn der Browser-Tab geschlossen oder der Anwender längst zu einer anderen Seite navigiert ist.

Push Notifications

Wird eine Webseite gerade nicht im Smartphonebrowser angezeigt, kann sie nicht mit dem Nutzer interagieren. Diese einfache wie offensichtliche Erkenntnis führt dazu, dass für Hinweismeldungen ein globaler Skriptkontext notwendig ist. Genau den bieten Service Worker. Mit dem Notifications-API ist es möglich, dem Anwender sowohl auf dem Desktopbrowser als auch auf dem Smartphone Benachrichtigungen anzuzeigen.

Wie progressiv soll es sein?

Um den Entwicklern das Leben zu erleichtern und die Qualität von PWAs insgesamt zu steigern, bietet Google mit Lighthouse ein Open-Source-Werkzeug an, mit dem man eine Webseite auf Herz und Nieren hinsichtlich der Anforderungen von PWAs prüfen kann. Lighthouse ist als Chrome Extension über den Chrome Web Store oder als Kommandozeilentool über npm install -g lighthouse verfügbar. Somit kann es direkt in Webpack Builds eingebunden werden, um regelmäßiges Feedback zu erhalten.

Lighthouse bietet von Haus aus einen Satz an Metriken, mit dem es eine Untersuchung für eine gegebene Seite durchführt. Zu diesen Metriken gehört unter anderem der Test, ob die Seite Inhalte bei nicht vorhandener Internetverbindung anzeigt oder wie sie mit schwankender Leitungsgeschwindigkeit umgeht. Auch die Zeit, die für das Rendern vergeht, bis der Nutzer etwas Sinnvolles angezeigt bekommt, wird von Lighthouse gemessen. Features wie Secure Origin und Barrierefreiheitsaspekte werden ebenfalls getestet. Das Testergebnis zeigt Lighthouse auf einer übersichtlichen HTML-Seite oder als JSON-Datei an. Die Analyse wird in verschiedene Kategorien mit einem jeweiligen Score von 0 bis 100 aufgeschlüsselt. Die Kategorien umfassen derzeit:

  • Progressive Web-App: Service-Worker-Test, Offlinefähigkeit, Ladegeschwindigkeit unter 3G-Bedingungen, Anbieten eines Splash-Screens, Einfärben der Adresszeile in den Farben der Seite)
  • Performance: diverse Metriken zur Renderzeit
  • Barrierefreiheit: Annotationen für Bilder und Links, damit Screen-Reader sie vorlesen können
  • generelle Best Practices: Verwendung von HTTP2, keine direkte Nachfrage nach der Location des Nutzers oder ob Benachrichtigungen angezeigt werden dürfen

Der Report gibt für jeden einzelnen Analysepunkt Hinweise und Tipps, wie der Score verbessert werden kann. Mit diesem Rüstzeug gewappnet, können wir uns nun mit der Frontend-Technologie React beschäftigen, um damit eine PWA zu entwickeln.

React

Die Frontend-Bibliothek React erblickte 2013 als Open-Source-Bibliothek das Licht der Welt. Damals setzte Facebook massiv auf den Einsatz einer modifizierten PHP-Erweiterung namens XHP. Ein Software Engineer bekam die Erlaubnis, XHP im Browser in JavaScript zu implementieren, um das Problem der performancehungrigen Roundtrips des serverseitig erzeugten HTML-Codes zu entschärfen: React war geboren.

Vier Jahre später ist React eine der beliebtesten Frontend-Bibliotheken. Viele namhafte Webseitenbetreiber wie Instagram, Netflix, Imgur oder Walmart setzen auf die Oberflächenbibliothek. React bietet von Haus aus nur User Interfaces. Somit deckt die Bibliothek nur den View-Teil im Model-View-Controller-Pattern ab. Das hat den großen Vorteil, dass keine Architektur vorgeschrieben ist und man React auch sehr flexibel in bestehende Anwendungen integrieren kann. Zudem gibt es eine riesige Auswahl an zusätzlichen Bibliotheken und Frameworks, um React zu einem vollwertigen Paket anzureichern.

International PHP Conference 2018

Getting Started with PHPUnit

by Sebastian Bergmann (thePHP.cc)

Squash bugs with static analysis

by Dave Liddament (Lamp Bristol)

API Summit 2018

From Bad to Good – OpenID Connect/OAuth

mit Daniel Wagner (VERBUND) und Anton Kalcik (business.software.engineering)

React verfolgt einen komponentenorientierten Ansatz. Der von React propagierte Ansatz ist es, das UI einer Seite in unabhängige und wiederverwendbare Einheiten zu schneiden. Diese Komponenten stehen in Eltern-Kind-Beziehungen zueinander. Ein besonderes Schmankerl nennt sich Virtual DOM. Das Document Object Model ist die baumstrukturierte Repräsentation eines HTML-Dokuments. Möchte man im HTML-Dokument beispielsweise einen Zähler in einem Textfeld hochsetzen, muss dafür das DOM geändert werden. Bei komplexen Webseiten ist das Neuschreiben des gesamten DOM eine zeitintensive Angelegenheit. Dank des Virtual DOM verändert React nur die Teile des DOM, die sich wirklich geändert haben. Dadurch können Webanwendungen extrem performant im Browser ausgeführt werden.

Performance ist jedoch nur ein Aspekt, auf den die React-Schöpfer Wert legen. Es geht auch darum, gut lesbaren und verständlichen JavaScript-Code zu schreiben. Aus diesem Grund verfolgt React einen deklarativen Ansatz. Als Entwickler beschreibt man, wie eine Komponente funktionieren und aussehen soll. Die React-Bibliothek übernimmt anhand dieser Beschreibungen das Instanziieren von JavaScript-Objekten. Um die Lesbarkeit weiter zu steigern und damit sich Webentwickler wie zu Hause fühlen, hat Facebook eine JavaScript-Erweiterung namens JSX veröffentlicht. JSX ermöglicht es, XML-Fragmente direkt im JavaScript-Code zu verwenden. Somit kann die HTML-Syntax direkt im JavaScript-Code genutzt werden und es kommt einem vor, als würde man HTML schreiben, obwohl man gerade in React entwickelt. Listing 1 zeigt eine einfache Komponente für eine statische To-do-Liste.

class ToDoComponent extends React.Component {
  render() {
    return (
      <div className="todo-list">
        <h1>ToDo-Liste für {this.props.name}</h1>
        <ul>
          <li>Einkaufen</li>
          <li>Putzen</li>
       </ul>
      </div>
    );
  }
}

Im JavaScript-Code würde die Komponente folgendermaßen benutzt werden:

<ShoppingList name="Heinz" />

Kritische Zeitgenossen mögen nun fragen, wie der Name Heinz in die Komponente kommt und was das props-Objekt damit zu tun hat. Im Gegensatz zu Angular – dort gibt es Two-Way Data Binding – setzt React auf das Prinzip des One-Way Data Flow. Daten fließen immer unidirektional von ihren Erzeugern zu den Empfängern. React unterteilt die Daten einer Komponente in zwei unterschiedliche Kategorien: State und Props (kurz für Properties).

React-Anfänger tun sich am Anfang manchmal schwer damit, diese auseinanderzuhalten. Properties sind Daten, die sich innerhalb der Komponente nicht ändern und die von außen zugeführt werden. Der State enthält Daten, die sich im Lebenszyklus einer Komponente verändern können. Soll eine Komponente beim Rendern beispielsweise stets eine bestimmte Hintergrundfarbe haben, könnte das über eine Property abgebildet werden. Soll der Farbcode über ein Textfeld verändert und gespeichert werden, würde hierfür der State verwendet werden. Ein weiteres Prinzip für die Datenhaltung nennt sich Data-down-Actions-up. Dieses Prinzip erklärt den Datenfluss innerhalb einer Hierarchie von Komponenten. Daten fließen stets von Elternkomponenten zu Kindkomponenten. Möchten die Kindkomponenten jene Daten abändern, tun sie das nicht selbstständig, sondern lösen Actions aus, die die Änderung der Daten in den Elternkomponenten veranlassen.

Create-React-App

Wer neu im JavaScript-Universum ist, verzweifelt am Anfang schnell an der schieren Masse an Tools und deren Konfigurationsdateien. React-Neulinge scheitern oft schon am Aufbau des Set-ups mit Webpack, Babel und Konsorten, bevor sie auch nur eine Zeile React-Code schreiben. Anfangs stellte die Community Skripte zur Verfügung, um das initiale Aufsetzen eines React-Projekts zu erleichtern. Mittlerweile bietet Facebook dafür auch ein offizielles Werkzeug an. create-react-app soll die Erstellung und Konfiguration von React Apps erleichtern und unterstützen. So kann mit einem Terminalbefehl eine vollständige Web-App erzeugt werden. Das Tool lässt sich über den Node-Befehl npm install -g create-react-app installieren.

Mit dem Befehl create-react-app todo-liste legen wir ein einfaches React-Basisprojekt an. Das erzeugte Projekt bringt bereits die komplette Build-Konfiguration mit. Somit kann man sich vor allem zu Beginn voll auf React konzentrieren. Besonders nützlich ist, dass create-react-app bereits eine umfangreiche Unterstützung für Progressive Web-Apps mitbringt. So wird im public-Ordner des Projekts automatisch eine Manifestdatei erzeugt und mit Standardangaben über Icons, Farbthemen und Bezeichner der App gefüllt. Wer nicht jetzt schon vor Glück jubelt, wird über eine weitere Sache entzückt sein: Im src-Ordner des Projekts erzeugt create-react-app automatisch ein Service-Worker-Skript namens registerServiceWorker.js und registriert es in der Datei index.js. Dieses Skript ist nur im Produktionsmodus aktiv und übernimmt das Caching und Ausliefern der Assets der Web-App. Damit ist unsere App in unterstützten Browsern wie Chrome und Firefox schon offlinefähig. Mit den Kommandos cd todo-liste und npm start startet die Webanwendung im Development-Modus und kann im Browser unter http://localhost:3000/ aufgerufen werden (Abb. 1).

Abb. 1: Die von create-react-app erzeugte Basisanwendung

Abb. 1: Die von create-react-app erzeugte Basisanwendung

Erstellung einer einfachen To-do-Listen-Anwendung in React

Wir beginnen mit der ziemlich unspektakulären Beispielanwendung, die create-react-app erzeugt hat, und erweitern sie zu einer einfachen To-do-Listen-Anwendung. Ziel soll sein, dass Einträge über ein Textfeld zu der To-do-Liste hinzugefügt werden. Gemäß der React-Denkweise beginnen wir nun, die Anwendung in verschiedene Komponenten zu zerteilen. Die erste Komponente besteht aus dem Textfeld und dem Button, mit dem neue Einträge zur Liste hinzugefügt werden. React bietet verschiedene Wege an, um Komponenten zu definieren: Functional Components und Class Components. Eine Functional Component ist schlichtweg eine JavaScript-Function, die eine React-Komponente beschreibt. Class Components nutzen ECMAScript-2015-Klassen, um Komponenten zu beschreiben, und bieten mehr Möglichkeiten in Bezug auf den State und den Lebenszyklus der Komponente. Für die Eingabekomponente der To-do-Liste verwenden wir eine Class Component, da wir im State die Einträge unserer Liste speichern werden. Die Klasse TodoList fügen wir unterhalb der Importanweisungen in die von create-react-app generierte Datei App.js im Ordner src ein (Listing 2).

class TodoList extends React.Component {
  render() {
    return (
      <div className="todoListMain">
        <div className="header">
          <form>
            <label>
              Zu erledigen: 
              <input ref={input => (this._inputElement = input)} />
            </label>
            <button type="submit">
              Hinzufügen
            </button>
          </form>
        </div>
      </div>
    );
  }
}

Wer nun erneut die Adresse http://localhost:3000 öffnet, wird sich verwundert die Augen reiben. Wir sehen immer noch die Demostartseite, an der sich nichts geändert hat. Aus gutem Grund. Wir haben zwar eine Komponente definiert und beschrieben, wie sie gerendert werden soll. An welcher Stelle das geschehen soll, haben wir React allerdings nicht mitgeteilt.
Das lässt sich schnell nachholen, in dem wir die Klasse App so ändern, dass sie unsere TodoList-Komponente benutzt. Dazu können wir beispielsweise die Codestelle <p className=“App-intro“> in der Render-Funktion der Klasse App folgendermaßen verändern:

<div className="App-intro">
  <TodoList />
</div>

Der Browser sollte die veränderte Seite nun dank des von create-react-app konfigurierten Hot Reloadings automatisch neu laden. Hinzufügen lassen sich die Einträge für die To-do-Liste jedoch noch nicht. Dazu benötigen wir einen Ablageort, an dem wir die Einträge für die To-do-Liste speichern. Hierfür eignet sich der State der Komponente. Somit definieren wir in der Klasse TodoList zuerst ein leeres items-Objekt state (Listing 3).

state = {
  items: []
};
Nun erstellen wir in TodoList eine Funktion, die beim Klick auf den Hinzufügen-Button ausgelöst werden soll.
addItem = e => {
  const itemArray = this.state.items;
  itemArray.push({
      text: this._inputElement.value,
      key: Date.now()
  });
    
  this.setState({
      items: itemArray
  });
    
  this._inputElement.value = "";
  e.preventDefault();
};

Damit die Funktion beim Abschicken des Formulars ausgelöst wird, muss der <Form>-Tag in der Klasse TodoList noch entsprechend geändert werden:

<form onSubmit={this.addItem}>

Die Funktion addItem erzeugt ein Objekt mit den Attributen text und key. Der key ist wichtig, da React sonst mehrere Listeneinträge nicht eindeutig voneinander unterscheiden kann. Der setState-Aufruf sorgt offensichtlich dafür, dass React die übergebenen Objekte im State der Komponente ablegt. Dabei geht das Framework äußerst clever vor und sammelt asynchron eventuell erst mehrere dieser Aufrufe, um den State zu ändern. Aufmerksame Leser mögen nun die Frage stellen, woher _inputElement stammt. Tatsächlich haben wir es schon im Inputelement des Formulars der Klasse TodoList definiert:

ref={input => (this._inputElement = input)}
Refs sind Referenzen auf DOM-Objekte. Wir verwenden hier die Referenz auf das Inputfeld, um den Inhalt auszulesen und das Textfeld danach zu leeren. Das häufige Verwenden von Refs sehen die React-Macher allerdings als Antipattern, da Änderungen hauptsächlich über Events geschehen sollen. Nach einem Reload des Browsers wird der State unserer Komponente zwar intern aktualisiert, angezeigt werden die Einträge jedoch noch nicht. Dafür erstellen wir eine weitere Komponente (Listing 4).

class TodoItems extends React.Component {
  render() {

    var todoEntries = this.props.entries;
    function createTodos(item) {
      return (
        <li key={item.key}>
          {item.text}
        </li>
      );
    }
    var listItems = todoEntries.map(createTodos);
    return (
      <ul>
        {listItems}
      </ul>
    );
  }
}

TodoItems bekommt ein entries-Array als Prop der Einträge übergeben und gibt diese als Liste zurück. Wir binden die Komponente im JSX von TodoList nach dem </form>-Tag mit <TodoItems entries={this.state.items} /> ein. Nach dem Neuladen der Anwendung können der To-do-Liste Einträge hinzugefügt werden (Abb. 2).

Abb. 2: Eine einfache To-do-Listen-Anwendung

Abb. 2: Eine einfache To-do-Listen-Anwendung

Test der bestehenden Anwendung mit Lighthouse

Über den Terminalbefehl npm run build erzeugen wir ein Produktionsrelease der Web-App. Dadurch wird zum Beispiel der Service Worker aktiviert. Mit npm install -g serve installieren wir nun einen einfachen Webserver und starten die Anwendung mit dem Befehl serve -s build. Über http://localhost:3000 ist die Web-App lokal erreichbar. Über Generate Report des Lighthouse-Plug-ins im Browser kann nun eine Analyse der Seite gestartet werden (Abb. 3). Unsere Seite erreicht in der Kategorie Progressive Web-App bereits einen sehr guten Score mit 82 von 100 möglichen Punkten. Der komplette Code der Beispielanwendung ist hier zu finden.

 

Abb. 3: Ausschnitt einer Analyse mit Lighthouse

Abb. 3: Ausschnitt einer Analyse mit Lighthouse

Weitere Optimierungsmöglichkeiten

React ist die optimale Bibliothek zum Aufbau einer Progressive Web-App. Mit create-react-app ist es ein Kinderspiel, ein einfaches Grundgerüst einer PWA auf die Beine zu stellen. Entwickler vermeiden dadurch am Anfang viele Fehlerquellen und können sich vor allem auf die Entwicklung der App konzentrieren. Wer tiefer einsteigen möchte, kann das durch den Eject-Befehl tun. In diesem Fall schreibt create-react-app alle Konfigurationsdateien in das Projektverzeichnis, und der Nutzer kann sich von nun an um die Konfiguration der Anwendung kümmern. Notwendig ist dies zum Beispiel, wenn die App Push Notifications verwenden soll. Darüber hinaus bietet die Community auch andere Werkzeuge wie das react-starter-kit oder react-boilerplate zum einfachen Erstellen einer Progressive Web-App an.

Wenn es nach Google geht, sind Progressive Web-Apps die nächste App-Evolutionsstufe. Konkurrent Apple scheint davon noch nicht ganz überzeugt zu sein, denn viele wichtige Konzepte wie Service Worker werden auf iOS-Geräten noch nicht unterstützt. Somit sind Progressive Web-Apps derzeit eine eher kleine Revolution. Sie sind jedoch definitiv ein Schritt in Richtung eines offeneren mobilen Webs, denn die große Zeit der Desktopcomputer und Laptops ist definitiv vorbei. Stark angepasste mobile Webseiten mit Offlinefähigkeit haben das Potenzial, Apps langfristig den Platz streitig zu machen. Somit war Steve Jobs Vision der Web-Apps nur eine Dekade zu früh.

PHP Magazin

Entwickler MagazinDieser Artikel ist im PHP Magazin erschienen. Das PHP Magazin deckt ein breites Spektrum an Themen ab, die für die erfolgreiche Webentwicklung unerlässlich sind.

Natürlich können Sie das PHP Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

X
- Gib Deinen Standort ein -
- or -