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

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Keine Bearbeitungszusammenfassung
 
(99 dazwischenliegende Versionen desselben Benutzers 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/2018/helloworld/WK_HelloWorld04a/web/index.html <code>index.html</code> (4a)],
'''Musterlösung'''<br/>
[https://glossar.hs-augsburg.de/beispiel/tutorium/2018/helloworld/WK_HelloWorld04b/web/index.html <code>index.html</code> (4b)],
v01: [https://glossar.hs-augsburg.de/beispiel/tutorium/2024/wk_hello_world/wk_hello_world_04/web_v01/index0.html <code>index0.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2024%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb_v01%2Findex0.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2024%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb_v01%2Findex0.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis/https-glossar-hs-augsburg-de-beispiel-tutorium-2024-wk_hello_world-wk_hello_world_04-web_v01-index0-html/2uuk2uqiqo?form_factor=mobile Google PageSpeed])<br/>
[https://glossar.hs-augsburg.de/beispiel/tutorium/2018/helloworld/WK_HelloWorld04c/web/index.html <code>index.html</code> (4c)]<br/>
v01: [https://glossar.hs-augsburg.de/beispiel/tutorium/2024/wk_hello_world/wk_hello_world_04/web_v01/index1.html <code>index1.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2024%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb_v01%2Findex1.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2024%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb_v01%2Findex1.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis/https-glossar-hs-augsburg-de-beispiel-tutorium-2024-wk_hello_world-wk_hello_world_04-web_v01-index1-html/jfhoc3pzhk?form_factor=mobile Google PageSpeed])<br/>
Repositories (unterschiedliche webpack-Varianten): [https://gitlab.multimedia.hs-augsburg.de:8888/kowa/WK_HelloWorld04a.git WK_HelloWorld04a.git], [https://gitlab.multimedia.hs-augsburg.de:8888/kowa/WK_HelloWorld04b.git WK_HelloWorld04b.git], [https://gitlab.multimedia.hs-augsburg.de:8888/kowa/WK_HelloWorld04c.git WK_HelloWorld04c.git]
v01: [https://glossar.hs-augsburg.de/beispiel/tutorium/2024/wk_hello_world/wk_hello_world_04/web_v01/index2.html <code>index2.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2024%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb_v01%2Findex2.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2024%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb_v01%2Findex2.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis/https-glossar-hs-augsburg-de-beispiel-tutorium-2024-wk_hello_world-wk_hello_world_04-web_v01-index2-html/0r6b3j26g6?form_factor=mobile Google PageSpeed])<br/>
v02: [https://glossar.hs-augsburg.de/beispiel/tutorium/2024/wk_hello_world/wk_hello_world_04/web_v02/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2024%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb_v02%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2024%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb_v02%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis/https-glossar-hs-augsburg-de-beispiel-tutorium-2024-wk_hello_world-wk_hello_world_04-web_v02-index-html/voo4cmfcnv?form_factor=mobile Google PageSpeed])<br/>
v03: [https://glossar.hs-augsburg.de/beispiel/tutorium/2024/wk_hello_world/wk_hello_world_04/web_v03/index.html <code>index.html</code>] ([https://validator.nu/?doc=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2024%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb_v03%2Findex.html HTML validate], [https://css-validator.org/validator?uri=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2024%2Fwk_hello_world%2Fwk_hello_world_04%2Fweb_v03%2Findex.html&profile=css21&usermedium=all&warning=1&lang=de CSS validate], [https://pagespeed.web.dev/analysis/https-glossar-hs-augsburg-de-beispiel-tutorium-2024-wk_hello_world-wk_hello_world_04-web_v02-index-html/voo4cmfcnv?form_factor=mobile Google PageSpeed])<br/>
 
[{{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 [[Modul|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 Spielentwicklung und teilweise
während der Spielausführung (Runtime) erfolgt.  
während der Spielausfü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 (das heißt, 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 Sie für Ihr Projekt ein Git-Repository, committen Sie und pushen Sie es dann auf den Git-Server.


Dadurch, dass die Datei  <code>.gitignore</code> das Verzeichnis <code>node_modules</code>  enthält,
==Erstellen eines neuen Projekts==
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 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]]).


==Erstellen einer Ordnerstruktur==
==Erstellen einer Ordnerstruktur==
Zeile 51: Zeile 48:
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>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.  '''Verschieben Sie die Datei <code>web/index.html</code> in diesen Ordner.'''
* <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/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 nur die Dateien „<code>head.css</code>“ und „<code>main.css</code>“ geben. '''Verschieben Sie die Datei <code>web/main.css</code> in diesen Ordner.'''
* <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>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. '''Verschieben Sie die Datei <code>web/main.js</code> in diesen Ordner.'''
* <code>web</code>: (Dieser Ordner existiert schon.) Der Inhalt dieses Ordners kommt später auf einen echten Web-Server.
* <code>web/css</code>: 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.)
* <code>web/js</code>: Hierher kopiert Node.js die erzeugten JavaScript-Dateien.
 
Wenn Sie jetzt die Datei <code>src/index.html</code> mittels <code>run</code> ausführen, sollte Ihre Anwendung immer noch laufen. '''WebStorm hat die Pfade in der Datei <code>index.html</code>, unter denen die Dateien <code>main.css</code> und <code>main.js</code> zu finden sind, automatisch angepasst.'''
Vergleichen Sie die <code>index.html</code>-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 „<code>web</code>“ von der  WebStorm-Fehlerüberwachung aus:


* Rechtsklick auf den Ordner „<code>web</code>→ <code>Mark Directory as</code> → <code>Excluded</code>
Nehmen Sie in der Datei <code>web/index.html</code> folgende beiden Ersetzungen vor:
* <code>main.css</code> → <code>css/main.css</code>
* <code>main.js</code> → <code>js/main.js</code>


Für den Ordner „<code>node_modules</code>“ brauchen Sie dies nicht zu machen. Dieser Ordner wird von WebStorm-Fehlerüberwachung automatisch ignoriert.
Wenn Sie jetzt die Datei <code>index.html</code> mittels <code>run</code> ausführen, sollte Ihre Anwendung wieder funktionieren.  


Sie sollten nach jeden größeren Entwicklungsschritt die Änderungen im Repository speichern:
Sollte dies der Fall sein, so sollten Sie die Änderungen committen und evtl. auch gleich
 
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.'''
* Klick auf das Commit-Icon.
 
Hin und wieder ist auch ein <code>git push</code> angebracht. '''Es schadet übrigens nicht, vorher noch einmal zu überprüfen, ob die Anwendung immer noch läuft.'''


==Die App==
==Die App==
Zeile 88: Zeile 71:
In der dritten Version der Web-Anwendung stehen die Verweise auf diese Dateien im <code>head</code>-Bereich des Dokuments.
In der dritten Version der Web-Anwendung stehen die Verweise auf diese Dateien im <code>head</code>-Bereich des Dokuments.
Dieser wird vollständig geladen, bevor der <code>body</code>-Bereich eingelesen wird. (Genauer gesagt, nur die CSS-Datei wird vollständig geladen. Durch die Angabe von <code>async="async"</code> im  
Dieser wird vollständig geladen, bevor der <code>body</code>-Bereich eingelesen wird. (Genauer gesagt, nur die CSS-Datei wird vollständig geladen. Durch die Angabe von <code>async="async"</code> im  
Script-Tag wird erreicht, dass diese Datei geladen wird, während der Body-Bereich gerendert wird.)
Script-Tag wird erreicht, dass die JavaScript-Datei <code>main.js</code> geladen wird, während der Body-Bereich gerendert wird.)


Das heißt aber, dass der Browser keine Inhalte des HTML-Dokuments
Das heißt aber, dass der Browser keine Inhalte des HTML-Dokuments
Zeile 103: Zeile 86:


===CSS-Dateien===
===CSS-Dateien===
Damit das Problem mit dem falschen Layout nicht auftritt, empfiehlt Google, 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  
Damit das Problem mit dem falschen Layout nicht auftritt, empfiehlt Google ([https://developers.google.com/speed/pagespeed/insights/ PageSpeed Insights]),  
(oder mittels des PageSpeed-Optimization-Modul von Google) gelesen werden.<ref>{{Quelle|Google (Web)}}, Kapitel „CSS-Bereitstellung optimieren“, https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery</ref> (Früher hat Google ernsthaft empfohlen, das Link-Element ganz ans Ende der HTML-Datei  zu stellen, also '''nach''' dem schließenden <code>html</code>-Tag. Das wäre aber natürlich nicht standardkonform.)
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  
(oder mittels des PageSpeed-Optimization-Modul von Google) gelesen werden.


Gehen Sie so vor, wie es Google empfiehlt. Genauer gesagt, gehen Sie so vor,  
[https://pagespeed.web.dev/analysis/https-glossar-hs-augsburg-de-beispiel-tutorium-2024-wk_hello_world-wk_hello_world_04-web_v01-index0-html/2uuk2uqiqo?form_factor=mobile PageSpeed Insights (für Musterlösung index0.html)]:
<blockquote cite="https://pagespeed.web.dev/analysis/https-glossar-hs-augsburg-de-beispiel-tutorium-2024-wk_hello_world-wk_hello_world_04-web_v01-index0-html/2uuk2uqiqo?form_factor=mobile">
Empfehlung
 
Ressourcen blockieren den First Paint Ihrer Seite. Versuchen Sie, wichtiges JS und wichtige CSS inline anzugeben und alle nicht kritischen JS und Stile aufzuschieben. Weitere Informationen.
</blockquote>
 
''Anmerkung: Die aktuelle Version von PageSpeed Insights zeigt diese Empfehlung nicht mehr an, vermutlich weil das Hello-World-Beispiel so klein ist, dass die vorgeschlagene Verbesserung keine Vorteile mehr bringt.''
 
Gehen Sie so vor, wie es Google früher empfohlen hat. Genauer gesagt, 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
wie es unter [[Multimedia-Programmierung: Best Practices#CSS|Multimedia-Programmierung: Best Practices]] beschrieben ist. In diesem und den folgenden Tutorien werden
die dort beschriebenen Prinzipien berücksichtigt.
die dort beschriebenen Prinzipien berücksichtigt.
Zeile 112: Zeile 105:
Legen Sie die Datei <code>src/css/head.css</code> an und fügen Sie folgenden (sehr kurzen!) Code ein:
Legen Sie die Datei <code>src/css/head.css</code> an und fügen Sie folgenden (sehr kurzen!) Code ein:


<source lang="javascript">
<source lang="css">
html, body
body
{ background-color: #C5EFFC; }
 
.hidden
{ display: none; }
</source>
 
In der Datei <code>src/index.html</code> müssen Sie das Link-Element in Header folgendermaßen
<source lang="html">
<link rel="stylesheet" href="css/head.css"/>
</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-Regelsätze direkt in die HTML-Datei einfügt. Ersetzen Sie also das
Link-Element durch folgendes Style-Element:
 
<source lang="html">
<style>
body
{ background-color: #C5EFFC; }
{ background-color: #C5EFFC; }


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


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


In der Datei <code>src/index.html</code> müssen Sie das Link-Element in Header folgendermaßen
Es ist sinnvoll, die Datei <code>main.css</code> in <code>body.css</code> umzubenennen:
<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.
 
Leider ist CSS-Link-Element nicht möglich, <code>async="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:
 
<source lang="html">
<!-- Load body.css asynchronously (a hack!), i.e. parallel to the body content.
    preload is not supported by some browsers:
      https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preload
    The noscript section is for browsers that do not support JavaScript at all.
-->
<link rel="preload" href="css/body.css" as="style" onload="this.rel='stylesheet'">
<link rel="stylesheet" href="css/body.css" media="print" onload="this.media='all'">
</source>
 
Die Datei <code>body.css</code> wird für den Medientyp <code>none</code> geladen. Diesen
Medientyp gibt es aber gar nicht. Es gibt die Typen <code>screen</code>,
<code>print</code>, <code>speech</code>, <code>all</code> sowie ein paar weitere
veraltete Typen. Da der Medientyp <code>none</code> vorerst nicht gebraucht wird, laden ihn moderne Browser asynchron. Sobald das HTML-Dokument vollständig geladen wurde, feuert das Ereignis <code>onload</code>. Sobald dies passiert, wird der Medientyp <code>none</code> durch <code>all</code> ersetzt. Daran erkennt der Browser, dass
dies eine CSS-Datei ist, die durchaus zum Layouten der aktuellen Seite verwendet werden soll. Und das macht der Browser dann auch.
 
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.
* Dies Datei wird mittels eines Hacks asynchron, {{dh}} parallel zum Body-Element geladen.
 
===<code>index.html</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,
dass das Laden einer Seite so schnell geht, dass der Benutzer gar nicht erst ungeduldig wird.
 
Fügen Sie die CSS-Klasse <code>hidden</code> in beide HTML-Sektions ein:
 
<source lang="html">
<source lang="html">
<link rel="stylesheet" type="text/css" href="css/head.css"/>
<section id="section_form" class="hidden">
...
</section>
<section id="section_hello" class="hidden">
...
</section>
</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.


===JavaScript-Dateien===
Wenn man jetzt das Dokument im Browser öffnet, sieht man nichts, außer einem hellblauen Hintergrund.
 
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>):


Für JavaScript-Dateien empfiehlt Google ebenfalls, diese nicht schon zu Beginn zu laden.<ref>{{Quelle|Google (Web)}}, Kapitel „Remove Render-Blocking JavaScript“, https://developers.google.com/speed/docs/insights/BlockingJS</ref> Dieser Vorschlag war bis vor kurzem noch sehr sinnvoll. Man könnte das
<source lang="html">
Skript-Element aus dem HTML-Header-Bereich entfernen und vor dem schließenden Body-Tag einfügen.
document.getElementById('section_form').classList.remove('hidden');
Bis vor Kurzem war diese Vorgehensweise auch sehr weit verbreitet.
</source>
Seitdem die meisten Browser die Async- und Defer-Attribute im Skript-Element unterstützen, ist dies
jedoch nicht 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 der HRML-Datei das Attribut
An welche Stelle dieser Befehl eingefügt wird, erfahren Sie im nachfolgenden Abschnitt.
<code>class="hidden"</code> einzufügen, 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.
===JavaScript-Dateien===


Fügen Sie nun den Befehl
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
<source lang="javascript">
<source lang="javascript">
document.getElementById('section_form').classList.remove('hidden');
document.getElementById('section_form').classList.remove('hidden');
</source>
</source>
als letzen Befehl in den Rumpf der Funktion <code>init</code> in der Datei
als letzen Befehl in den Rumpf der Funktion <code>init</code> in der Datei
<code>src/js/main.js</code> ein.
<code>web/js/main.js</code> ein.


Wenn Sie jetzt die Web-App starten, sollte sie wieder funktionieren (wenn auch mit einem sehr CSS-armen Layout).
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. (Besser wäre – wie gesagt – ein Splash Screen.) Wenn dann alles, {{dh}} alle CSS- und JavaScript-Dateien geladen wurden, wird die eigentliche Anwendung sichtbar. Dies ist bei großen Anwendungen,
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.  
wie einem Spiel, eine übliche Vorgehensweise.


Vergessen Sie nicht:
Vergessen Sie nicht: Commit!
* Klick auf das Commit-Icon.


===Modularisierung der JavaScript-Dateien===
===Modularisierung der JavaScript-Dateien===
Zeile 167: Zeile 213:
die zweite initialisiert die App, {{dh}} aktiviert die Begrüßungsfunktion.  
die zweite initialisiert die App, {{dh}} aktiviert die Begrüßungsfunktion.  


Erstellen Sie die JavaScript-Datei <code>src/js/greet.js</code> und verschieben Sie die
Erstellen Sie die JavaScript-Datei <code>web/js/greet.js</code> und verschieben Sie die
beiden Funktionen <code>sayHello</code> und <code>sayHelloOnEnter</code>
beiden Funktionen <code>sayHello</code> und <code>sayHelloOnEnter</code>
(jeweils samt Funktionsrumpf :-) ) in diese Datei.
(jeweils samt Funktionsrumpf :-) ) in diese Datei.
Zeile 176: Zeile 222:
Export-Anweisung könnten von anderen ES-6-Dateien nicht importiert werden.)
Export-Anweisung könnten von anderen ES-6-Dateien nicht importiert werden.)


In der Datei <code>src/js/main.js</code> werden jetzt diese beiden Funktionen importiert.
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)
Fügen Sie ganz am Anfang der Datei ({{dh}} vor der verbliebenen Init-Funktion)
die folgende Import-Anweisung ein:
die folgende Import-Anweisung ein:
Zeile 189: Zeile 235:
<code>greet.sayHello</code> und <code>greet.sayHelloOnEnter</code>.
<code>greet.sayHello</code> und <code>greet.sayHelloOnEnter</code>.


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


Fügen Sie jetzt noch das Attribut <code>type="module"</code> in das Script-Element in der
Fügen Sie jetzt noch das Attribut <code>type="module"</code> in das Script-Element in der
Datei <code>src/index.html</code> ein '''und löschen Sie gegebenenfalls das Attribut <code>type="text/javascript"</code>'''.  
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.
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.
<!--
'''Löschen Sie gegebenenfalls das Attribut <code>type="text/javascript"</code>'''.  
-->
Damit sollte Ihre Anwendung wieder funktionieren.
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 <code>greet.js</code> 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:
Vergessen Sie nicht, Ihr aktuelles Projekt zu committen und auf dem Git-Server zu speichern, sobald alles funktioniert.´


* <code>File</code>/<code>WebStorm</code> → <code>Settings</code>/<code>Preferences</code> → <code>Build, Execution, Deployment</code> → <code>Debugger</code>
== Verbesserung der Init-Funktion ==
* Häkchen bei <code>Allow unsigned requests</code>
* <code>OK</code>


Jetzt sollte die Anwendung wieder laufen (auch wenn Sie sie von WebStorm aus starten).
Bislang sieht die Init-Funktion folgendermaßen aus.


Vergessen Sie nicht:
<source lang="javascript">
* Klick auf das Commit-Icon.
function init()
{ document.getElementById('button_submit')
          .addEventListener('click', greet.sayHello);
  window.addEventListener('keydown', greet.sayHelloOnEnter);


===Node.js und webpack===
  document.getElementById('section_form').classList.remove('hidden');
 
}
Jetzt müssen sie „nur noch“ die CSS-Datei <code>main.css</code> laden, dann funktioniert Ihre
Anwendung wieder wie gewohnt. Und Sie ist überdies modular aufgebaut. (Sehr viel weiter kann man
eine Hello-World-Anwendung nicht modularisieren. :-) )
 
Google empfiehlt, die eigentliche CSS-Datei dynamisch zu laden. Dazu gibt es zahlreiche Möglichkeiten.
Ich empfehle derzeit webpack dafür einzusetzen<ref>webpack: https://webpack.js.org/</ref>.
 
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 dynamisch 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
(falls Sie nicht mehr genau wissen, wie man Node.js bedient, sollten Sie sich noch einmal
das Tutorium [[HTML5-Tutorium: JavaScript: Entwicklungsumgebung: Node.js]] ansehen):
 
<source lang="bash">
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
</source>
</source>


Führen Sie nun einen Git-Commit durch. Sorgen Sie dafür, dass auch die neu angelegten
Man kann auch einen anderen Import-Befehl verwenden, um auf das Objekt <code>greet</code> verzichten zu können.
Dateien <code>package.json</code> und <code>package-lock.json</code> im Git-Repository
gespeichert werden. Dies können Sie entweder beim Commit erzielen, indem Sie vor
<source lang="javascript">
<code>Unversioned Files</code> ein Häkchen setzen oder indem Sie vor dem Commit
import { sayHello, sayHelloOnEnter } from './greet.js'
für beiden Dateien Folgendes ausführen:
function init()
{ document.getElementById('button_submit')
          .addEventListener('click', sayHello);
  window.addEventListener('keydown', sayHelloOnEnter);


Rechtsklick auf den Dateinamen → <code>Git</code> → <code>Add</code>
   document.getElementById('section_form').classList.remove('hidden');
 
}
Achtung, wenn Git Ihnen anbietet, den Ordner <code>node_modules</code> zu speichern,
haben Sie die Datei <code>.gitignore</code> vergessen. Fügen Sie in diesem Fall unbedingt
die Datei [https://glossar.hs-augsburg.de/beispiel/tutorium/2018/.gitignore .gitignore] in Ihr Projekt ein!
 
====webpack====
 
Nun muss noch webpack konfiguriert werden, damit die Dateien <code>web/index.html</code>, <code>web/css/head.css</code>
und <code>web/js/mains.js</code> automatisch generiert werden können.
 
Erstellen Sie im Wurzelverzeichnis des Projektes die Datei <code>webpack.config.js</code>
(die Sie unbedingt auch in Git speichern sollten). Fügen Sie folgenden Code in diese Datei ein.
 
''<code>webpack.config.js</code>''
<source lang="html">
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'
                ]
      },
    ]
  }
};
</source>
</source>


Es handelt sich um ein JavaScript-Datei, die direkt von Node.js interpretiert wird.
Darüber hinaus kann man die Autofokus-Funktionalität verbessern. Das Autofokus-Attribut reagiert laut HTML-Standard nur in manchen Situation.
Aus diesem Grund wird auch nicht das EcmaScript-6-Modul-Konzept eingesetzt, sondern
Wenn man die Situationen  selbst definiert werden, in den Autofokus aktiv wird, muss man den Fokus per JavaSript setzen, sobald eine dieser Situationen eintritt.
[[CommonJS]], das Standard-Modulkonzept vom Node. Hier werden Module mittels <code>require</code>
<source lang="javascript">
und nicht mittels <code>import</code> importiert. Für Sie ist das allerdings nebensächlich,
function autofocus()
da Sie im Rahmen der Tutorien keine komplexen Konfigurationsdateien für webpack implementieren werden.
{ document.getElementById('input_name').focus(); }


Im Prinzip macht diese Datei nichts andere als ein Objekt zu exportieren,  
function init()
das webpack-Konfigurationsinformationen enthält:
{ document.getElementById('button_submit')
          .addEventListener('click', greet.sayHello);
  window.addEventListener('keydown', greet.sayHelloOnEnter);


* <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.
  window.addEventListener('focus', autofocus);
* <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> erzeugt, wobei <code>[name]</code> durch den Schlüsselname des Entry-Eintrags ersetzt wird. In der obigen Datei gibt es drei Entry-Elemente.
  document.getElementById('button_reset').addEventListener('click', autofocus);
* <code>plugins</code>: Hier werden webpack-Hilfsmodule geladen, die bestimmte Zusatzaufgaben erledigen. Sie verwenden derzeit 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,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ären Ausdrucks]] wird im Tutoriumsbeispiel geprüft, ob die aktuelle Datei auf <code>head.css</code>, <code>mains.css</code> oder <code>.html</code> 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 <code>npm</code> installiert.) Leider gibt es soviel Loader, dass die Auswahl geeigneter Loader nicht gerade einfach ist. Erschwerend 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
  document.getElementById('section_form').classList.remove('hidden');
<code>main.css</code> vom Style-Loader Anweisungen in die  Datei <code>main.js</code> geschrieben.
   autofocus();
Damit wird erreicht, dass die Datei  <code>main.css</code> in der Datei <code>main.js</code>
enthalten ist. Sobald <code>main.js</code> 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 <code>main.js</code>
geladen. Genau das wollten wir erreichen.
 
Die Datei <code>head.css</code> 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 <code>web/css/head.css</code> gespeichert.
 
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, in die referenzierende Datei integrieren. Zum Beispiel wird
die Datei <code>greet.js</code> automatisch in die Datei <code>main.js</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.
 
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 <code>scripts</code>-Objekt der Datei <code>package.json</code>
drei weitere Skript-Anweisungen ein:
 
<source lang="json">
"scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
  "dev": "webpack --mode development",
  "prod": "webpack --mode production",
  "watch": "webpack --mode development --watch"
}
}
</source>
Geben Sie nun im WebStorm-Terminal die Anweisung <code>npm run dev</code> (dev = development) ein.
Wenn Sie alles richtig gemacht haben, müsste webpack fehlerfrei durchlaufen und die Dateien
<code>web/index.html</code>, <code>web/js/main.js</code> sowie <code>web/css/head.css</code> erstellen. 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>).
Wenn die Dateiname nicht grün, sondern braun eingefärbt sind, müssen Sie sie (spätestens beim Commit) unter Git-Kontrolle stellen.
Wenn Sie jetzt die Datei <code>index.html</code> 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.
Allerdings ist das CSS-Layout immer noch mangelhaft. Die Datei <code>main.css</code>
wird bislang noch nicht eingebunden. Das können Sie ganz einfach ändern.
Fügen Sie in die Datei <code>src/js/main.js</code> folgenden Import-Befehl am Anfang der Datei ein:
<source lang="javascript"</code>
import '../css/main.css';
</source>
</source>


Dieser Befehl ist in JavaScript eigentlich nicht erlaubt. Wenn Sie jetzt versuchen, die Datei
===Google-Pagespeed-Test===
<code>src/index.html</code> im Browser zu öffnen, werden Sie in der Browser-Konsole
eine Fehlermeldung erhalten.
 
webpack unterstützt allerdings das Importieren von CSS-Dateien in JavaScript-Dateien.
Das Tool wurde so konfiguriert, dass die
Datei so eingebunden wird, dass sie dynamisch in das HTML-Dokument injiziert wird
(siehe oben).
 
Geben Sie im WebStorm-Terminal noch einmal die Anweisung <code>npm run dev</code> ein
und öffnen Sie die Datei <code>web/index.html</code> im Browser (nicht die Datei <code>src/index.html</code>!). Jetzt sollte sie wieder sauber formatiert sein
(evtl. erst nach einem Reload im Browser)!


Sehen Sie sich mal den Inhalt der Datei <code>main.js</code> an. Darin finden Sie
Wenn man die Anwendung noch einmal mit Google testet, erhält man eine neue Empfehlung
– neben vielen Kommentaren, die zur Strukturierung der Datei verwendet werden – die Inhalte der Dateien <code>main.js</code>, <code>greet.js</code> und <code>main.css</code>
sowie spezielle Anweisungen, die von webpack eingefügt wurden.


In dieser Form ist die Datei für Entwicklungszwecke einigermaßen brauchbar. Bei einem Fehler
[https://pagespeed.web.dev/analysis/https-glossar-hs-augsburg-de-beispiel-tutorium-2024-wk_hello_world-wk_hello_world_04-web_v01-index2-html/0r6b3j26g6?form_factor=mobile PageSpeed Insights (für Musterlösung index2.html)]:
zeigt einem der Browser, in welcher Zeile dieser Datei der Fehler aufgetreten ist.  
<blockquote cite="https://pagespeed.web.dev/analysis/https-glossar-hs-augsburg-de-beispiel-tutorium-2024-wk_hello_world-wk_hello_world_04-web_v01-index2-html/0r6b3j26g6?form_factor=mobile">
Normalerweise handelt es sich um eine Zeile, die Sie erfasst haben. webpack-Befehle sollten keine
Halten Sie die Anfrageanzahl niedrig und die Übertragungsgröße gering 5 Anfragen • 5 KiB.
Fehler werfen. Nachteilig ist, dass diese Zeile etwas anders aussehen kann, wie in Ihrem Sourcecode,
</blockquote>
da sie eventuell transformiert wurde. Aber man findet die Originalzeile normalerweise recht schnell.


Gehen Sie nochmal ins WebStorm-Terminal und geben Sie diesmal
''Anmerkung: Die aktuelle Version von PageSpeed Insights zeigt diese 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''
die Anweisung <code>npm run prod</code> (prod = production) ein.
Es werden wieder die drei Dateien erstellt. Und die Web-Anwendung sollte
immer noch funktionieren. Wenn Sie sich die gebündelte JavaScript-Datei <code>web/js/main.js</code> noch einmal ansehen,
werden Sie feststellen, dass sie nun viel kleiner ist. Sie enthält keine überflüssige Leerzeichen, keine Zeilenumbrüche
und auch keine Kommentare mehr (evtl. 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.
Das heißt, wir sollen die Dateien komprimieren: Überflüssige Kommentare, Leerzeichen und Zeilenumbrüche
Dieser startet einen webpack-Watcher, der bei jeder Änderung an einer Datei, die von webpack
löschen, lange Variablen-, Konstanten- und Funktionsnamen verkürzen etc. Außerdem sollten wir die beiden Dateien
beim Bündeln berücksichtigt wird, dafür sorgt, dass die Dateien im Web-Ordner neu erstellt werden
<code>main.js</code> und <code>greet.js</code> wieder zu einer Datei zusammenfügen.  
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 <code>src/css/head.css</code>  
('''nicht in der Datei <code>web/css/head.css</code>''') die Hintergrundfarbe, speichern Sie die Datei und
laden Sie danach die HTML-Datei im Browser mittels des Reload-Buttons neu.  


Vergessen Sie nicht:
Da dies gegen das Prinzip der Modularisierung spricht, wird dies nicht im Sourcecode, sondern automatisch mit Hilfe eines geeigneten Tools ([[Vite]], [[webpack]], ...) realisiert (siehe [[HTML5-Tutorium: JavaScript: Hello World 05|Tutorium: Teil 5]]). Das heißt, der Sourcecode wird weiterhin modular aufgebaut. Anschließend wird der Sourcecode mit Hilfe eines [[Transpiler]]s (Source-to-Source-Compiler) in eine kompakte Darstellung transformiert. Die Anzahl der Dateien, die vom Browser geladen werden müssen, wird drastisch reduziert, überflüssige Leerzeichen und Kommentare werden entfernt, Variablennamen werden durch kurze Namen ersetzt etc.  
* Klick auf das Commit-Icon und anschließend <code>git push</code>.


===webpack improved===
Ein Transpiler kann auch die Programmiersprache ändern. Zum Beispiel kann ECMAScript 6 in ECMAScript 5 übersetzt werden. Oder man verwendet eine Sprache wie TypeScript, CoffeeScript etc. oder sogar Java und übersetzt diese in ECMAScript, damit der Browser den Code interpretieren kann.


Bislang wird im Produktivmodus nur die JavaScript-Datei komprimiert. Schöner wäre es, wenn auch die CSS-Datei und die HTML-Datei komprimiert werden würden.
===Weitere Modularisierung===
Und wenn man die Google-Vorgaben vollständig umsetzen möchte, sollte außerdem der komprimierte Inhalt der Datei <code>head.css</code> direkt in die
Datei <code>index.html</code> eingefügt werden. All das kann man mit webpack erreichen.


Installieren Sie dazu folgende Node.js-Pakete:
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.


<source lang="bash">
Musterlösung:<br/>
# Die folgenden Pakete wurden schon installiert.  
v02: [https://glossar.hs-augsburg.de/beispiel/tutorium/2024/wk_hello_world/wk_hello_world_04/web_v02/index.html <code>index.html</code>]<br/>
# Das brauchen Sie nicht zu wiederholen.
[{{Git-Server}}/kowa/wk_hello_world_04/-/tree/v02 Git-Repository v02]
# Es macht aber auch nichts, wenn Sie sie noch einmal installieren.
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


# Die folgenden Pakete werden zusätzlich benötigt:
===Konfigurierbarkeit===
npm install --save-dev cssnano postcss-loader
npm install --save-dev posthtml-loader posthtml-inline-assets
npm install --save-dev html-webpack-plugin terser-webpack-plugin
npm install --save-dev
</source>


Nun müssen Sie „nur noch“ den Inhalt der Datei <code>webpack.config.js</code> ersetzen.
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.  
(In dieser Datei steckt stundenlange Arbeit, da die webpack-Dokumentation häufig nicht so prickelnd ist
und es Dutzende von Paketen gibt, die Ähnliches leisten, aber oft die ein oder andere Schwäche haben.
Ich behaupte übrigens nicht, dass dies die perfekte Konfigurationsdatei ist, um die gewünschten Optimierungen zu erzielen.
Aber sie funktioniert! Zumindest solange webpack5 nicht veröffentlicht wurde. )


<source lang="javascript">
Musterlösung:<br/>
const
v03: [https://glossar.hs-augsburg.de/beispiel/tutorium/2024/wk_hello_world/wk_hello_world_04/web_v03/index.html <code>index.html</code>]<br/>
  path                      = require('path'),
[{{Git-Server}}/kowa/wk_hello_world_04/-/tree/v03 Git-Repository v03]
  TerserPlugin              = require('terser-webpack-plugin'),
  SuppressChunksPlugin      = require('suppress-chunks-webpack-plugin').default;


function absolutePath(l_path)
==Fortsetzung des Tutoriums==
{ return path.resolve(__dirname, l_path); }


module.exports = function(p_env, p_argv)
Wenn man die Vorschläge von Google beachtet,  
{ let isProd = p_argv.mode === 'production';
beschleunigt dies die Übertragung der Dateien vom Server zum Client, macht aber  den Code unlesbar.
  // isProd === true iff webpack is run in production mode
Die Zusammenführung zweier Dateien widerspricht dem Prinzip der Modularisierung. Das Entfernen von Kommentaren,
 
Leerzeichen und Leerzeilen ist aus Entwickler-Sicht vollkommen kontraproduktiv.
  return {
  // store the current mode ('production', 'development')
  // in the webpack object so that webpack plugins can access it
  mode:    p_argv.mode,
 
  // https://webpack.js.org/configuration/devtool/
  // 'eval-source-map' than 'inline-source-map' would be better.
  // However, using this devtool forces a developer using WebStorm
  // to authorize an error url created by webpack within WebStorm
  // before it can be viewed within the browser.
  devtool: isProd ? false : 'inline-source-map',
 
  entry:
  { // use the following files to create
    // web/index.html, web/css/head.css, web/js/main.js
    html: absolutePath('src/index.html'),
    head: absolutePath('src/css/head.css'),
    main: absolutePath('src/js/main.js'),
  },
 
  output:
  { // placesJavaScript files in dictionary web/js
    filename: 'js/[name].js',
    path: absolutePath('web'),
  },
 
  plugins:
  [ // delete html.js, head.js from output dictionary web/js
    // (For every member of the entry list, a JavaScript file
    // is created. index.html and head.css are extracted from
    // those files. So html.js and head.js are not needed
    // any longer.)
    new SuppressChunksPlugin(['html', 'head'])
  ],
 
  optimization:
  { minimizer:
    [ // minimizes ES6 JavaScript files
      // remove all JS comments but those that are marked to be preserved
      new TerserPlugin({ terserOptions: {output: {comments: /@preserve/}}})
    ]
  },
 
  module:
  { rules:
    [ { test:    /head\.css$/,
        use:    [ // store the extracted CSS content in a file
                  { loader: 'file-loader',
                    options: { name: '[name].[ext]',
                                outputPath: 'css/'
                              }
                  },
                  // extract the CSS content from the file loaded
                  'extract-loader',
                  // load the CSS file (using the optimizer defined below)
                  { loader: 'css-loader', options: { importLoaders: 1 } },
                  // optimize the CSS file
                  { loader: 'postcss-loader',
                    options: { sourceMap: 'inline',
                                plugins:
                                  () =>
                                  [ require('cssnano')
                                    ({ preset:
                                      [ 'default',
                                        { discardComments:
                                          // remove all CSS comments but those
                                          // that are marked tpo be preserved
                                          { remove: c => !c.match(/@preserve/) }
                                        }
                                        ],
                                    })
                                  ]
                              }
                  }
                ]
      },
 
      { test:    /main\.css$/,
        exclude: [/node_modules/, /web/],
        use:    [ // enable to import CSS files into a JavaScript file
                  'style-loader',
                  // load the CSS file (using the optimizer defined below)
                  { loader: 'css-loader', options: { importLoaders: 1 } },
                  // optimize the CSS file
                  { loader: 'postcss-loader',
                    options: { sourceMap: 'inline',
                                plugins:  [ require('cssnano') ]
                                          // all CSS comments are removed
                              }
                  }
                ]
      },
 
      { test:  /\.html$/,
        use:    [ // store the extracted HTML content in a file
                  { loader: 'file-loader',
                    options: { name: '[name].[ext]',
                              outputPath: '/'
                            }
                  },
                  // extract the HTML content from the file loaded
                  'extract-loader',
                  // load the HTML file; if on production mode minimize it
                  { loader: 'html-loader',
                    options: { minimize:                    isProd,
                              removeComments:              true,
                              ignoreCustomComments:        [/@preserve/],
                              //https://gist.github.com/stalniy/d55fc7990b4340c9d0ef55fd8838ed34
                              collapseWhitespace:          true,
                              conservativeCollapse:        false,
                              collapseInlineTagWhitespace: false,
                              removeAttributeQuotes:      false,
                              caseSensitive:              true,
                            }
                  },
                  { loader: 'posthtml-loader',
                    options:
                    { plugins:
                      [ require('posthtml-inline-assets')
                              ({ from: 'web/css', // 'web/web', 'web/abc' works as well ...
                                                  // this doesn't make any sense ...
                                  inline: { script: false }
                              })
                      ],
                    }
                  }
                ]
      },
    ]
  }
}};
</source>


Testen Sie den Übersetzungsmechanismus. Führen Sie <code>npm run dev</code> aus und sehen
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 sich die erzeugten Dateien an (insbesondere den Head-Bereich der Datei <code>web/index.html</code>).
Führen Sie danach <code>npm run prod</code> aus und sehen Sie sich die Dateien noch einmal an.


Führen Sie die erzeugte Datei <code>web/index.html</code> jedes Mal auch im Browser aus und
Sie sollten nun [[HTML5-Tutorium: JavaScript: Hello World 05|Teil 5 des Tutoriums]] bearbeiten.
überprüfen Sie, ob sie noch funktioniert.
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.


Übrigens: webpack wurde so konfiguriert, dass HTML-, CSS- und JavaScript-Kommentare, in denen das Schlüsselwort <code>@preserve</code> enthalten ist,
auch in den komprimierten Code eingefügt werden. Das ist wichtig, wenn man Lizenzinformationen in diese Dateien einfügen möchte.
==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 26. April 2024, 12:51 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: index0.html (HTML validate, CSS validate, Google PageSpeed)
v01: index1.html (HTML validate, CSS validate, Google PageSpeed)
v01: index2.html (HTML validate, CSS validate, Google PageSpeed)
v02: index.html (HTML validate, CSS validate, Google PageSpeed)
v03: 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).

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 mittels run 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.

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 die JavaScript-Datei main.js 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 (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 index0.html):

Empfehlung

Ressourcen blockieren den First Paint Ihrer Seite. Versuchen Sie, wichtiges JS und wichtige CSS inline anzugeben und alle nicht kritischen JS und Stile aufzuschieben. Weitere Informationen.

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

Gehen Sie so vor, wie es Google früher 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-Regelsätze 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="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 (a hack!), i.e. parallel to the body content. 
     preload is not supported by some browsers:
       https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preload
     The noscript section is for browsers that do not support JavaScript at all.
-->
<link rel="preload" href="css/body.css" as="style" onload="this.rel='stylesheet'">
<link rel="stylesheet" href="css/body.css" media="print" onload="this.media='all'">

Die Datei body.css wird für den Medientyp none geladen. Diesen Medientyp gibt es aber gar nicht. Es gibt die Typen screen, print, speech, all sowie ein paar weitere veraltete Typen. Da der Medientyp none vorerst nicht gebraucht wird, laden ihn moderne Browser asynchron. Sobald das HTML-Dokument vollständig geladen wurde, feuert das Ereignis onload. Sobald dies passiert, wird der Medientyp none durch all ersetzt. Daran erkennt der Browser, dass dies eine CSS-Datei ist, die durchaus zum Layouten der aktuellen Seite verwendet werden soll. Und das macht der Browser dann auch.

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.
  • Dies Datei wird mittels eines 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!

Modularisierung der JavaScript-Dateien

Die JavaScript-Datei main.js aus dem dritten Teil 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 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 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 die Zeichenfolge greet. einfügen.

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

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('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('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 init()
{ document.getElementById('button_submit')
          .addEventListener('click', greet.sayHello);
  window.addEventListener('keydown', greet.sayHelloOnEnter);

  window.addEventListener('focus', autofocus);
  document.getElementById('button_reset').addEventListener('click', autofocus);

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

Google-Pagespeed-Test

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

PageSpeed Insights (für Musterlösung index2.html):

Halten Sie die Anfrageanzahl niedrig und die Übertragungsgröße gering 5 Anfragen • 5 KiB.

Anmerkung: Die aktuelle Version von PageSpeed Insights zeigt diese 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 sollen die Dateien komprimieren: Überflüssige Kommentare, Leerzeichen und Zeilenumbrüche löschen, lange Variablen-, Konstanten- und Funktionsnamen verkürzen etc. Außerdem sollten wir die beiden Dateien main.js und greet.js wieder zu einer Datei zusammenfügen.

Da dies gegen das Prinzip der Modularisierung spricht, wird dies nicht im Sourcecode, sondern automatisch mit Hilfe eines geeigneten Tools (Vite, webpack, ...) realisiert (siehe Tutorium: Teil 5). Das heißt, der Sourcecode wird weiterhin modular aufgebaut. Anschließend wird der Sourcecode mit Hilfe eines Transpilers (Source-to-Source-Compiler) in eine kompakte Darstellung transformiert. Die Anzahl der Dateien, die vom Browser geladen werden müssen, wird drastisch reduziert, überflüssige Leerzeichen und Kommentare werden entfernt, Variablennamen werden durch kurze Namen ersetzt etc.

Ein Transpiler kann auch die Programmiersprache ändern. Zum Beispiel kann ECMAScript 6 in ECMAScript 5 übersetzt werden. Oder man verwendet eine Sprache wie TypeScript, CoffeeScript etc. oder sogar Java und übersetzt diese in ECMAScript, damit der Browser den Code interpretieren kann.

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.

Musterlösung:
v02: index.html
Git-Repository v02

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-Repository v03

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