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

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Kowa (Diskussion | Beiträge)
Keine Bearbeitungszusammenfassung
Kowa (Diskussion | Beiträge)
Keine Bearbeitungszusammenfassung
 
(223 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
{{HTML5-Tutorium:JavaScript:HelloWorld:Menü}}
{{HTML5-Tutorium:JavaScript:HelloWorld:Menü}}


'''Musterlösung''': [https://glossar.hs-augsburg.de/beispiel/tutorium/es6/hello_world/WK_HelloWorld04/web/index.html <code>index.html</code>],
'''Musterlösung'''<br/>
([https://glossar.hs-augsburg.de/beispiel/tutorium/es6/hello_world/WK_HelloWorld04/ SVN-Repository])
v01_1: [https://glossar.hs-augsburg.de/beispiel/tutorium/2025/wk_hello_world/wk_hello_world_04/web/v04_1/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html Google PageSpeed])<br/>
v01_2: [https://glossar.hs-augsburg.de/beispiel/tutorium/2025/wk_hello_world/wk_hello_world_04/web/v04_2/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_2%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_2%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_2%2Findex.html Google PageSpeed])<br/>
v01_3: [https://glossar.hs-augsburg.de/beispiel/tutorium/2025/wk_hello_world/wk_hello_world_04/web/v04_3/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_3%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_3%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_3%2Findex.html Google PageSpeed])<br/>
v01_4: [https://glossar.hs-augsburg.de/beispiel/tutorium/2025/wk_hello_world/wk_hello_world_04/web/v04_4/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_4%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_4%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_4%2Findex.html Google PageSpeed])<br/>
v01_5: [https://glossar.hs-augsburg.de/beispiel/tutorium/2025/wk_hello_world/wk_hello_world_04/web/v04_5/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_5%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_5%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_5%2Findex.html Google PageSpeed])
 
[{{Git-Server}}/kowa/wk_hello_world_04.git Git-Repository]
 
==Anwendungsfälle (Use Cases)==
==Anwendungsfälle (Use Cases)==
Gegenüber dem [[HTML5-Tutorium:_JavaScript:_Hello_World_03|dritten Teil des Tutoriums]]
Gegenüber dem [[HTML5-Tutorium:_JavaScript:_Hello_World_03|dritten Teil des Tutoriums]]
ändern sich die die Anwendungsfälle nicht. Die Anwendung leistet also genau dasselbe wie zuvor.
ä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, {{dh}} zu [[Modul|modularisieren]].
In diesem Teil des Tutoriums geht es darum, die Anwendung besser zu strukturieren, {{dh}} zu modularisieren (vgl. [[Programmierprinzipien#Modularit.C3.A4t.2C_Modularity.2C_Teile_und_herrsche.2C_Divide_et_impera|Programmierprinzipien]]).


==Modularisierung==
==Modularisierung==


Eine Anwendung, wie {{zB}} ein HTML5-Spiel, besteht aus diversen unterschiedlichen Komponenten mit ganz unterschiedlichen Aufgaben.
Eine Anwendung, wie {{zB}} 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
Bei einem Web-Frontend müssen beispielsweise verschieden Komponenten (Buttons, Scrolbars, Textfelder, Datumsfelder etc.) erstellt werden. 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 Spielentwicklung und teilweise
etc. erstellt und verwaltet werden, wobei das Erstellen und Verwalten teilweise während der Spiel-Entwicklung und teilweise
während der Spielausführung (Runtime) erfolgt.  
während der Spiel-Ausführung (Runtime) erfolgt. An der Entwicklung eines Spiels sind üblicherweise mehrere oder gar viele
 
An der Entwicklung eines Web-Systems oder eines Spiels sind üblicherweise mehrere oder gar viele
Entwickler gleichzeitig beteiligt. Oft müssen einzelnen Komponenten an neue Endgeräte oder Betriebssysteme angepasst werden.
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,  
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.
wenn eine Anwendung so erweitert werden soll, dass es auch auf einem Smartphone oder Tablet läuft.


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


==Erstellen eines neuen Projektes==
Je mehr Dateien von unterschiedlichen Autoren erstellt werden, desto größer ist die Gefahr, dass es beim Zugriff auf globale Variablen zu Namenskollisionen kommt.
Außerdem weiß niemand, welche globale Größe noch benötigt wird und welche nicht. Sicherheitshalber löscht man daher keine dieser Variablen, auch wenn sie vollkommen veraltet sind.


Erstellen Sie ein neues Projekt  „<code>HelloWorld04</code>“.
Daher gilt der [[Multimedia-Programmierung: Best Practices#Modularisierung|Grundsatz]]: Verwende so wenig globale Größen wie möglich.


Kopieren Sie anschließend die Projektdateien (insbesondere den Ordner „<code>web</code>“ mit den Dateien „<code>index.html</code>“, „<code>main.css</code>“ und „<code>main.js</code>“ '''sowie die Datei  <code>.gitignore</code>''') aus dem dritten Teil des Tutoriums und passen Sie den Titel in der HTML-Datei an.
== Erstellen eines neuen Projekts ==


Erstellen Sie für Ihr Projekt ein Git-Repository, committen Sie und pushen Sie es dann auf den Git-Server.
Erstellen Sie ein neues Projekt <code>hello_world_04</code> als Fork von <code>hello_world_03</code> (siehe [[HTML5-Tutorium: JavaScript: Hello World 02|Hello-World-02-Tutorium]]).


Dadurch, dass die Datei  <code>.gitignore</code> das Verzeichnis <code>node_modules</code>  enthält,
== Die App ==
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 <code>npm install</code> '''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 <code>node_modules</code> in Ihr Abgabemedium (CD, DVD, USB-Stick ...) ein.''' Ihr Betreuer wird es Ihnen danken!
=== Erstellen einer Ordnerstruktur ===
 
==Erstellen einer Ordnerstruktur==


Grundsätzlich gilt auch bei der Programmierung: Ordnung ist das halbe Leben.
Grundsätzlich gilt auch bei der Programmierung: Ordnung ist das halbe Leben.
Zeile 49: Zeile 50:
legt man im entsprechenden Ordner geeignete Unterordner an.
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 <code>Yes</code>):
Legen Sie im <code>web</code>-Verzeichnis Ihres Projekts folgende Ordner an:
* <code>web</code>: Der Inhalt dieses Ordners kommt später auf einen echten Web-Server.
* <code>web/css</code>: 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 ist das hier nicht notwendig. Es wird nur die Dateien „<code>head.css</code>“ und „<code>body.css</code>“ geben. '''Verschieben Sie die Datei <code>web/main.css</code> in diesen Ordner.'''
* <code>src</code>: Der Inhalt dieses Ordners enthält die Ausgangsdateien, die mit Hilfe von Node.js noch transformiert und dann in den Web-Ordner kopiert werden.
* <code>web/js</code>: Hierher kommen die JavaScript-Dateien, die von der Anwendung benötigt werden. '''Verschieben Sie die Datei <code>web/main.js</code> in diesen Ordner.'''
* <code>web/css</code>: Hier wird in den Tutorien jeweils nur eine sehr kleine Datei liegen: „<code>initial.css</code>“. Diese sorgt dafür, dass das Fenster der App während der Initialisierung kein defektes Layout präsentiert. Der größte Teil des CSS-Codes wird später dynamisch (per JavaScript) geladen.
* <code>web/js</code>: Hierher kopiert Node.js die erzeugten JavaScript-Dateien.
* <code>src/css</code>: 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 jeweils nur eine Datei namens „<code>main.css</code>“ oder „<code>main.scss</code>“ geben.
* <code>src/js</code>: Hierher kommt die Datei „<code>app.js</code>“, 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.
* <code>src/js/app</code>: Hier speichern Sie die übrigen von Ihnen geschriebenen JavaScript-Dateien Ihrer Web-Anwendung. In diesem Ordner werden {{iAllg}} Unterordner  zur weiteren Strukturierung der Anwendung angelegt.


Fremde Bibliotheken und automatisch generierte JavaScript-Dateien enthalten häufig
Nehmen Sie in der Datei <code>web/index.html</code> folgende beiden Ersetzungen vor:
unsauberen Code, die Ihnen von WebStorm beim Speichern der Dateien im Repository angezeigt werden.
* <code>main.css</code> → <code>css/main.css</code>
In einer Rückfrage sollen Sie dann bestätigen, dass Sie die  „fehlerhaften“ Dateien trotzdem speichern wollen.
* <code>main.js</code> → <code>js/main.js</code>


Damit Ihnen bei einem Commit nur Fehler in Ihren eigenen Dateien angezeigt werden, schließen Sie die Ordner 
Wenn Sie jetzt die Datei <code>index.html</code> mit dem Live Server ausführen, sollte Ihre Anwendung wieder funktionieren.
<code>node_modules</code>, „<code>web/css</code>“ und „<code>web/js</code>“ von der
WebStorm-Fehlerüberwachung aus:


* Rechtsklick auf den Ordner „<code>node_modules</code>“ → <code>Mark Directory as</code> → <code>Excluded</code>
Sollte dies der Fall sein, so sollten Sie die Änderungen committen und evtl. auch gleich
* Rechtsklick auf den Ordner „<code>web/css</code>“ → <code>Mark Directory as</code> → <code>Excluded</code>
per <code>git push</code> auf den Git-Server hochladen. '''Es schadet übrigens nie, vor einen Commit oder gar einen Push noch einmal zu überprüfen, ob die Anwendung noch läuft.'''
* Rechtsklick auf den Ordner „<code>web/js</code>“ → <code>Mark Directory as</code> → <code>Excluded</code>


Verschieben Sie nun die Datei <code>main.css</code>“ in den soeben erstellten Ordner „<code>src/css</code>“.
=== Modularisierung der Datei <code>main.js</code> ===
Die Datei  „<code>index.html</code>“ verbleibt im Ordner „<code>web</code>“. Der Grund ist, dass künftig die CSS-Datei
mit Hilfe des Node.js-Pakets <code>webpack</code> automatisch erstellt wird. Dabei wird die Source „<code>main.css</code>“
von webpack komprimiert und die Ergebnisdatei in den Ordner  „<code>web/css</code>“ eingefügt.  Die Datei „<code>index.html</code>“ wird
dagegen vorerst noch nicht automatisch generiert.


Sie sollten nach jeden größeren Entwicklungsschritt die Änderungen im Repository speichern:
Dateien wie die ECMAScript-Datei <code>main.js</code> aus dem dritten Teil des Tutoriums werden sehr schnell groß, wenn man alle Funktionalität in diese eine Datei einfügt. Dies reduziert die Lesbarkeit und die Wartbbarkeit des Codes.
[[Bertrand Meyer]], einer der wesentlichen Begründer der modularen und objektorientierten Programmierung, fordert,
dass ein Programmcode in sogenannte Module zerlegt werden sollen. Ein Modul stellt eine gewisse Funktionalität zur Verfügung. Die zugehörigen Funktionen werden exportiert und können von anderen Modulen importiert werden, sofern sie diese Funktionalität zur Erfüllung ihrer Aufgabe benötigen. Meyer hält es für sinnvoll, dass ein Modul jeweils nur eine oder sehr wenige Aufgaben erfüllt. Damit können Module von vielen anderen Modulen wiederverwendet werden.


* Klick auf das Commit-Icon.
Die Datei <code>main.js</code> wird im Sinne von Meyer in zwei Teile aufgespalten:
<code>greet.js</code> und <code>main.js</code>. Die erste Datei enthält die eigentliche Begrüßungsfunktionalität,
die zweite initialisiert die App, {{dh}} aktiviert die Begrüßungsfunktiontionalität.  


Hin und wieder ist auch ein <code>git push</code> angebracht.
Erstellen Sie die JavaScript-Datei <code>web/js/greet.js</code> und verschieben Sie die
beiden Funktionen <code>sayHello</code> und <code>sayHelloOnEnter</code>
(jeweils samt Funktionsrumpf :-) ) in diese Datei.


==Die App==
Schreiben Sie vor jede der beiden Funktionsdefinitionen das Schlüsselwort <code>export</code>.
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. (Funktionen ohne
Export-Anweisung könnten von anderen ES-6-Dateien nicht importiert werden.)


===Laden der CSS- und der JavaScript-Dateien===
In der Datei <code>web/js/main.js</code> werden jetzt diese beiden Funktionen importiert.
Fügen Sie ganz am Anfang der Datei ({{dh}} vor der verbliebenen Init-Funktion)
die folgende Import-Anweisung ein:


Eine Web-Anwendung funktioniert nur – wie Sie bereits erfahren haben –, wenn nicht nur die HTML-Datei, sondern auch die zugehörigen
<source lang="javascript">
CSS- und JavaScript-Dateien geladen werden. Allerdings gibt es dabei ein Problem: Die CSS- und JavaScript-Dateien werden im Laufe der
import * as greet from './greet.js';
Zeit immer zahlreicher und/oder größer. Diese Dateien zu laden, dauert seine Zeit.
</source>


In der aktuellen dritten Version der Web-Anwendung stehen die Verweise auf diese Dateien im <code>head</code>-Bereich des Dokuments.
Damit wird erreicht, dass alle (<code>*</code>) Elemente, die in der Datei <code>greet.js</code>
Dieser wird vollständig geladen, bevor der <code>body</code>-Bereich eingelesen wird. Das heißt aber, dass der Browser keine Inhalte des HTML-Dokuments
exportiert werden, unter ihrem jeweiligen Namen im Objekt <code>greet</code> gespeichert werden.  
darstellen kann, solange er die JavaScript- und CSS-Dateien lädt. Wenn dies zu lange dauert, verliert der Besucher die Geduld und verlässt die Seite
Das heißt, innerhalb der Datei <code>main.js</code> gibt es nun die beiden Funktionen
vorzeitig.
<code>greet.sayHello</code> und <code>greet.sayHelloOnEnter</code>.


Besser wäre es daher andersherum vorzugehen: Es wird zuerst der <code>body</code>-Bereich geladen und dann die JavaScript- und die CSS-Dateien.
Fügen Sie jetzt noch das Attribut <code>type="module"</code> in das Script-Element in der
Im Falle von JavaScript ist das durchaus sinnvoll, aber im Falle von CSS hat das den Effekt, dass der Browser noch keine Layout-Vorgaben erhalten hat,
Datei <code>index.html</code> ein. Damit teilen Sie dem Browser mit, dass Sie die ECMAScript-6-Befehle <code>import</code> und <code>export</code> verwenden möchten.  
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?
Anderenfalls hätten sie Zugriff auf alle Objekte, Konstanten, Variablen und Funktionen einer geladenen Datei, so wie die bis ECMAScript 5 üblich war. Das heißt, das Laden einer Datei konnte zu massiven Problemen führen, wenn in zwei verschiedenen Dateien unterschiedliche Objekte etc. zufälligerweise gleich benannt wurden. Um diese Probleme zu umgehen gab es zahlreiche ziemlich aufwändige Hacks. Mit dem Modul-Konzept von ECMAScript gehören diese Probleme der Vergangenheit an.


===CSS-Dateien===
Damit sollte Ihre Anwendung wieder funktionieren.
Für CSS-Dateien empfiehlt Google ernsthaft, das Link-Element ganz ans Ende der HTML-Datei  zu stellen, also '''nach''' dem schließenden <code>html</code>-Tag. Damit das Problem mit dem falschen Layout nicht auftritt, sollen ganz wenige, wichtige CSS-Befehle direkt – {{dh}} als CSS-Befehle innerhalb eines <code>style</code>-Elements – in dem HTML-Head-Bereich eingefügt
werden.<ref>{{Quelle|Google (Web)}}, Kapitel „CSS-Bereitstellung optimieren“, https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery</ref>


Von ersten Vorschlag rate ich dringend ab, da er  nicht [[HTML]]-konform ist, ja noch nicht einmal [[SGML]]-konform. Der zweite Vorschlag hat zur Folge, dass Struktur (HTML) und Layout (CSS)
Vergessen Sie nicht, Ihr aktuelles Projekt zu committen und auf dem Git-Server zu speichern, sobald alles funktioniert.´
vermischt werden. Dies war einer der Kardinalfehler von frühen HTML-Versionen aus der Zeit, bevor es das CSS-Format gab. '''Tun Sie das {{iAllg}} nicht.''' Aber: Sie können Webpack<ref>https://webpack.js.org/</ref> dazu bringen, dies dynamisch zur Laufzeit für Sie nachzuholen.


Gehen Sie so vor, wie es unter [[Multimedia-Programmierung: Best Practices#CSS|Multimedia-Programmierung: Best Practices]] beschrieben ist. In diesem und den folgenden Tutorien werden
==== Musterlösung v04_1 ====
diese Prinzipien berücksichtigt.
Verison v04_1 [https://glossar.hs-augsburg.de/beispiel/tutorium/2025/wk_hello_world/wk_hello_world_04/web/v04_1/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html Google PageSpeed])


Legen Sie die Datei <code>web/css/initial.css</code> an und fügen Sie folgenden (sehr kurzen!) Code ein:
===Laden der CSS- und der JavaScript-Dateien===
 
<source lang="javascript">
body    { background-color: #C5EFFC; }
.hidden { display: none; }
</source>
 
In der Datei <code>src/css/main.css</code> können Sie das <code>.hidden</code>-Element entfernen.


In der Datei <code>web/index.html</code> müssen Sie das Link-Element in Header folgendermaßen
Führen Sie [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html Google PageSpeed] für die  
<source lang="html">
Musterlösung der vorherigen Teilaufgabe aus und sehen Sie sich an, welche Verbesserungsvorschläge Google macht.  
<link rel = "stylesheet" type = "text/css" href = "css/initial.css"/>
</source>
anpassen. Das heißt, Sie laden im HTML-Head-Bereich nur noch die kleine CSS-Datei.


===JavaScript-Dateien===
Folgendes findet Google PageSpeed verbesserungswürdig:
# [https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources?hl=de ''Ressourcen entfernen, die das Rendering blockieren.]''<br />''Mögliche Einsparung von 300 ms.''
# [https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains?hl=de ''Verkettung kritischer Anfragen vermeiden'']<br />''2 Ketten gefunden''
# [''Largest Contentful Paint-Element 1.350 ms'' https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint?hl=de]
# ''Dokument enthält keine Meta-Beschreibung''


Für JavaScript-Dateien empfiehlt Google ebenfalls, diese nicht schon zu Beginn zu laden.<ref>{{Quelle|Google (Web)}}, Kapitel „CSS-Bereitstellung optimieren“, https://developers.google.com/speed/docs/insights/BlockingJS</ref> Dieser Vorschlag ist sehr sinnvoll.
''Anmerkung'': Google Pagespped ist auch über die Chroeme-Entwickler-Konsole zugänglich. Damit können auch lokale Server (<ocde>localhost</code> {{}}) getestet werden. Echte Prformanzprobleme erkennt man aber eher, wenn man seinen Live-Server testet.


Entfernen Sie das <code>script</code>-Element aus dem HTML-Header-Bereich und fügen vor dem schließenden <code>body</code>-Tag folgendes Script-Element ein:
Eine Meta-Beschreibung hinzuzufügen, ist kein Problem. Fügen Sie folgende Zeile in den Head-Bereich der HTML-Datei ein.


<source lang="html5">
<source language="HTML">
<body>
<meta name="description"  
  ...
      content="A Hello World application that demonstrates best practices for HTML, CSS, and JavaScript files."
  <script type="text/javascript" src="js/app.bundle.js"></script>
>
</body>
</source>
</source>


Fügen Sie außerdem in das erste <code>section</code>-Tag im Body das Attribut
Das effiziente Laden der JavaScript- und CSS-Dateien ist viel komplexer, aber auch viel wichtiger, da die CSS- und JavaScript-Dateien werden im Laufe der
<code>class="hidden"</code> ein, damit man auch dieses Element beim Laden der Anwendung
Zeit immer zahlreicher und/oder größer werden. Diese Dateien zu laden, dauert seine Zeit. Auch das Laden von vielen kleinen Dateien dauert deutlich länger, als das Laden einer einzigen großen Datei. Das aber widerspricht der Modularisierung, die wir vorher vorgenommen haben. Modularisierung und Performance widersprechen sich zunächst einmal.
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.  
In Web-Anwendung aus dem dritten Teil des Tutoriums stehen die Verweise auf die CSS- und JavaScript-Dateien im <code>head</code>-Bereich des Dokuments.
Wenn Sie im Browser die Konsole öffnen, sehen Sie dort (evtl. nach einem Reload) eine Fehlermeldung,
Die CSS-Datei wird vollständig geladen, bevor der <code>body</code>-Bereich eingelesen wird. Die JavaScript-Datei <code>main.js</code> wird dagegen parallel zum Body-Bereich geladen. Dies wird durch die Angabe von <code>async</code> im
dass die JavaScript-Datei <code>app.bundle.js</code> nicht gefunden wird. Dieser Fehler ist nicht
Script-Tag wird erreicht.  
weiter erstaunlich, da es diese Datei ja noch gar nicht gibt.


Weiter unten im Tutorium wird beschrieben, wie Sie mit Hilfe eines ES6-Import-Befehls und Webpack dafür sorgen,
Das heißt aber, dass der Browser keine Inhalte des HTML-Dokuments
dass diese Datei nicht nur den JavaScript-Code der Anwendung bereit stellt, sondern auch den Inhalt
darstellen kann, solange er die CSS-Datei (und evtl. weitere Dateien wie Fonts, Hintergrundbilder etc.) lädt. Wenn dies zu lange dauert,  
der eigentlichen CSS-Datei <code>main.css</code> dynamisch in die HTML-Datei einfügt (''injiziert'').
verliert der Besucher die Geduld und verlässt die Seite vorzeitig.


Vergessen Sie nicht:
Besser wäre es daher andersherum vorzugehen: Es wird zuerst der Body-Bereich geladen und dann die JavaScript- und die CSS-Dateien.
* Klick auf das SVN-Commit-Icon.
Im Falle von JavaScript ist das durchaus sinnvoll (und durch die Angabe von <code>async</code> 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.


===Modularisierung der JavaScript-Dateien===
Auch das ist verwirrend und wirkt unprofessionell: https://de.wikipedia.org/wiki/Ampel


Die JavaScript-Datei <code>main.js</code> aus dem dritten des Tutoriums wird in zwei Teile aufgespalten:
Was also machen?
<code>greet.js</code> und <code>app.js</code>. Die erste Datei enthält die eigentliche Begrüßungsfunktionalität,
die zweite initialisiert die App, {{dh}} aktiviert die Begrüßungsfunktion.


Erstellen Sie folgende zwei JavaScript-Dateien:
===CSS-Dateien===
 
Damit das Problem mit dem falschen Layout nicht auftritt, empfiehlt Google ([https://developers.google.com/speed/pagespeed/insights/ PageSpeed Insights]),
'''<code>src/js/app/greet.js</code>'''
ganz wenige, wichtige CSS-Befehle direkt – {{dh}} als CSS-Befehle innerhalb eines <code>style</code>-Elements – in dem HTML-Head-Bereich einzufügen. Alle anderen CSS-Anweisungen sollen erst am Ende der HTML-Datei dynamisch mittels JavaScript
<source lang="javascript">
(oder mittels des PageSpeed-Optimization-Modul von Google) gelesen werden.
/**
* @namespace greet
* Contains methods to say hello to the user of the web app.
*/


/**
[https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html PageSpeed Insights (für Musterlösung v04_1)]:
* Welcomes the user of the web app by displaying a welcome message
<blockquote cite="https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_1%2Findex.html">
* that includes his name. The name is fetched from a text input field.
[https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources?hl=de Ressourcen blockieren den First Paint deiner Seite. Versuche, wichtiges JS und wichtige CSS inline anzugeben und alle nicht kritischen JS und Stile zurückzustellen.]
*/
</blockquote>
export const sayHello =
function()
{
  document.getElementById('heading_hello').innerHTML =
    'Hello, ' + document.getElementById('input_name').value + '!';
  document.getElementById('section_form').classList.add('hidden');
  document.getElementById('section_hello').classList.remove('hidden');
};


/**
Gehen Sie so vor, wie es Google empfohlen hat. Genauer gesagt, gehen Sie so vor,  
* A keyboard event observer. It tests whether the enter key has been pressed.
wie es unter [[Multimedia-Programmierung: Best Practices#CSS|Multimedia-Programmierung: Best Practices]] beschrieben ist. In diesem und den folgenden Tutorien werden
* If so, the greet method is activated. Default reactions of the browser are
die dort beschriebenen Prinzipien berücksichtigt.
* disabled.
* @param {KeyboardEvent} p_event - A standard JavaScript keyboard event object
*  (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
*/
export const sayHelloOnEnter =
function(p_event)
{
  if ( (p_event.code === 'Enter' || p_event.keyCode === 13) &&
      document.activeElement === document.getElementById('input_name')
    )
  {
    p_event.preventDefault();
    p_event.stopPropagation();
    sayHello();
  }
};
</source>


Sie sehen, dass dieses Modul zwei Funktionen exportiert. Diese (und nur diese) können jetzt
Legen Sie die Datei <code>src/css/head.css</code> an und fügen Sie folgenden (sehr kurzen!) Code ein:
von anderen Modulen importiert und damit genutzt werden. Dies nutzen Sie im zweiten Modul aus:


'''<code>src/js/app.js</code>'''
<source lang="css">
<source lang="javascript">
body
import '../css/main.css';
{ background-color: #C5EFFC; }
import * as greet from './app/greet.js';


// Initialize the app.
.hidden
document.getElementById('button_submit')
{ display: none; }
        .addEventListener('click',  greet.sayHello);
window  .addEventListener('keydown', greet.sayHelloOnEnter);
 
// As now all files have been loaded and all data has been
// initialized, the app is ready to start.
// So make the form visible!
document.getElementById('section_form').classList.remove('hidden');
</source>
</source>


Sie importieren alle Elemente (<code>*</code>), die Ihnen das Modul <code>src/app/greet.js</code>
In der Datei <code>src/index.html</code> müssen Sie das Link-Element in Header folgendermaßen
zur Verfügung stellt, und speichern diese Elemente in einem Objekt namens <code>greet</code>.
<source lang="html">
Damit ist es Ihnen möglich die beiden Funktionen <code>sayHello</code> und <code>sayHelloOnEnter</code>
<link rel="stylesheet" href="css/head.css"/>
zu verwenden. Sie können jeweils unter dem Namen <code>greet.sayHello</code> bzw. <code>greet.sayHelloOnEnter</code>,
auf sie zugreifen.
 
Weil Sie schon gerade dabei sind, importieren Sie am besten gleich auch noch
die CSS-Datei <code>main.css</code>. Webpack sorgt dann dafür, dass diese
Datei in die Datei <code>app.bundle.js</code> integriert wird und somit ''dynamisch''
geladen wird, sobald der Browser die Script-Anweisung
 
<source lang="html5">
<script type="text/javascript" src="js/app.bundle.js"></script>
</source>
</source>
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. Noch besser ist es gemäß den Google-Empfehlungen,
wenn man die CSS-Regeln direkt in die HTML-Datei einfügt. Ersetzen Sie also das
Link-Element durch folgendes Style-Element:


ausführt.
<source lang="html">
<style>
body
{ background-color: #C5EFFC; }


Der letzte Befehl in der JavaScript-Datei <code>app.js</code>
.hidden
 
{ display: none; }
<source lang="javascript">
</style>
document.getElementById('section_form').classList.remove('hidden');
</source>
</source>


sorgt dafür, dass das Eingabeformular sichtbar wird. Da die
In der Datei <code>src/css/main.css</code> können Sie nun das  
notwendigen Callback-Funktionen zuvor mit dem Formular verknüpft wurden,
<code>.hidden</code>-Element und
ist es jetzt einsatzbereit, und es spricht nichts mehr dafür, den Benutzer
das Farbattribut <code>background-color: #C5EFFC;</code> entfernen.
länger an der Verwendung dieses genialen ( :-) ) Formulars zu hindern.


Vergessen Sie nicht:
Es ist sinnvoll, die Datei <code>main.css</code> in <code>body.css</code> umzubenennen:
* Klick auf das SVN-Commit-Icon.
<code>head.css</code> wird im HTML-Head-Bereich vor dem Body-Bereich geladen,
<code>body.css</code> soll parallel zum Body-Bereich geladen werden und dann zur Verfügung stehen, wenn der Body vollständig geladen wurde.


===Node.js, Grunt, Babel und Webpack===
Leider ist CSS-Link-Element nicht möglich, <code>async</code> anzugeben, wie wir dies beim Skript-Element gemacht haben, um JavaScript-Code parallel zum Body-Element zu laden. Hier hilft derzeit nur ein kleiner Hack:


Eigentlich wäre Sie jetzt fertig, wenn denn die Standardbrowser die ES6-Import- und Export-Befehle kennen würden.
<source lang="html">
Doch das ist leider in den allermeisten Fällen nicht der Fall. Daher müssen die ES6-Dateien in ES5-Dateien transformiert werden.
<!-- Load body.css asynchronously, i.e. parallel to the body content. -->
Dafür gibt es diverse Möglichkeiten. Ich empfehle derzeit Webpack<ref>Webpack: https://webpack.js.org/</ref>.
<link rel="preload" href="css/body.css" as="style" onload="this.rel='stylesheet'">
</source>


Zum Transformieren und Packen von JavaScript-, JSON- und CSS/SCSS-Dateien kommen neben Webpack auch noch Grunt<ref>Grunt: https://gruntjs.com/</ref> und  Babel<ref>Babel: https://babeljs.io/</ref> zum Einsatz. Diese Pakete kennen Sie ja schon
Die Datei <code>body.css</code> wird für den Medientyp <code>style</code> parallel zum HTML-Dokument geladen, aber nicht aktiviert.
aus dem Tutorium „[[HTML5-Tutorium: JavaScript: Entwicklungsumgebung|Einrichten eine Entwicklungsumgebung für Web-Anwendungen]]“.
Um aus dem Inhalt der vorab geladenen Datei für den Browser ein Stylesheet zu machen, muss man den Wert des Attributs <code>rel</code>
in <code>'stylesheet'</code> abändern. Das sollte erst erfolgen, wenn das Dokument vollständig geladen wurde, wenn also das
das Fensterobjekt des Browsers, in dem das HTML-Dokument gerendert werden soll, das Ereignis <code>load</code> feuert. Für dieses Ereignis kann man entweder im <code>link</code>-Tag den Händler <code>onload</code> einfügen oder man schreibt den Aktivierungsbefehl in die Init-Funktion in der Datei <code>main.js</code>.


Webpack ist ein sehr mächtiges Werkzeug, mit dem eine Vielzahl von Web-Dateien in einer oder einigen wenigen JavaScript-Dateien
Für die zweite Variante sollte man dem <code>link</code>-Element eine eindeutigen Identifikator zuweisen:
zusammengefasst und bei Bedarf auch komprimiert werden können. Es handelt sich also um einen ''JavaScript module bundler''.
Einer der Entwickler von Webpack ist [[Johannes Ewald]], ein ehemaliger Student der [[HSA]] (vgl. https://github.com/jhnns). Er bietet auch
<source lang="html">
regelmäßig Vorlesung an der HSA zum Thema „JavaScript“ an.
<link id="body_css" rel="preload" href="css/body.css" as="style">
</source>


====Node.js-Pakete====
Danach kann man den Aktivierungsbefehl in die Funktion <code>init</code> in der Datei <code>main.js</code> einfügen.


Öffnen Sie das WebStorm-Terminal und installieren Sie die benötigten Node.js-Pakete
<source lang="javascript">
 
document.getElementById('body_css').rel = 'stylesheet';
<source lang="bash">
npm init -y
# Name, Description, Repository, Autor, Lizenz und Keywords in package.json anpassen
npm install --save-dev grunt grunt-contrib-watch
npm install --save-dev babel-cli babel-preset-env
npm install --save-dev webpack@3.11.0 grunt-webpack
npm install --save-dev babel-loader json-loader style-loader css-loader
</source>
</source>


'''Achtung: Seit Anfang 2018 gibt es webpack in der Version 4.x. Dieses Tutorium ist für webpack Version 3.x konzipiert. Die Verwendung von <code>npm install --save-dev webpack</code> installiert die aktuelle Version, was zu Inkompatibilitäten mit den restlichen Paketen führt. Installieren Sie daher bitte die letzte 3-Version von webpack: <code>npm install --save-dev webpack@3.11.0</code>.'''
Zusammenfassung:
* In <code>head.css</code> stehen möglichst wenig CSS-Anweisungen. Sie sollten ausreichen, um die aktuelle Seite “above the fold” („über dem Zeitungsknick“) fehlerfrei zu rendern. Das heißt, es sollte alles korrekt gerendert werden, was nach dem Laden im größtmöglichen Browserfenster sichtbar ist.
* Der Inhalt der <code>head.css</code> sollte direkt in den Head-Bereich der HTML-Seite eingefügt werden.
* Die restlichen CSS-Anweisungen werden in die Datei <code>body.css</code> eingetragen.
* Diese Datei wird mittels eines kleinen Hacks asynchron, {{dh}} parallel zum Body-Element geladen.


Achtung: <code>grunt-cli</code> wird auch benötigt. Aber dieses Paket wurde schon
===<code>index.html</code>===
im Tutorium „[[HTML5-Tutorium: JavaScript: Entwicklungsumgebung|Einrichten eine Entwicklungsumgebung für Web-Anwendungen]]“
global installiert und braucht daher nicht noch einmal installiert zu werden.


Sorgen Sie nun dafür. dass die Dateien <code>package.json</code> und <code>package-lock.json</code>
Da die Datei <code>head.css</code> nur die Hintergrundfarbe sowie die <code>.hidden</code>-Regel enthält, sollten alle HTML-Elemente, die gerendert werden sollen, zunächst unsichtbar gemacht werden. Bei einer Web-Anwendung, wie einem Spiel, die zum Laden länger braucht, könnte man eine Sanduhr oder einen Ladebalken einfügen, um anzuzeigen, dass man noch etwas warten muss. Üblicherweise, sollte man dafür sorgen,
auch im Repository gespeichert werden (Leider erledigt die WebStorm nicht automatisch):
dass das Laden einer Seite so schnell geht, dass der Benutzer gar nicht erst ungeduldig wird.


* Rechtsklick auf  „<code>package.json</code>“ → <code>Subversion</code> → <code>Add to VCS</code>
Fügen Sie die CSS-Klasse <code>hidden</code> in beide HTML-Sektions ein:
* Rechtsklick auf  „<code>package-lock.json</code>“ → <code>Subversion</code> → <code>Add to VCS</code>


====Webpack====
<source lang="html">
<section id="section_form" class="hidden">
...
</section>
<section id="section_hello" class="hidden">
...
</section>
</source>


Nun müssen noch Webpack und Grunt konfiguriert werden, damit die Datei <code>app.bundle.js</code>
Wenn man jetzt das Dokument im Browser öffnet, sieht man nichts, außer einem hellblauen Hintergrund.  
automatisch generiert werden kann.


Erstellen Sie im Wurzelverzeichnis des Projektes folgende Datei:
Abhilfe schafft hier ein kleiner JavaScript-Befehl, der <code>hidden</code> von der Eingabe-Section <code>hidden-form</code> entfernt, sobald das Dokument vollständig geladen wurde (samt allen CSS- und JavaScript-Dateien</code>):


''<code>webpack.config.js</code>''
<source lang="javascript">
<source lang="html">
document.getElementById('section_form').classList.remove('hidden');
const path = require('path');
</source>


function absolutePath(l_path)
An welche Stelle dieser Befehl eingefügt wird, erfahren Sie im nachfolgenden Abschnitt.
{ return path.resolve(__dirname, l_path);
}


module.exports =
===JavaScript-Dateien===
{ entry:
  { app: absolutePath('src/js/app.js')
  },


  output:
Für JavaScript-Dateien empfiehlt Google ebenfalls, diese nicht schon zu Beginn zu laden, sondern das Laden „aufzuschieben“. Das machen wir bereits mit Hilfe des Async-Attributs.
  { filename: 'js/[name].bundle.js',
    path: absolutePath('web')
  },


  module:
Fügen Sie nun den zuvor erwähnten Befehl
  { rules:
<source lang="javascript">
    [ { test:  /\.json$/,
document.getElementById('section_form').classList.remove('hidden');
        loader: 'json-loader'
      },
      { test:  /\.js$/,
        use:    [ { loader: 'babel-loader',
                    options:
                    { presets: ['env'],
                      ignore: '/node_modules/'
                    },
                  },
                ],
      },
      { test:  /\.css$/,
        use:    [ 'style-loader',
                  { loader: 'css-loader', options: { minimize: true } }
                ]
      }
    ],
  },
 
  plugins:
  [],
};
</source>
</source>
als letzen Befehl in den Rumpf der Funktion <code>init</code> in der Datei
<code>web/js/main.js</code> ein.


Es handelt sich um ein JavaScript-Datei, die direkt von Node.js interpretiert wird.
Wenn Sie jetzt die Web-App starten, sollte sie wieder funktionieren.
Aus diesem Grund wird auch nicht das EcmaScript-6-Modul-Konzept eingesetzt, sondern
[[CommonJS]], das Standard-Modulkonzept vom Node. Hier werden Module mittels <code>require</code>
und nicht mittels <code>import</code> importiert. Für Sie ist das allerdings nebensächlich,
das 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,  
Was haben Sie erreicht? Solange die Anwendung geladen wird, sehen Sie nur einen blauen Hintergrund ohne Inhalt, bzw. {{iAllg}} einen so genannten Splash Screen. Wenn dann alles, {{dh}} alle CSS- und JavaScript-Dateien geladen wurden, wird die eigentliche Anwendung sichtbar.
das Webpack-Konfigurationsinformationen enthält:


* <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.
Vergessen Sie nicht: Commit!
* <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>[name].bundle.js</code>
erzeugt, wobei <code>[name]</code> durch den Schlüsselname des Entry-Eintrags ersetzt wird.
In der obigen Datei gibt es nur ein Entry-Element. Dessen Schlüsselname lautet <code>app</code>.
* <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
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
Datei auf <code>.json</code>, <code>.js</code> oder <code>.css</code> endet. Je nach der
Endung wird ein anderer Loader zum Laden und Verarbeiten der Datei verwendet.
(Für Eebpack gibt es diverse Loader, einige davon haben Sie zuvor mit Hilfe von <code>npm</code> installiert.)


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


JavaScript-Dateien werden mit dem Babel-Loader geladen und transformiert.
Bislang sieht die Init-Funktion folgendermaßen aus.
Auf welche Weise der Babel-Loader dies macht wird nicht mehr in der Datei <code>.babelrc</code>
festgelegt, sondern direkt in der Datei <code>webpack.config.js</code>. Der Loader kann
auch Dateien laden, die sich im Verzeichnis <code>node_modules</code> befinden.
Die dort gespeicherten Module sorgen allerdings selbst dafür, dass sie ausführbaren
Web-Code bereitstellen. Daher sollten sie vom Babel-Loader nicht noch einmal transformiert werden.
Mit <code>ignore: '/node_modules/'</code> wird also nicht festgelegt, dass Babel die
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
<source lang="javascript">
Style-Loader Anweisungen in die  Datei <code>app.bundle.js</code> geschrieben, die dafür sorgen,
function init()
dass die CSS-Elemente beim Laden dieser JavaScript-Datei direkt in den HTML-Head-Bereich
{ document.getElementById('button_submit')
der zugehörigen HTML-Datei eingefügt (''injiziert'') werden.
          .addEventListener('click', greet.sayHello);
  window.addEventListener('keydown', greet.sayHelloOnEnter);


Am Ende der Konfigurations-Datei, gibt es noch eine Plugin-Liste, die dazu genutzt werden kann,
  document.getElementById('body_css').rel = 'stylesheet';
dateiunabhängige Transformationen an der Ergebnis-Datei (<code>app.bundle.js</code>)
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
  document.getElementById('section_form').classList.remove('hidden');
der Kommandozeile aufrufen. Besser ist es allerdings, NPM-Skript-Befehle oder gar Grunt-Befehle
}
zu verwenden.
</source>


====NPM-Skripte====
Man kann auch einen anderen Import-Befehl verwenden, um auf das Objekt <code>greet</code> verzichten zu können.
<source lang="javascript">
import { sayHello, sayHelloOnEnter } from './greet.js'
function init()
{ document.getElementById('button_submit')
          .addEventListener('click', sayHello);
  window.addEventListener('keydown', sayHelloOnEnter);


Fügen Sie das <code>scripts></code>-Objekt der Datei <code>package.json</code>
  document.getElementById('body_css').rel = 'stylesheet';
drei weitere Skript-Anweisungen ein:


<source lang="json">
   document.getElementById('section_form').classList.remove('hidden');
"scripts": {
}
  "test": "echo \"Error: no test specified\" && exit 1",
</source>
  "bundle": "webpack",
  "compress": "webpack -p",
   "watch": "webpack --watch"
},
</source>  


Geben Sie nun im WebStorm-Terminal die Anweisung <code>npm run bundle</code> ein.
Darüber hinaus kann man die Autofokus-Funktionalität verbessern. Das Autofokus-Attribut reagiert laut HTML-Standard nur in manchen Situation.
Wenn man die Situationen  selbst definiert werden, in den Autofokus aktiv wird, muss man den Fokus per JavaSript setzen, sobald eine dieser Situationen eintritt.
<source lang="javascript">
function autofocus()
{ document.getElementById('input_name').focus(); 
}


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


* Rechtsklick auf  „<code>app.bundle.js</code>“ → <code>Subversion</code> → <code>Add to VCS</code>
function init()
{ // Say hello, if the submit button is clicked or the buttoen 'Enter' is pressed.
  document.getElementById('button_submit').addEventListener('click', sayHello);
  window.addEventListener('keydown', sayHelloOnEnter);


Wenn Sie jetzt die Datei <code>index.html</code> im Browser öffnen, sollten Sie nach kurzer Zeit
  // Set the focus it the window is reentered.
wieder das Begrüßungsmenü sehen. Nach Eingabe des Namens und Klick auf den Say-hello-Button
  window.addEventListener('visibilitychange', autofocus);
oder Drücken der Enter-Taste sollten Sie freundlich begrüßt werden.
 
  // If the reset button is clicked, not only the textares are reset but also the focus is replaced.
  document.getElementById('button_reset').addEventListener('click', reset);


Sehen Sie sich mal den Inhalt der Datei <code>app.bundle.js</code> an. Darin finden Sie
  // Activate the body css file that had been preloaded asynchronously.
– neben vielen Kommentaren, die zur Strukturierung der Datei verwendet werden – die Inhalte der Dateien <code>greet.js</code>, <code>app.js</code> und <code>main.css</code>
  document.getElementById('body_css').rel = 'stylesheet';
sowie spezielle Anweisungen, die von Webpack eingefügt wurden.  


In dieser Form ist die Datei für Entwicklungszwecke einigermaßen brauchbar. Bei einem Fehler
  // Now that all files have been loaded, the app can be started.
zeigt einem der Browser, in welcher Zeile dieser Datei der Fehler aufgetreten ist.  
  // So the form is made visible!
Normalerweise handelt es sich um eine Zeile, die Sie erfasst haben. Webpack-Befehle sollten keine
  document.getElementById('section_form').classList.remove('hidden');
Fehler werden. Nachteilig ist, dass diese Zeile etwas anders aussehen kann, wie in Ihrem Sourcecode,
  autofocus();
das sie ja mittels Babel transformiert wurde. Aber man findet die Originalzeile normalerweise recht schnell.
}
 
</source>
Gehen Sie nochmal ins WebStorm-Terminal und geben Sie diesmal
die Anweisung <code>npm run compress</code> ein.
Es wird wieder die Datei <code>app.bundle.js</code> 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 <code>npm run watch</code> testen.
==== Musterlösung v04_2 ====
Dieser startet einen Webpack-Watcher, der bei jeder Änderung an einer Datei, die von Webpack
Verison v04_2 [https://glossar.hs-augsburg.de/beispiel/tutorium/2025/wk_hello_world/wk_hello_world_04/web/v04_2/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_2%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_2%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_2%2Findex.html Google PageSpeed])
beim Bündeln berücksichtigt wird, dafür sorgt, dass <code>app.bundle.js</code> neu erstellt wird
und Sie die Ergebnisse Ihrer Änderung durch einen einfachen Reload der Datei <code>index.html</code>
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:
== Weitere Modularisierung ==
* Klick auf das SVN-Commit-Icon.


====Grunt====
Teilen Sie die Datei <code>greet.js</code> in zwei Dateien <code>greet.js</code> und <code>greet_on_enter.js</code> mit jeweils eines Funktion auf, um dem
[[Programmierprinzipien#Single_Responsibility_Principle.5B9.5D|Single Responsibility Principle]] zu genügen.
Versuchen Sie weitere Modularisierungsmöglichkeiten zu finden, um dem Single Responsibility Principle noch besser zu genügen.


Nun können Sie auch noch Grunt konfigurieren. Hier wäre das noch nicht so wichtig,
Musterlösung:<br/>
da Grunt auch nicht mehr leistet, als die zuvor getesteten NPM-Skript-Befehle.
v03: [https://glossar.hs-augsburg.de/beispiel/tutorium/2024/wk_hello_world/wk_hello_world_04/web/v04_3/index.html <code>index.html</code>], Git:
Allerdings kann man mit Grunt nicht nur Webpack, sondern auch noch viele
[{{Git-Server}}/kowa/wk_hello_world_04/-/tree/master/web/v04_5?ref_type=heads v04_3]<br />
weitere Tools verwalten. Ich persönlich finde es ganz angenehm, weil alle Tools
v04: [https://glossar.hs-augsburg.de/beispiel/tutorium/2024/wk_hello_world/wk_hello_world_04/web/v04_4/index.html <code>index.html</code>], Git:
mit Hilfe eines einzigen Werkzeugs administriert werden.
[{{Git-Server}}/kowa/wk_hello_world_04/-/tree/master/web/v04_5?ref_type=heads v04_4]


Fügen Sie folgende Datei ins Wurzelverzeichnis Ihrer Projektes ein:
== Konfigurierbarkeit ==


'''<code>gruntfile.js</code>'''
Entfernen Sie programmspezifische Konstanten aus den JavaScript-Dateien und definieren Sie diese in einer Datei <code>config.js</code> oder (sehr viel besser) <code>config.json</code>, um dem Prinzip der [[Programmierprinzipien#Konfigurierbarkeit.2C_Customizability|Konfigurierbarkeit]] zu genügen.  
<source lang="javascript">
const webpack      = require('webpack'),
      webpackConfig = require('./webpack.config.js');


module.exports = function(grunt)
Musterlösung:<br/>
{
v03: [https://glossar.hs-augsburg.de/beispiel/tutorium/2024/wk_hello_world/wk_hello_world_04/web/v04_5/index.html <code>index.html</code>], Git: [{{Git-Server}}/kowa/wk_hello_world_04/-/tree/master/web/v04_5?ref_type=heads v04_5]
  // Project configuration.
  grunt.initConfig
  ({
    pkg: grunt.file.readJSON('package.json'),


    webpack:
Ich verwende hier die Funktionen <code>getJson</code> und <code>controlerKey</code> (bzw. die allgemeinere Variante <code>ControllerKey</code>, eine ECMAScript-Klasse), die erst später eingeführt und erklärt werden.
    {
      options: webpackConfig,


      bundle:
Die Musterlösung müsste noch ausgebaut werden:
      {
* Für die Internationalisierung müsste eine interaktive Auswahlmöglichkeit geschaffen werden. Zurzeit kann der Sprachwechsel nur erfolgen, indem in <code>main.js</code> der Array-Zugriff <code>["de"]</code> durch <code>["en"]</code> ersetzt wird.
      },
* Für die Internationalisierung müssten eigene Module zur Verfügung gestellt werden.


      compress:
==Fortsetzung des Tutoriums==
      {
        plugins:
          webpackConfig.plugins.concat
          ( new webpack.optimize.UglifyJsPlugin({ mangle: true }) )
      },


      watch:
Wenn man die Anwendung noch einmal mit Google testet, erhält man eine neue Empfehlung
      {
        watch:    true,
        keepalive: true
      }
    },
  });


  // Register task(s)
[https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_2%2Findex.html], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_3%2Findex.html], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_4%2Findex.html], [https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb%2Fv04_5%2Findex.html]:
  grunt.loadNpmTasks('grunt-webpack');
<blockquote cite="https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2025%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb_v01%2Fv01_1%2Findex.html">
  grunt.loadNpmTasks('grunt-contrib-watch');
* [https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains?hl=de Verkettung kritischer Anfragen vermeiden] – 1/2/4/5 Ketten gefunden
* [https://developer.chrome.com/docs/lighthouse/performance/resource-summary?hl=de Halten Sie die Anfrageanzahl niedrig und die Übertragungsgröße gering] – 5 Anfragen • 5 KiB.
</blockquote>


  grunt.registerTask('default', ['webpack:bundle']);
''Anmerkung: Die aktuelle Version von PageSpeed Insights zeigt die zweite Empfehlung nicht mehr an, vermutlich weil das Hello-World-Beispiel so klein ist, dass die vorgeschlagene Verbesserung keine Vorteile mehr bringt. Wir beachten die ehemalige Empfehlung trotzdem.''  
  grunt.registerTask('bundle',   ['webpack:bundle']);
  grunt.registerTask('compress', ['webpack:compress']);
  grunt.registerTask('watch',    ['webpack:watch']);
};
</source>


Damit stehen Ihnen folgende Grunt-Befehle zur Verfügung:
Das heißt, wir sollten die verschiedenen Dateien
<code>main.js</code>, <code>greet.js</code> etc. wieder zu einer Datei zusammenfügen.
Und wir sollten alle Dateien komprimieren: Überflüssige Kommentare, Leerzeichen und Zeilenumbrüche
löschen, lange Variablen-, Konstanten- und Funktionsnamen verkürzen etc. 


* <code>grunt</code> bzw. <code>grunt bundle</code> (entspricht <code>npm run bundle</code>)
Dies machen wir im [[HTML5-Tutorium: JavaScript: Hello World 05|5. Teil des Tutoriums]].
* <code>grunt compress</code> (entspricht <code>npm run compress</code>)
Wenn man die Vorschläge von Google beachtet,
* <code>grunt watch</code> (entspricht <code>npm run watch</code>)
beschleunigt dies die Übertragung der Dateien vom Server zum Client, macht aber  den Code unlesbar.
Die Zusammenführung zweier Dateien widerspricht dem Prinzip der Modularisierung. Das Entfernen von Kommentaren,
Leerzeichen und Leerzeilen ist aus Entwickler-Sicht vollkommen kontraproduktiv.


Interessant ist, dass hier die Komprimierung der Zieldatei mit Hilfe des Webpack-Plugins
Daher hat es sich eingebürgert, modularen Code mit Kommentaren und Leerzeichen zu schreiben. Dieser wird dann, bevor er an einen Browser übergeben wird automatisch zusammengefasst und komprimiert (minifiziert).  
<code>UglifyJsPlugin</code> und nicht mit Hilfe der Kommandozeilen-Option <code>-f</code> erzielt wird.
Diese Plugin bietet viele Optionen, mit denen der Kompressionsvorgang gezielt gesteuert werden kann.  


Und noch einmal (sobald alles funktioniert):
Sie sollten nun [[HTML5-Tutorium: JavaScript: Hello World 05|Teil 5 des Tutoriums]] bearbeiten.
* Klick auf das SVN-Commit-Icon.
Dort erfüllen wir die Google-Vorgaben mittels <code>vite</code>, einem mächtiges Werkzeug zur Umwandlung von Web-Dateien. Leider ist die Konfiguration dieses Werkzeugs sehr komplex.


==Quellen==
==Quellen==
<references/>
<references/>
<ol>
<ol>
<li value="7"> {{Quelle|Kowarschick, W.: Multimedia-Programmierung}}</li>
<li value="1"> {{Quelle|Kowarschick, W.: Web-Programmierung}}</li>
</ol>
</ol>
<noinclude>[[Kategorie: HTML5-Tutorium: JavaScript: Hello World]][[Kategorie: HTML5-Beispiel]][[Kategorie:Kapitel:Multimedia-Programmierung:Beispiele]]</noinclude>
<noinclude>[[Kategorie: HTML5-Tutorium: JavaScript: Hello World]][[Kategorie: HTML5-Beispiel]][[Kategorie:Kapitel:Multimedia-Programmierung:Beispiele]]</noinclude>

Aktuelle Version vom 8. April 2025, 06:36 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
v01_1: index.html (HTML validate, CSS validate, Google PageSpeed)
v01_2: index.html (HTML validate, CSS validate, Google PageSpeed)
v01_3: index.html (HTML validate, CSS validate, Google PageSpeed)
v01_4: index.html (HTML validate, CSS validate, Google PageSpeed)
v01_5: index.html (HTML validate, CSS validate, Google PageSpeed)

Git-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 (vgl. Programmierprinzipien).

Modularisierung

Eine Anwendung, wie z. B. ein HTML5-Spiel, besteht aus diversen unterschiedlichen Komponenten mit ganz unterschiedlichen Aufgaben. Bei einem Web-Frontend müssen beispielsweise verschieden Komponenten (Buttons, Scrolbars, Textfelder, Datumsfelder etc.) erstellt werden. 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 Spielentwicklung und teilweise während der Spielausführung (Runtime) erfolgt.

An der Entwicklung eines Web-Systems oder 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 eine Anwendung 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.

Je mehr Dateien von unterschiedlichen Autoren erstellt werden, desto größer ist die Gefahr, dass es beim Zugriff auf globale Variablen zu Namenskollisionen kommt. Außerdem weiß niemand, welche globale Größe noch benötigt wird und welche nicht. Sicherheitshalber löscht man daher keine dieser Variablen, auch wenn sie vollkommen veraltet sind.

Daher gilt der Grundsatz: Verwende so wenig globale Größen wie möglich.

Erstellen eines neuen Projekts

Erstellen Sie ein neues Projekt hello_world_04 als Fork von hello_world_03 (siehe Hello-World-02-Tutorium).

Die App

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 web-Verzeichnis Ihres Projekts folgende Ordner an:

  • web/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 ist das hier nicht notwendig. Es wird nur die Dateien „head.css“ und „body.css“ geben. Verschieben Sie die Datei web/main.css in diesen Ordner.
  • web/js: Hierher kommen die JavaScript-Dateien, die von der Anwendung benötigt werden. Verschieben Sie die Datei web/main.js in diesen Ordner.

Nehmen Sie in der Datei web/index.html folgende beiden Ersetzungen vor:

  • main.csscss/main.css
  • main.jsjs/main.js

Wenn Sie jetzt die Datei index.html mit dem Live Server ausführen, sollte Ihre Anwendung wieder funktionieren.

Sollte dies der Fall sein, so sollten Sie die Änderungen committen und evtl. auch gleich per git push auf den Git-Server hochladen. Es schadet übrigens nie, vor einen Commit oder gar einen Push noch einmal zu überprüfen, ob die Anwendung noch läuft.

Modularisierung der Datei main.js

Dateien wie die ECMAScript-Datei main.js aus dem dritten Teil des Tutoriums werden sehr schnell groß, wenn man alle Funktionalität in diese eine Datei einfügt. Dies reduziert die Lesbarkeit und die Wartbbarkeit des Codes. Bertrand Meyer, einer der wesentlichen Begründer der modularen und objektorientierten Programmierung, fordert, dass ein Programmcode in sogenannte Module zerlegt werden sollen. Ein Modul stellt eine gewisse Funktionalität zur Verfügung. Die zugehörigen Funktionen werden exportiert und können von anderen Modulen importiert werden, sofern sie diese Funktionalität zur Erfüllung ihrer Aufgabe benötigen. Meyer hält es für sinnvoll, dass ein Modul jeweils nur eine oder sehr wenige Aufgaben erfüllt. Damit können Module von vielen anderen Modulen wiederverwendet werden.

Die Datei main.js wird im Sinne von Meyer 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üßungsfunktiontionalität.

Erstellen Sie die JavaScript-Datei web/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. (Funktionen ohne Export-Anweisung könnten von anderen ES-6-Dateien nicht importiert werden.)

In der Datei web/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 Import-Anweisung ein:

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

Damit wird erreicht, dass alle (*) Elemente, die in der Datei greet.js exportiert werden, unter ihrem jeweiligen Namen im Objekt greet gespeichert werden. Das heißt, innerhalb der Datei main.js gibt es nun die beiden Funktionen greet.sayHello und greet.sayHelloOnEnter.

Fügen Sie jetzt noch das Attribut type="module" in das Script-Element in der Datei index.html ein. Damit teilen Sie dem Browser mit, dass Sie die ECMAScript-6-Befehle import und export verwenden möchten.

Anderenfalls hätten sie Zugriff auf alle Objekte, Konstanten, Variablen und Funktionen einer geladenen Datei, so wie die bis ECMAScript 5 üblich war. Das heißt, das Laden einer Datei konnte zu massiven Problemen führen, wenn in zwei verschiedenen Dateien unterschiedliche Objekte etc. zufälligerweise gleich benannt wurden. Um diese Probleme zu umgehen gab es zahlreiche ziemlich aufwändige Hacks. Mit dem Modul-Konzept von ECMAScript gehören diese Probleme der Vergangenheit an.

Damit sollte Ihre Anwendung wieder funktionieren.

Vergessen Sie nicht, Ihr aktuelles Projekt zu committen und auf dem Git-Server zu speichern, sobald alles funktioniert.´

Musterlösung v04_1

Verison v04_1 index.html (HTML validate, CSS validate, Google PageSpeed)

Laden der CSS- und der JavaScript-Dateien

Führen Sie Google PageSpeed für die Musterlösung der vorherigen Teilaufgabe aus und sehen Sie sich an, welche Verbesserungsvorschläge Google macht.

Folgendes findet Google PageSpeed verbesserungswürdig:

  1. Ressourcen entfernen, die das Rendering blockieren.
    Mögliche Einsparung von 300 ms.
  2. Verkettung kritischer Anfragen vermeiden
    2 Ketten gefunden
  3. [Largest Contentful Paint-Element 1.350 ms https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint?hl=de]
  4. Dokument enthält keine Meta-Beschreibung

Anmerkung: Google Pagespped ist auch über die Chroeme-Entwickler-Konsole zugänglich. Damit können auch lokale Server (<ocde>localhost o. Ä.) getestet werden. Echte Prformanzprobleme erkennt man aber eher, wenn man seinen Live-Server testet.

Eine Meta-Beschreibung hinzuzufügen, ist kein Problem. Fügen Sie folgende Zeile in den Head-Bereich der HTML-Datei ein.

<meta name="description" 
      content="A Hello World application that demonstrates best practices for HTML, CSS, and JavaScript files."
>

Das effiziente Laden der JavaScript- und CSS-Dateien ist viel komplexer, aber auch viel wichtiger, da die CSS- und JavaScript-Dateien werden im Laufe der Zeit immer zahlreicher und/oder größer werden. Diese Dateien zu laden, dauert seine Zeit. Auch das Laden von vielen kleinen Dateien dauert deutlich länger, als das Laden einer einzigen großen Datei. Das aber widerspricht der Modularisierung, die wir vorher vorgenommen haben. Modularisierung und Performance widersprechen sich zunächst einmal.

In Web-Anwendung aus dem dritten Teil des Tutoriums stehen die Verweise auf die CSS- und JavaScript-Dateien im head-Bereich des Dokuments. Die CSS-Datei wird vollständig geladen, bevor der body-Bereich eingelesen wird. Die JavaScript-Datei main.js wird dagegen parallel zum Body-Bereich geladen. Dies wird durch die Angabe von async im Script-Tag wird erreicht.

Das heißt aber, dass der Browser keine Inhalte des HTML-Dokuments darstellen kann, solange er die CSS-Datei (und evtl. weitere Dateien wie Fonts, Hintergrundbilder etc.) 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 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: https://de.wikipedia.org/wiki/Ampel

Was also machen?

CSS-Dateien

Damit das Problem mit dem falschen Layout nicht auftritt, empfiehlt Google (PageSpeed Insights), 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.

PageSpeed Insights (für Musterlösung v04_1):

Ressourcen blockieren den First Paint deiner Seite. Versuche, wichtiges JS und wichtige CSS inline anzugeben und alle nicht kritischen JS und Stile zurückzustellen.

Gehen Sie so vor, wie es Google empfohlen hat. 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 berücksichtigt.

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

body
{ background-color: #C5EFFC; }

.hidden
{ display: none; }

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

<link rel="stylesheet" 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. Noch besser ist es gemäß den Google-Empfehlungen, wenn man die CSS-Regeln direkt in die HTML-Datei einfügt. Ersetzen Sie also das Link-Element durch folgendes Style-Element:

<style>
body
{ background-color: #C5EFFC; }

.hidden
{ display: none; }
</style>

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

Es ist sinnvoll, die Datei main.css in body.css umzubenennen: head.css wird im HTML-Head-Bereich vor dem Body-Bereich geladen, body.css soll parallel zum Body-Bereich geladen werden und dann zur Verfügung stehen, wenn der Body vollständig geladen wurde.

Leider ist CSS-Link-Element nicht möglich, async anzugeben, wie wir dies beim Skript-Element gemacht haben, um JavaScript-Code parallel zum Body-Element zu laden. Hier hilft derzeit nur ein kleiner Hack:

<!-- Load body.css asynchronously, i.e. parallel to the body content. -->
<link rel="preload" href="css/body.css" as="style" onload="this.rel='stylesheet'">

Die Datei body.css wird für den Medientyp style parallel zum HTML-Dokument geladen, aber nicht aktiviert. Um aus dem Inhalt der vorab geladenen Datei für den Browser ein Stylesheet zu machen, muss man den Wert des Attributs rel in 'stylesheet' abändern. Das sollte erst erfolgen, wenn das Dokument vollständig geladen wurde, wenn also das das Fensterobjekt des Browsers, in dem das HTML-Dokument gerendert werden soll, das Ereignis load feuert. Für dieses Ereignis kann man entweder im link-Tag den Händler onload einfügen oder man schreibt den Aktivierungsbefehl in die Init-Funktion in der Datei main.js.

Für die zweite Variante sollte man dem link-Element eine eindeutigen Identifikator zuweisen:

<link id="body_css" rel="preload" href="css/body.css" as="style">

Danach kann man den Aktivierungsbefehl in die Funktion init in der Datei main.js einfügen.

document.getElementById('body_css').rel = 'stylesheet';

Zusammenfassung:

  • In head.css stehen möglichst wenig CSS-Anweisungen. Sie sollten ausreichen, um die aktuelle Seite “above the fold” („über dem Zeitungsknick“) fehlerfrei zu rendern. Das heißt, es sollte alles korrekt gerendert werden, was nach dem Laden im größtmöglichen Browserfenster sichtbar ist.
  • Der Inhalt der head.css sollte direkt in den Head-Bereich der HTML-Seite eingefügt werden.
  • Die restlichen CSS-Anweisungen werden in die Datei body.css eingetragen.
  • Diese Datei wird mittels eines kleinen Hacks asynchron, d. h. parallel zum Body-Element geladen.

index.html

Da die Datei head.css nur die Hintergrundfarbe sowie die .hidden-Regel enthält, sollten alle HTML-Elemente, die gerendert werden sollen, zunächst unsichtbar gemacht werden. Bei einer Web-Anwendung, wie einem Spiel, die zum Laden länger braucht, könnte man eine Sanduhr oder einen Ladebalken einfügen, um anzuzeigen, dass man noch etwas warten muss. Üblicherweise, sollte man dafür sorgen, dass das Laden einer Seite so schnell geht, dass der Benutzer gar nicht erst ungeduldig wird.

Fügen Sie die CSS-Klasse hidden in beide HTML-Sektions ein:

<section id="section_form" class="hidden">
...
</section>
<section id="section_hello" class="hidden">
...
</section>

Wenn man jetzt das Dokument im Browser öffnet, sieht man nichts, außer einem hellblauen Hintergrund.

Abhilfe schafft hier ein kleiner JavaScript-Befehl, der hidden von der Eingabe-Section hidden-form entfernt, sobald das Dokument vollständig geladen wurde (samt allen CSS- und JavaScript-Dateien):

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

An welche Stelle dieser Befehl eingefügt wird, erfahren Sie im nachfolgenden Abschnitt.

JavaScript-Dateien

Für JavaScript-Dateien empfiehlt Google ebenfalls, diese nicht schon zu Beginn zu laden, sondern das Laden „aufzuschieben“. Das machen wir bereits mit Hilfe des Async-Attributs.

Fügen Sie nun den zuvor erwähnten Befehl

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

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

Wenn Sie jetzt die Web-App starten, sollte sie wieder funktionieren.

Was haben Sie erreicht? Solange die Anwendung geladen wird, sehen Sie nur einen blauen Hintergrund ohne Inhalt, bzw. i. Allg. einen so genannten Splash Screen. Wenn dann alles, d. h. alle CSS- und JavaScript-Dateien geladen wurden, wird die eigentliche Anwendung sichtbar.

Vergessen Sie nicht: Commit!

Verbesserung der Init-Funktion

Bislang sieht die Init-Funktion folgendermaßen aus.

function init()
{ document.getElementById('button_submit')
          .addEventListener('click', greet.sayHello);
  window.addEventListener('keydown', greet.sayHelloOnEnter);

  document.getElementById('body_css').rel = 'stylesheet';

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

Man kann auch einen anderen Import-Befehl verwenden, um auf das Objekt greet verzichten zu können.

import { sayHello, sayHelloOnEnter } from './greet.js'
 
function init()
{ document.getElementById('button_submit')
          .addEventListener('click', sayHello);
  window.addEventListener('keydown', sayHelloOnEnter);

  document.getElementById('body_css').rel = 'stylesheet';

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

Darüber hinaus kann man die Autofokus-Funktionalität verbessern. Das Autofokus-Attribut reagiert laut HTML-Standard nur in manchen Situation. Wenn man die Situationen selbst definiert werden, in den Autofokus aktiv wird, muss man den Fokus per JavaSript setzen, sobald eine dieser Situationen eintritt.

function autofocus()
{ document.getElementById('input_name').focus();   
}

function reset()
{ document.getElementById('input_name').value = '';
  autofocus();  
}

function init()
{ // Say hello, if the submit button is clicked or the buttoen 'Enter' is pressed.
  document.getElementById('button_submit').addEventListener('click', sayHello);
  window.addEventListener('keydown', sayHelloOnEnter);

  // Set the focus it the window is reentered.
  window.addEventListener('visibilitychange', autofocus);
  
  // If the reset button is clicked, not only the textares are reset but also the focus is replaced.
  document.getElementById('button_reset').addEventListener('click', reset);

  // Activate the body css file that had been preloaded asynchronously.
  document.getElementById('body_css').rel = 'stylesheet';

  // Now that all files have been loaded, the app can be started.
  // So the form is made visible!
  document.getElementById('section_form').classList.remove('hidden');
  autofocus();
}

Musterlösung v04_2

Verison v04_2 index.html (HTML validate, CSS validate, Google PageSpeed)

Weitere Modularisierung

Teilen Sie die Datei greet.js in zwei Dateien greet.js und greet_on_enter.js mit jeweils eines Funktion auf, um dem Single Responsibility Principle zu genügen. Versuchen Sie weitere Modularisierungsmöglichkeiten zu finden, um dem Single Responsibility Principle noch besser zu genügen.

Musterlösung:
v03: index.html, Git: v04_3
v04: index.html, Git: v04_4

Konfigurierbarkeit

Entfernen Sie programmspezifische Konstanten aus den JavaScript-Dateien und definieren Sie diese in einer Datei config.js oder (sehr viel besser) config.json, um dem Prinzip der Konfigurierbarkeit zu genügen.

Musterlösung:
v03: index.html, Git: v04_5

Ich verwende hier die Funktionen getJson und controlerKey (bzw. die allgemeinere Variante ControllerKey, eine ECMAScript-Klasse), die erst später eingeführt und erklärt werden.

Die Musterlösung müsste noch ausgebaut werden:

  • Für die Internationalisierung müsste eine interaktive Auswahlmöglichkeit geschaffen werden. Zurzeit kann der Sprachwechsel nur erfolgen, indem in main.js der Array-Zugriff ["de"] durch ["en"] ersetzt wird.
  • Für die Internationalisierung müssten eigene Module zur Verfügung gestellt werden.

Fortsetzung des Tutoriums

Wenn man die Anwendung noch einmal mit Google testet, erhält man eine neue Empfehlung

[1], [2], [3], [4]:

Anmerkung: Die aktuelle Version von PageSpeed Insights zeigt die zweite Empfehlung nicht mehr an, vermutlich weil das Hello-World-Beispiel so klein ist, dass die vorgeschlagene Verbesserung keine Vorteile mehr bringt. Wir beachten die ehemalige Empfehlung trotzdem.

Das heißt, wir sollten die verschiedenen Dateien main.js, greet.js etc. wieder zu einer Datei zusammenfügen. Und wir sollten alle Dateien komprimieren: Überflüssige Kommentare, Leerzeichen und Zeilenumbrüche löschen, lange Variablen-, Konstanten- und Funktionsnamen verkürzen etc.

Dies machen wir im 5. Teil des Tutoriums. Wenn man die Vorschläge von Google beachtet, beschleunigt dies die Übertragung der Dateien vom Server zum Client, macht aber den Code unlesbar. Die Zusammenführung zweier Dateien widerspricht dem Prinzip der Modularisierung. Das Entfernen von Kommentaren, Leerzeichen und Leerzeilen ist aus Entwickler-Sicht vollkommen kontraproduktiv.

Daher hat es sich eingebürgert, modularen Code mit Kommentaren und Leerzeichen zu schreiben. Dieser wird dann, bevor er an einen Browser übergeben wird automatisch zusammengefasst und komprimiert (minifiziert).

Sie sollten nun Teil 5 des Tutoriums bearbeiten. Dort erfüllen wir die Google-Vorgaben mittels vite, einem mächtiges Werkzeug zur Umwandlung von Web-Dateien. Leider ist die Konfiguration dieses Werkzeugs sehr komplex.

Quellen

  1. Kowarschick (WebProg): Wolfgang Kowarschick; Vorlesung „Web-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2024; Quellengüte: 3 (Vorlesung)