HTML5-Tutorium: JavaScript: Hello World 04: Unterschied zwischen den Versionen

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Kowa (Diskussion | Beiträge)
Kowa (Diskussion | Beiträge)
Zeile 272: Zeile 272:
module.exports =
module.exports =
{ entry:
{ entry:
   { html: absolutePath('src/index.html'),
   { index: absolutePath('src/index.html'),
     head: absolutePath('src/css/head.css'),
     head: absolutePath('src/css/head.css'),
     main: absolutePath('src/js/main.js'),
     main: absolutePath('src/js/main.js'),
   },
   },


Zeile 283: Zeile 283:
    
    
   plugins:
   plugins:
   [ new SuppressChunksPlugin(['html', 'head']) ],
   [ new SuppressChunksPlugin(['index', 'head']) ],
    
    
   module:
   module:
Zeile 330: Zeile 330:


* <code>entry</code>: JavaScript-Dateien, die gebündelt und komprimiert werden sollen. Man muss jeweils nur die Wurzeldatei angeben. Webpack sucht in dieser Datei rekursiv nach Import-Befehlen und packt die benötigten Dateien ebenfalls in die Ausgabedatei.
* <code>entry</code>: JavaScript-Dateien, die gebündelt und komprimiert werden sollen. Man muss jeweils nur die Wurzeldatei angeben. Webpack sucht in dieser Datei rekursiv nach Import-Befehlen und packt die benötigten Dateien ebenfalls in die Ausgabedatei.
* <code>output</code>: Hier wird festgelegt, wie die gepackten Dateien heißen sollen. Für jeden
* <code>output</code>: Hier wird festgelegt, wie die gepackten Dateien heißen sollen. Für jeden Eintrag im Attribute <code>entry</code> wird eine Datei mit dem Namen <code>web/js/[name].js</code>
Eintrag im Attribute <code>entry</code> wird eine Datei mit dem Namen <code>web/js/[name].js</code>
erzeugt, wobei <code>[name]</code> durch den Schlüsselname des Entry-Eintrags ersetzt wird.
erzeugt, wobei <code>[name]</code> durch den Schlüsselname des Entry-Eintrags ersetzt wird.
In der obigen Datei gibt es nur drei Entry-Elemente.
In der obigen Datei gibt es drei Entry-Elemente.
* <code>plugins</code>: Hier werden Webpack-Hilfsmodule geladen, die bestimmte Zusatzaufgaben erledigen. Sie verwenden nur das Modul <code>SuppressChunksPlugin</code>. Dieses löscht die Dateien <code>web/js/html.js</code> und <code>web/js/head.js</code>.
* <code>plugins</code>: Hier werden Webpack-Hilfsmodule geladen, die bestimmte Zusatzaufgaben erledigen. Sie verwenden nur das Modul <code>SuppressChunksPlugin</code>. Dieses löscht die Dateien <code>web/js/index.js</code> und <code>web/js/head.js</code>,  nachdem die Dateien <code>web/js/index.html</code> und <code>web/js/head.css</code> daraus mittels gewissen Loadern extrahiert wurden. Grundsätzlich generiert Webpack für jeden Entry eine JavaScript-Datei, egal, um welches Inputformat es sich handelt. Wenn man einen anderen Dateityp benötigt, muss man diese aus der zugehörigen JavaScript-Datei extrahieren und kann letztere danach mittels <code>SuppressChunksPlugin</code> löschen.  
* <code>module</code>: In diesem Attribut werden Regeln (<code>rules</code>) angegeben,
* <code>module</code>: In diesem Attribut werden Regeln (<code>rules</code>) angegeben,
die festlegen, auf welche Weise eine Datei transformiert werden sollen. Bei jeder Regel gibt
die festlegen, auf welche Weise eine Datei transformiert werden sollen. Bei jeder Regel gibt
es einen Test, der festlegt, für welche Dateien die jeweilige Regel zutrifft. Mit Hilfe eines
es einen Test, der festlegt, für welche Dateien die jeweilige Regel zutrifft. Mit Hilfe eines
[[regulärer Ausdruck|regulärer Ausdrücke]] wird im Tutoriumsbeispiel geprüft, ob die aktuelle
[[regulärer Ausdruck|regulärer Ausdrücke]] wird im Tutoriumsbeispiel geprüft, ob die aktuelle
Datei auf <code>.json</code>, <code>.js</code> oder <code>.css</code> endet. Je nach der
Datei auf <code>head.css</code>, <code>mains.css</code> oder <code>.html</code> endet. Je nach der
Endung wird ein anderer Loader zum Laden und Verarbeiten der Datei verwendet.
Endung werden anderer Loader zum Laden und Verarbeiten der zugehörigen Datei verwendet.
(Für Eebpack gibt es diverse Loader, einige davon haben Sie zuvor mit Hilfe von <code>npm</code> installiert.)
(Für Webpack gibt es diverse Loader, einige davon haben Sie zuvor mit Hilfe von <code>npm</code> installiert.
Leider gibt es soviel Loader, dass die Auswahl geeineter Loader nicht gerade einfach ist. Erschwerden kommt hinzu, dass sich mit jeder neuen Hauptversion von Webpack nicht nur die Struktur der Konfigurationsdatei, sonder auch die verfügbaren Loader und Plugins verändern. :-( )


JSON-Dateien werden mit dem JSON-Loader geladen und in die Ergebnis-Datei (<code>app.bundle.js</code>)
eingefügt.


JavaScript-Dateien werden mit dem Babel-Loader geladen und transformiert.  
CSS-Dateien werden zunächst mit dem CSS-Loader geladen und verarbeitet. Anschließend wird die Datei
Auf welche Weise der Babel-Loader dies macht wird nicht mehr in der Datei <code>.babelrc</code>
<code>main.css</code> vom Style-Loader Anweisungen in die  Datei <code>main.js</code> geschrieben.
festgelegt, sondern direkt in der Datei <code>webpack.config.js</code>. Der Loader kann
Damit wird erreicht, dass die Datei  <code>main.css</code> in der Datei <code>main.js</code>
auch Dateien laden, die sich im Verzeichnis <code>node_modules</code> befinden.
enthalten ist. Sobald <code>main.js</code> geladen wird, „injiziert“ sie  
Die dort gespeicherten Module sorgen allerdings selbst dafür, dass sie ausführbaren
die CSS-Elemente dieser JavaScript-Datei direkt in den HTML-Head-Bereich
Web-Code bereitstellen. Daher sollten sie vom Babel-Loader nicht noch einmal transformiert werden.
der zugehörigen HTML-Datei. Das heißt, die CSS-Datei wird dynamisch von <code>main.js</code>
Mit <code>ignore: '/node_modules/'</code> wird also nicht festgelegt, dass Babel die
geladen. Genau das wollten wir erreichen.
benötigten Dateien nicht lädt, sondern es wird festgelegt, dass sie nicht noch einmal transformiert werden.


CSS-Dateien werden zunächst mit dem CSS-Loader geladen und verarbeitet. Anschließend werden vom
Die Datei <code>head.css</code> soll nicht dynamisch geladen werden. Sie wird daher mit Hilfe
Style-Loader Anweisungen in die  Datei <code>app.bundle.js</code> geschrieben, die dafür sorgen,
des Extract- und des File-Loaders aus der zugehörigen JavaScript-Datei extrahiert und unter
dass die CSS-Elemente beim Laden dieser JavaScript-Datei direkt in den HTML-Head-Bereich
dem Namen <code>web/css/head.css</code> gespeichert.
der zugehörigen HTML-Datei eingefügt (''injiziert'') werden.


Analog wird die Datei <code>src/index.html</code> mittels eines HTML-Loaders in eine JavaScript-Datei
geladen und von dort mit dem Extract-Loader extrahiert.  Mittels des File-Loaders wird sie schließlich unter
dem Namen <code>web/index.html</code> gespeichert.
Dieses Vorgehen klingt zunächst einmal so, als ob man mit Kanonen auf Spatzen schießen würde.
Warum kopiert man beispielsweise die Dateien nicht einfach direkt vom Ordner <code>src</code>
in den Ordner <code>web</code>. Ganz eingach: Die Loader können noch viel mehr, als eine Datei nur
zu kopieren. Sie können Dateien, die von diesen referenziert werden integrieren. Zum Beispiel wird
die Datei <code>greet.js</code> automatisch in die Datei <code>main.css</code> eingefügt, so dass
index.html nur eine einzige JAvaScript-Datei laden muss und nicht zwei. (Bei großen Projekten
werden so Dutzende von Dateien in eine einzige gepackt.) Außerdem kann man mit
speziellen Anweisungen Webpack dazu bringen, die erzeugten JavaScript-/HTML-/CSS-Dateien
zu komprimieren, so dass das DAtenvolumen teils drastisch reduziert wird.
/
Am Ende der Konfigurations-Datei, gibt es noch eine Plugin-Liste, die dazu genutzt werden kann,
Am Ende der Konfigurations-Datei, gibt es noch eine Plugin-Liste, die dazu genutzt werden kann,
dateiunabhängige Transformationen an der Ergebnis-Datei (<code>app.bundle.js</code>)
dateiunabhängige Transformationen an der Ergebnis-Datei (<code>app.bundle.js</code>)

Version vom 31. Oktober 2018, 18:34 Uhr

Dieser Artikel erfüllt die GlossarWiki-Qualitätsanforderungen nur teilweise:

Korrektheit: 3
(zu größeren Teilen überprüft)
Umfang: 4
(unwichtige Fakten fehlen)
Quellenangaben: 3
(wichtige Quellen vorhanden)
Quellenarten: 5
(ausgezeichnet)
Konformität: 3
(gut)

Vorlesung WebProg

Inhalt | Teil 1 | Teil 2 | Teil 3 | Teil 4 | Teil 5 | Teil 6 | Vue 1 | Vue 2 | Vue 3 | Vue 4 | Vue 5 | Vue 6

Musterlösung: index.html, (SVN-Repository)

Anwendungsfälle (Use Cases)

Gegenüber dem dritten Teil des Tutoriums ändern sich die die Anwendungsfälle nicht. Die Anwendung leistet also genau dasselbe wie zuvor.

In diesem Teil des Tutoriums geht es darum, die Anwendung besser zu strukturieren, d. h. zu modularisieren.

Modularisierung

Eine Anwendung, wie z. B. ein HTML5-Spiel, besteht aus diversen unterschiedlichen Komponenten mit ganz unterschiedlichen Aufgaben. Bei einem HTML5-Spiel müssen die Spielfiguren, die Spielszenen, die Spiellogik, die Benutzereingaben, die Darstellung das Spiels im Browser etc. erstellt und verwaltet werden, wobei das Erstellen und Verwalten teilweise während der Spiel-Entwicklung und teilweise während der Spiel-Ausführung (Runtime) erfolgt. An der Entwicklung eines Spiels sind üblicherweise mehrere oder gar viele Entwickler gleichzeitig beteiligt. Oft müssen einzelnen Komponenten an neue Endgeräte oder Betriebssysteme angepasst werden. Beispielsweise muss eine Tastatursteuerung durch eine Touch- oder Gestensteuerung ersetzt werden, wenn ein Spiel so erweitert werden soll, dass es auch auf einem Smartphone oder Tablet läuft.

Das alles ist nur machbar, wenn man die Anwendung modularisiert, d. h. in kleine, möglichst unabhängige Komponenten unterteilt. Ein großer Feind der Modularisierung sind globale Variablen und Funktionen. Je mehr Dateien von unterschiedlichen Autoren erstellt werden, desto größer ist die Gefahr, dass es zu Namenskollisionen kommt. Daher gilt der Grundsatz: Verwende so wenig globale Größen wie möglich.

Erstellen eines neuen Projektes

Erstellen Sie ein neues Projekt „HelloWorld04“.

Kopieren Sie anschließend die Projektdateien (insbesondere den Ordner „web“ mit den Dateien „index.html“, „main.css“ und „main.jssowie die Datei .gitignore) aus dem dritten Teil des Tutoriums und passen Sie den Titel in der HTML-Datei an.

Erstellen Sie für Ihr Projekt ein Git-Repository, committen Sie und pushen Sie es dann auf den Git-Server.

Dadurch, dass die Datei .gitignore das Verzeichnis node_modules enthält, werden die Dateien, die in Zukunft in diesem Ordner gespeichert werden, nicht ins Git-Repository übertragen. Dies ist notwendig, da sich in diesem Ordner mehrere tausend ganz kleine Dateien befinden werden, und die Speicherung dieser Daten im Git-Repository Stunden in Anspruch nehmen kann. Dabei ist eine Speicherung gar nicht notwendig, da sie jederzeit mit dem Befehl npm install sehr schnell wieder hergestellt werden können.

BTW: Sollten Sie einmal eine Abschlussarbeit mit Node.js erstellen, fügen Sie bitte auf gar keinen Fall den Inhalt des Ordners node_modules in Ihr Abgabemedium (CD, DVD, USB-Stick ...) ein. Ihr Betreuer wird es Ihnen danken!

Erstellen einer Ordnerstruktur

Grundsätzlich gilt auch bei der Programmierung: Ordnung ist das halbe Leben.

Web-Anwendungen werden sehr schnell sehr groß. Also sollte man sich eine geeignete Ordnerstruktur überlegen. Üblicherweise legt man CSS-Dateien in einen Ordner namens „css“ und JavaScript-Dateien in einen Ordner namens „js“ oder „lib“. Sollten es viele CSS- und/oder JavaScript-Dateien werden, legt man im entsprechenden Ordner geeignete Unterordner an.

Legen Sie im Root-Verzeichnis Ihres Projektes folgende Ordner an (und beantworten Sie die Fragen, ob ein Ordner im Repository gespeichert werden soll, gegebenenfalls mit Yes):

  • src: Der Inhalt dieses Ordners enthält die Ausgangsdateien, die mit Hilfe von Node.js noch transformiert und dann in den Web-Ordner kopiert werden. Verschieben Sie die Datei web/index.html in diesen Ordner.
  • src/css: Hier werden die eigentlichen CSS-Dateien der Web-Anwendung gespeichert, die von der App dynamisch nachgeladen werden sollen. In diesem Ordner könnten zur weiteren Strukturierung Unterordner angelegt werden. Allerdings wird das in dieser Veranstaltung nicht notwendig sein. Es wird nur die Dateien „head.css“ und „app.css“ geben. Verschieben Sie die Datei web/main.css in diesen Ordner.
  • src/js: Hierher kommt die Datei „app.js“, die für den Start der Web-Anwendung zuständig ist. Üblicherweise liest sie weitere JavaScript-Dateien sowie eine oder mehrere JSON-Dateien mit Initialwerten ein, initialisiert die Anwendung und startet sie dann. Verschieben Sie die Datei web/main.js in diesen Ordner.
  • web: (Dieser Ordner existiert schon.) Der Inhalt dieses Ordners kommt später auf einen echten Web-Server.
  • web/css: Hierher kopiert Node.js die erzeugten CSS-Dateien. (In den Tutorien werden dort jeweils nur eine sehr kleine CSS-Dateien liegen. Diese sorgen dafür, dass der Browser während der Initialisierung einer App kein defektes Layout präsentiert. Der größte Teil des CSS-Codes ist in der Haupt-JavaScript-Datei der App enthalten.)
  • web/js: Hierher kopiert Node.js die erzeugten JavaScript-Dateien.

Wenn Sie jetzt die Datei src/index.html mittels run ausführen, sollte Ihre Anwendung immer noch laufen. WebStorm hat die Pfade in der Datei index.html, unter denen die Dateien main.css und main.js zu finden sind, automatisch angepasst. Vergleichen Sie die index.html-Dateien der HelloWorld-Versionen 3 und 4.

Fremde Bibliotheken und automatisch generierte JavaScript-Dateien enthalten häufig unsauberen Code, die Ihnen von WebStorm beim Speichern der Dateien im Repository angezeigt werden. In einer Rückfrage sollen Sie dann bestätigen, dass Sie die „fehlerhaften“ Dateien trotzdem speichern wollen.

Damit Ihnen bei einem Commit nur Fehler in von Ihnen erstellten Dateien angezeigt werden, schließen Sie den Ordner „web“ von der WebStorm-Fehlerüberwachung aus:

  • Rechtsklick auf den Ordner „web“ → Mark Directory asExcluded

Für den Ordner „node_modules“ brauchen Sie dies nicht zu machen. Dieser Ordner wird von WebStorm-Fehlerüberwachung automatisch ignoriert.

Sie sollten nach jeden größeren Entwicklungsschritt die Änderungen im Repository speichern:

  • Klick auf das Commit-Icon.

Hin und wieder ist auch ein git push angebracht. Es schadet übrigens nicht, vorher noch einmal zu überprüfen, ob die Anwendung immer noch läuft.

Die App

Laden der CSS- und der JavaScript-Dateien

Eine Web-Anwendung funktioniert nur – wie Sie bereits erfahren haben –, wenn nicht nur die HTML-Datei, sondern auch die zugehörigen CSS- und JavaScript-Dateien geladen werden. Allerdings gibt es dabei ein Problem: Die CSS- und JavaScript-Dateien werden im Laufe der Zeit immer zahlreicher und/oder größer. Diese Dateien zu laden, dauert seine Zeit.

In der dritten Version der Web-Anwendung stehen die Verweise auf diese Dateien im head-Bereich des Dokuments. Dieser wird vollständig geladen, bevor der body-Bereich eingelesen wird. (Genauer gesagt, nur die CSS-Datei wird vollständig geladen. Durch die Angabe von async="async" im Script-Tag wird erreicht, dass diese Datei geladen wird, während der Body-Bereich gerendert wird.)

Das heißt aber, dass der Browser keine Inhalte des HTML-Dokuments darstellen kann, solange er CSS-Datei (und evtl. weitere Dateien) lädt. Wenn dies zu lange dauert, verliert der Besucher die Geduld und verlässt die Seite vorzeitig.

Besser wäre es daher andersherum vorzugehen: Es wird zuerst der Body-Bereich geladen und dann die JavaScript- und die CSS-Dateien. Im Falle von JavaScript ist das durchaus sinnvoll (und durch die Angabe von async="async" auch machbar), aber im Falle von CSS hat das den Effekt, dass der Browser noch keine Layout-Vorgaben erhalten hat, wenn er mit dem Rendern der Seite beginnt. Also verwendet er die browserspezifischen Defaultwerte. Das heißt, die Seite sieht zunächst ganz anders aus, als vom Designer geplant. Wenn dann die CSS-Dateien geladen wurden, wird die Seite erneut gerendert und verändert ihr Aussehen. Auch das ist verwirrend und wirkt unprofessionell.

Was also machen?

CSS-Dateien

Damit das Problem mit dem falschen Layout nicht auftritt, empfiehlt Google, ganz wenige, wichtige CSS-Befehle direkt – d. h. als CSS-Befehle innerhalb eines style-Elements – in dem HTML-Head-Bereich einzufügen. Alle anderen CSS-Anweisungen sollen erst am Ende der HTML-Datei dynamisch mittels JavaScript (oder mittels des PageSpeed-Optimization-Modul von Google) gelesen werden.[1] (Früher hat Google ernsthaft empfohlen, das Link-Element ganz ans Ende der HTML-Datei zu stellen, also nach dem schließenden html-Tag. Das wäre aber natürlich nicht standardkonform.)

Gehen Sie so vor, wie es Google empfiehlt. Genauer gesagt, gehen Sie so vor, wie es unter Multimedia-Programmierung: Best Practices beschrieben ist. In diesem und den folgenden Tutorien werden die dort beschriebenen Prinzipien Prinzipien berücksichtigt.

Legen Sie die Datei web/css/head.css an und fügen Sie folgenden (sehr kurzen!) Code ein:

html, body
{ background-color: #C5EFFC; }

.hidden
{ display: none; }

In der Datei src/css/main.css können Sie das .hidden-Element und das Farbattribut background-color: #C5EFFC; entfernen.

In der Datei web/index.html müssen Sie das Link-Element in Header folgendermaßen

<link rel = "stylesheet" type = "text/css" href = "css/head.css"/>

anpassen. Das heißt, Sie laden im HTML-Head-Bereich nur noch die kleine CSS-Datei. Damit funktioniert Ihre Web-Anwendung derzeit nur noch rudimentär: Es gibt kaum noch CSS-Anweisungen, die berücksichtigt werden.

JavaScript-Dateien

Für JavaScript-Dateien empfiehlt Google ebenfalls, diese nicht schon zu Beginn zu laden.[2] Dieser Vorschlag war bis vor kurzem noch sehr sinnvoll. Man könnte das Skript-Element aus dem HTML-Header-Bereich entfernen und vor dem schließenden Body-Tag einfügen. Seitdem die meisten Browser die Async- und Defer-Attribute im Skript-Element unterstützen, ist dies nocht mehr notwendig. Und Sie haben ja schon ein Async-Attribut in Ihr Skript-Element eingefügt.

Was Sie aber machen sollten, ist, in das erste Section-Tag im Body das Attribut class="hidden" einzufüegen, damit man auch dieses Element beim Laden der Anwendung zunächst nicht sieht. In einer Anwendung, die zum Laden etwas länger dauert, würde man einen so genannten Splash Screen sichtbar einfügen und diesen beim eigentlichen Start der App ausblenden.

Wenn Sie nun die Web-App testweise starten, sollten Sie eine himmelblaue, aber ansonsten leere Seite sehen.

Fügen Sie nun den Befehl

document.getElementById('section_form').classList.remove('hidden');

als letzen Befehl in den Rumpf der Funktion init in der Datei src/js/main.js ein.

Wenn Sie jetzt die Web-App starten, sollte sie wieder funktionieren (wenn auch mit einem sehr CSS-armen Layout).

Was haben Sie erreicht? Solange die Anwendung geladen wird, sehen Sie nur einen blauen Hintergrund ohne Inhalt. (Besser wäre – wie gesagt – ein Splash Screen.) Wenn dann alles, d. h. alle CSS- und JavaScript-Dateien geladen wurden, wird die eigentliche Anwendung sichtbar. Dies ist bei großen Anwendungen, wie einem Spiel, eine übliche Vorgehensweise.

Vergessen Sie nicht:

  • Klick auf das Commit-Icon.

Modularisierung der JavaScript-Dateien

Die JavaScript-Datei main.js aus dem dritten des Tutoriums wird in zwei Teile aufgespalten: greet.js und main.js. Die erste Datei enthält die eigentliche Begrüßungsfunktionalität, die zweite initialisiert die App, d. h. aktiviert die Begrüßungsfunktion.

Erstellen Sie die JavaScript-Datei src/js/greet.js und verschieben Sie die beiden Funktionen sayHello und sayHelloOnEnter (jeweils samt Funktionsrumpf :-) ) in diese Datei.

Schreiben Sie vor jede der beiden Funktionsdefinitionen das Schlüsselwort export. Mit dieser ES-6-Anweisung legen Sie fest, dass die beiden Funktionen von anderen ES-6-Dateien mittels eines Import-Befehls importiert werden können. (Andere Funktionen ohne Export-Anweisung könnten von anderen ES-6-Dateien nicht importiert werden.)

In der Datei src/js/main.js werden jetzt diese beiden Funktionen importiert. Fügen Sie ganz am Anfang der Datei (d. h. vor der verbliebenen Init-Funktion) die folgende Improt-Anweisung ein:

import * as greet from './greet.js';

Damit wird erreicht, dass alle (*) Elemente, die von der Datei greet.js exportiert werden, unter ihrem jeweiligen Namen im Objekt greet gespeichert werden. (Man könnte auch nur bestimmte Elemente von greet.js importieren, sofern man nicht alle benötigt.) Das heißt, innerhalb der Datei main.js gibt es nun die beiden Funktionen greet.sayHello und greet.sayHelloOnEnter.

Damit die Web-Anwendung wieder funktioniert. müssen Sie also in der Datei main.js vor jede Verwendung dieser beiden Funktionen greet. einfügen. (WebStorm zeigt ihnen durch unterringeln an, dass die Funktionen sayHello und sayHelloOnEnter nicht existieren. Wenn Sie jeweils greet. davor einfügen, verschwinden die Fehlermarkierungen.)

Fügen Sie jetzt noch das Attribut type="module" in das Script-Element in der Datei src/index.html ein. Damit sollte Ihre Anwendung wieder funktionieren. Leider macht Ihnen WebStorm einen Strich durch die Rechnung. Aus „Sicherheitsgründen“ unterbindet WebStorm das dynamische Laden der Datei greet.js durch den Browser. Sie werden durch ein Pop-up-Fenster aufgefordert, eine “authorization URL” zum Clipboard zu kopieren. Wenn Sie das tun, ist es schon zu spät. Der Browser konnte die Datei nicht laden und daher funktioniert die Web-App nicht (wenn Sie sie von WebStorm aus starten; auf einem anderen Server würde sie schon funktionieren, wenn Sie alles richtig gemacht haben).

Gewöhnen Sie WebStorm dieses Verhalten ab:

  • FileSettings/PreferencesBuild, Execution, DeploymentDebugger
  • Häkchen bei Allow unsignen requests
  • OK

Jetzt sollte die Anwendung wieder laufen (auch wenn Sie sie von WebStorm aus starten).

Vergessen Sie nicht:

  • Klick auf das Commit-Icon.

Node.js und Webpack

Jetzt müssen Sie nur noch die CSS-Datei main.css laden, dann funktioniert Ihre Anwendung wieder wie gewohnt. Und Sie ist überdies modular aufgebaut. (Sehr vielweite kann man eine Hello-World-Anwendung nicht modularisieren. :-) )

Google empfihlt, die eigentliche CSS-Datei dynamisch zu laden. Dazu gibt es zahreiche Möglichkeiten. Ich empfehle derzeit Webpack dafür einzusetzen[3].

Webpack ist ein sehr mächtiges Werkzeug, mit dem eine Vielzahl von Web-Dateien in einer oder einigen wenigen JavaScript-Dateien zusammengefasst und bei Bedarf auch komprimiert werden können. Es handelt sich um einen JavaScript module bundler, der sehr viel mehr kann, als nur CSS-Dateien dymanisch einzulesen.

Einer der Entwickler von Webpack ist Johannes Ewald, ein ehemaliger Student der HSA (vgl. https://github.com/jhnns). Er bietet auch regelmäßig Vorlesung an der HSA zum Thema „JavaScript“ an.

Node.js-Pakete

Öffnen Sie das WebStorm-Terminal und installieren Sie die benötigten Node.js-Pakete

npm init -y
# Name, Description, Repository, Autor, Lizenz und Keywords in package.json anpassen
# Achtung: Der Name darf nur Kleinbuchstaben, Zahlen 
#          und das Underscore-Zeichen enthalten.
npm install --save-dev webpack webpack-cli
npm install --save-dev html-loader css-loader style-loader  
npm install --save-dev file-loader extract-loader  
npm install --save-dev suppress-chunks-webpack-plugin

Führen Sie nun einen Git-Commit durch. Sorgen Sie dafür, dass auch die neu angelegten Dateien package.json und package-lock.json im Git-Repository gespeichert werden. Dies können Sie entweder beim Commit erzielen, indem Sie vor Unversioned Files ein Häkchen setzen oder indem Sie vor dem Commit für beiden Dateien Folgendes ausführen:

Rechtsklick auf den Dateinamen → GitAdd

Achtung, wenn Git Ihnen anbietet, den Ordner node_modules zu speichern, haben Sie die Datei .gitignore vergessen. Fügen Sie in diesem Fall unbedingt die Datei .gitignore in Ihr Projekt ein!

Webpack

Nun muss noch Webpack konfiguriert werden, damit die Dateien web/css/head.css und web/js/mains.js automatisch generiert werden können.

Erstellen Sie im Wurzelverzeichnis des Projektes die Datei webpack.config.js (die Sie unbedingt auch in Git speichern sollten). Fügen Sie folgenden Code in diese Datei ein.

webpack.config.js

const
  path                    = require('path'),
  SuppressChunksPlugin    = require('suppress-chunks-webpack-plugin').default;

function absolutePath(l_path)
{ return path.resolve(__dirname, l_path); }

module.exports =
{ entry:
  { index: absolutePath('src/index.html'),
    head:  absolutePath('src/css/head.css'),
    main:  absolutePath('src/js/main.js'),
  },

  output:
  { filename: 'js/[name].js',
    path: absolutePath('web'),
  },
  
  plugins:
  [ new SuppressChunksPlugin(['index', 'head']) ],
  
  module:
  { rules:
    [ { test:   /head\.css$/,
        use:    [ { loader: 'file-loader',
                    options: { name: '[name].[ext]',
                               outputPath: 'css/'
                             }
                  },
                  'extract-loader',
                  'css-loader'
                ]
      },

      { test:    /main\.css$/,
        exclude: [/node_modules/, /web/],
        use:     [ 'style-loader',
                   'css-loader'
                 ]
      },
      
      { test:   /\.html$/,
        use:    [ { loader: 'file-loader',
                    options: { name: '[name].[ext]',
                               outputPath: '/'
                             }
                  },
                  'extract-loader',
                  'html-loader'
                ]
      },
    ]
  }
};

Es handelt sich um ein JavaScript-Datei, die direkt von Node.js interpretiert wird. Aus diesem Grund wird auch nicht das EcmaScript-6-Modul-Konzept eingesetzt, sondern CommonJS, das Standard-Modulkonzept vom Node. Hier werden Module mittels require und nicht mittels import importiert. Für Sie ist das allerdings nebensächlich, da Sie im Rahmen der Tutorien keine komplexen Konfigurationsdateien für Webpack implementieren werden.

Im Prinzip macht diese Datei nichts andere als ein Objekt zu exportieren, das Webpack-Konfigurationsinformationen enthält:

  • entry: JavaScript-Dateien, die gebündelt und komprimiert werden sollen. Man muss jeweils nur die Wurzeldatei angeben. Webpack sucht in dieser Datei rekursiv nach Import-Befehlen und packt die benötigten Dateien ebenfalls in die Ausgabedatei.
  • output: Hier wird festgelegt, wie die gepackten Dateien heißen sollen. Für jeden Eintrag im Attribute entry wird eine Datei mit dem Namen web/js/[name].js

erzeugt, wobei [name] durch den Schlüsselname des Entry-Eintrags ersetzt wird. In der obigen Datei gibt es drei Entry-Elemente.

  • plugins: Hier werden Webpack-Hilfsmodule geladen, die bestimmte Zusatzaufgaben erledigen. Sie verwenden nur das Modul SuppressChunksPlugin. Dieses löscht die Dateien web/js/index.js und web/js/head.js, nachdem die Dateien web/js/index.html und web/js/head.css daraus mittels gewissen Loadern extrahiert wurden. Grundsätzlich generiert Webpack für jeden Entry eine JavaScript-Datei, egal, um welches Inputformat es sich handelt. Wenn man einen anderen Dateityp benötigt, muss man diese aus der zugehörigen JavaScript-Datei extrahieren und kann letztere danach mittels SuppressChunksPlugin löschen.
  • module: In diesem Attribut werden Regeln (rules) angegeben,

die festlegen, auf welche Weise eine Datei transformiert werden sollen. Bei jeder Regel gibt es einen Test, der festlegt, für welche Dateien die jeweilige Regel zutrifft. Mit Hilfe eines regulärer Ausdrücke wird im Tutoriumsbeispiel geprüft, ob die aktuelle Datei auf head.css, mains.css oder .html endet. Je nach der Endung werden anderer Loader zum Laden und Verarbeiten der zugehörigen Datei verwendet. (Für Webpack gibt es diverse Loader, einige davon haben Sie zuvor mit Hilfe von npm installiert. Leider gibt es soviel Loader, dass die Auswahl geeineter Loader nicht gerade einfach ist. Erschwerden kommt hinzu, dass sich mit jeder neuen Hauptversion von Webpack nicht nur die Struktur der Konfigurationsdatei, sonder auch die verfügbaren Loader und Plugins verändern. :-( )


CSS-Dateien werden zunächst mit dem CSS-Loader geladen und verarbeitet. Anschließend wird die Datei main.css vom Style-Loader Anweisungen in die Datei main.js geschrieben. Damit wird erreicht, dass die Datei main.css in der Datei main.js enthalten ist. Sobald main.js geladen wird, „injiziert“ sie die CSS-Elemente dieser JavaScript-Datei direkt in den HTML-Head-Bereich der zugehörigen HTML-Datei. Das heißt, die CSS-Datei wird dynamisch von main.js geladen. Genau das wollten wir erreichen.

Die Datei head.css soll nicht dynamisch geladen werden. Sie wird daher mit Hilfe des Extract- und des File-Loaders aus der zugehörigen JavaScript-Datei extrahiert und unter dem Namen web/css/head.css gespeichert.

Analog wird die Datei src/index.html mittels eines HTML-Loaders in eine JavaScript-Datei geladen und von dort mit dem Extract-Loader extrahiert. Mittels des File-Loaders wird sie schließlich unter dem Namen web/index.html gespeichert.

Dieses Vorgehen klingt zunächst einmal so, als ob man mit Kanonen auf Spatzen schießen würde. Warum kopiert man beispielsweise die Dateien nicht einfach direkt vom Ordner src in den Ordner web. Ganz eingach: Die Loader können noch viel mehr, als eine Datei nur zu kopieren. Sie können Dateien, die von diesen referenziert werden integrieren. Zum Beispiel wird die Datei greet.js automatisch in die Datei main.css eingefügt, so dass index.html nur eine einzige JAvaScript-Datei laden muss und nicht zwei. (Bei großen Projekten werden so Dutzende von Dateien in eine einzige gepackt.) Außerdem kann man mit speziellen Anweisungen Webpack dazu bringen, die erzeugten JavaScript-/HTML-/CSS-Dateien zu komprimieren, so dass das DAtenvolumen teils drastisch reduziert wird. / Am Ende der Konfigurations-Datei, gibt es noch eine Plugin-Liste, die dazu genutzt werden kann, dateiunabhängige Transformationen an der Ergebnis-Datei (app.bundle.js) vorzunehmen. Derartige Plugins werden zunächst einmal nicht verwendet.

Damit steht einer Verwendung von Webpack nichts mehr im Wege. Man könnte Webpack nun direkt von der Kommandozeile aufrufen. Besser ist es allerdings NPM-Skript-Befehle zu verwenden.

NPM-Skripte

Fügen Sie das scripts>-Objekt der Datei package.json drei weitere Skript-Anweisungen ein:

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "bundle": "webpack",
  "compress": "webpack -p",
  "watch": "webpack --watch"
},

Geben Sie nun im WebStorm-Terminal die Anweisung npm run bundle ein.

Wenn Sie alles richtig gemacht haben, müsste Webpack fehlerfrei durchlaufen und die Datei web/js/app.bundle.js erstellt werden. Im Zweifelsfall müssen Sie auf das Reload-Icon unter der Menü-Leiste klicken, um sie zu sehen (View → Häkchen bei Toolbar). Wenn der Dateiname nicht grün, sondern braun eingefärbt ist, müssen Sie sie unter SVN-Kontrolle stellen:

  • Rechtsklick auf „app.bundle.js“ → SubversionAdd to VCS

Wenn Sie jetzt die Datei index.html im Browser öffnen, sollten Sie nach kurzer Zeit wieder das Begrüßungsmenü sehen. Nach Eingabe des Namens und Klick auf den Say-hello-Button oder Drücken der Enter-Taste sollten Sie freundlich begrüßt werden.

Sehen Sie sich mal den Inhalt der Datei app.bundle.js an. Darin finden Sie – neben vielen Kommentaren, die zur Strukturierung der Datei verwendet werden – die Inhalte der Dateien greet.js, app.js und main.css sowie spezielle Anweisungen, die von Webpack eingefügt wurden.

In dieser Form ist die Datei für Entwicklungszwecke einigermaßen brauchbar. Bei einem Fehler zeigt einem der Browser, in welcher Zeile dieser Datei der Fehler aufgetreten ist. Normalerweise handelt es sich um eine Zeile, die Sie erfasst haben. Webpack-Befehle sollten keine Fehler werden. Nachteilig ist, dass diese Zeile etwas anders aussehen kann, wie in Ihrem Sourcecode, das sie ja mittels Babel transformiert wurde. Aber man findet die Originalzeile normalerweise recht schnell.

Gehen Sie nochmal ins WebStorm-Terminal und geben Sie diesmal die Anweisung npm run compress ein. Es wird wieder die Datei app.bundle.js erstellt. Und die Web-Anwendung sollte immer noch funktionieren. Wenn Sie sich die gebündelte JavaScript noch einmal ansehen, stellen Sie fest, dass Sie nun viel kleiner ist. Sie enthält keine überflüssige Leerzeichen, keine Zeilenumbrüche und auch keine Kommentare mehr (mit Ausnahme des Kommentars, der Auskunft über den Autor und die Lizenz gibt). Diese Datei ist für den Produktivbetrieb viel besser geeignet, da sie weniger Bandbreite verbraucht. Insbesondere Smartphone-Besitzer freuen sich über kleine Dateien, da ihr Datenvolumen dadurch weniger belastet wird. Für die Fehlersuche bei der Entwicklung der Web-App ist diese Variante allerdings vollkommen ungeeignet.

Nun können Sie noch die Anweisung npm run watch testen. Dieser startet einen Webpack-Watcher, der bei jeder Änderung an einer Datei, die von Webpack beim Bündeln berücksichtigt wird, dafür sorgt, dass app.bundle.js neu erstellt wird und Sie die Ergebnisse Ihrer Änderung durch einen einfachen Reload der Datei index.html im Browser betrachten können. Ändern Sie doch einmal in der CSS-Datei die Hintergrundfarbe, speichern Sie die Datei und beobachten Sie das Terminal. Laden Sie danach die HTML-Datei im Browser mittels des Reload-Buttons neu.

Vergessen Sie nicht:

  • Klick auf das SVN-Commit-Icon.

Quellen

  1. Google (Web): Make the Web Faster; Organisation: Google Developers; https://developers.google.com/speed/; Quellengüte: 3 (Web), Kapitel „CSS-Bereitstellung optimieren“, https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery
  2. Google (Web): Make the Web Faster; Organisation: Google Developers; https://developers.google.com/speed/; Quellengüte: 3 (Web), Kapitel „Remove Render-Blocking JavaScript“, https://developers.google.com/speed/docs/insights/BlockingJS
  3. Webpack: https://webpack.js.org/
  1. Kowarschick (MMProg): Wolfgang Kowarschick; Vorlesung „Multimedia-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2018; Quellengüte: 3 (Vorlesung)