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

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 1: Zeile 1:
{{HTML5-Tutorium:JavaScript:HelloWorld:Menü}}
{{HTML5-Tutorium:JavaScript:HelloWorld:Menü}}


'''Musterlösung''': [https://glossar.hs-augsburg.de/beispiel/tutorium/2021/wk_hello_world/wk_hello_world_05/web/index.html <code>index.html</code>]<br/>
'''Musterlösung''': [https://glossar.hs-augsburg.de/beispiel/tutorium/2023/wk_hello_world/wk_hello_world_05/web/index.html <code>index.html</code>]<br/>
[https://glossar.hs-augsburg.de/beispiel/tutorium/2021/wk_hello_world/wk_hello_world_05/web_v03a/index.html <code>index.html (v03a)</code>],
[https://glossar.hs-augsburg.de/beispiel/tutorium/2023/wk_hello_world/wk_hello_world_05/web_v03a/index.html <code>index.html (v03a)</code>],
[https://glossar.hs-augsburg.de/beispiel/tutorium/2021/wk_hello_world/wk_hello_world_05/web_v03b/index.html <code>index.html (v03b)</code>],
[https://glossar.hs-augsburg.de/beispiel/tutorium/2023/wk_hello_world/wk_hello_world_05/web_v03b/index.html <code>index.html (v03b)</code>],
[https://glossar.hs-augsburg.de/beispiel/tutorium/2021/wk_hello_world/wk_hello_world_05/web_v04a/index.html <code>index.html (v04a)</code>],
[https://glossar.hs-augsburg.de/beispiel/tutorium/2023/wk_hello_world/wk_hello_world_05/web_v04a/index.html <code>index.html (v04a)</code>],
[https://glossar.hs-augsburg.de/beispiel/tutorium/2021/wk_hello_world/wk_hello_world_05/web_v04b/index.html <code>index.html (v04b)</code>]<br/>
[https://glossar.hs-augsburg.de/beispiel/tutorium/2023/wk_hello_world/wk_hello_world_05/web_v04b/index.html <code>index.html (v04b)</code>]<br/>
(Git-Repository: [https://gitlab.multimedia.hs-augsburg.de/kowa/wk_hello_world_05.git wk_hello_world_05], [https://gitlab.multimedia.hs-augsburg.de/kowa/wk_hello_world_05/-/tree/v00 Version 00], [https://gitlab.multimedia.hs-augsburg.de/kowa/wk_hello_world_05/-/tree/v01 Version 01], [https://gitlab.multimedia.hs-augsburg.de/kowa/wk_hello_world_05/-/tree/v02 Version 02], [https://gitlab.multimedia.hs-augsburg.de/kowa/wk_hello_world_05/-/tree/v03 Version 03], [https://gitlab.multimedia.hs-augsburg.de/kowa/wk_hello_world_05/-/tree/v04 Version 04])
(Git-Repository: [https://gitlab.multimedia.hs-augsburg.de/kowa/wk_hello_world_05.git wk_hello_world_05], [https://gitlab.multimedia.hs-augsburg.de/kowa/wk_hello_world_05/-/tree/v00 Version 00], [https://gitlab.multimedia.hs-augsburg.de/kowa/wk_hello_world_05/-/tree/v01 Version 01], [https://gitlab.multimedia.hs-augsburg.de/kowa/wk_hello_world_05/-/tree/v02 Version 02], [https://gitlab.multimedia.hs-augsburg.de/kowa/wk_hello_world_05/-/tree/v03 Version 03], [https://gitlab.multimedia.hs-augsburg.de/kowa/wk_hello_world_05/-/tree/v04 Version 04])



Version vom 7. März 2023, 16:01 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

Musterlösung: index.html
index.html (v03a), index.html (v03b), index.html (v04a), index.html (v04b)
(Git-Repository: wk_hello_world_05, Version 00, Version 01, Version 02, Version 03, Version 04)

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 mittels Vite[1] automatisch für den Server-Betrieb zu optimieren, sodass möglichst wenige, möglichst kleine Dateien zum Client übertragen werden.

Im Tutorium wird „Vite 4“ verwendet.

JavaScript Module Bundlers

Um Module einer Web-Anwendung für die Auslieferung zum Client zu komprimieren, zu bündeln und anderweitig zu optimieren, gibt es zahlreiche Werkzeuge. Für Node.js gibt es die sogenannten “JavaScript Module Bundlers”, wie z. B. Vite[1], Webpack[2] oder Browserify[3].

Üblicherweise optimieren die Bundlers nicht nur JavaScript-Dateien, sondern auch HTML-, CSS-, JSON- und andere Dateien.

Erstellen eines neuen Vite-Projekts (Version 00)

Erstellen Sie ein neues Vite-Projekt hello_world_05:

cd /webprog  # Ordner, in dem Ihre Projekte liegen
npm create vite@latest

Geben Sie für Vite folgende Optionen an:

Project name: hello_world_05
Select a framework: Vanilla
Select a variant: JavaScript

Öffnen Sie das Projekt in VSC und öffnen Sie in VSC ein zugehöriges Terminal (für den neuen Projektordner). Führen Sie folgende Befehle aus:

npm i
npm run dev

Nun wird ein Server gestartet und eine URL angezeigt, die Sie in einem Browser öffnen können. Make it so!

Stellen Sie anschließend das Projekt unter Git-Kontrolle und speichern Sie es wieder in Gitlab.

Zur Erinnerung: .gitignore

Achtung: Bevor Sie einen Commit durchführen, müssen Sie im selben Ordner, in dem sich die Datei package.json befindet, eine Datei namens .gitignore anlegen, in dem eine Zeile mit dem Wort node_modules enthalten ist. (Der Punkt zu Beginn des Dateinamens darf nicht fehlen!)

Glücklicherweise hat dies das Vite-Setup für Sie übernommen.

Diese Datei ist extrem wichtig. Wie Sie bereits im Node.js-Tutorium gesehen haben, enthält der Ordner node_modules sehr schnell sehr viele Dateien (mehrere Zehntausend). Wenn Sie diese im Repository speichern, bläht sich dieses extrem stark auf. Die Git-Push- und -Pull-Befehle werden dadurch sehr langsam.

Die Datei .gitignore dient dazu, dies zu verhindern. In jeder Zeile steht ein Dateiname oder ein Dateipattern (wie zum Beispiel *.log, das alle Dateien mit der Dateiextension log beschreibt), um zu verhindern, dass die zugehörigen Dateien ins Git-Repository eingefügt werden.

Wenn Sie dafür sorgen, die Dateien package.json und package-lock.json ins Repository eingefügt werden, kann ein anderer Benutzer, der Ihr Git-Repository auf seinen Rechner lädt, die fehlenden Node.js-Module jederzeit ganz einfach restaurieren:

npm i

Konfigurieren des Vite-Projekts Version 01

Das neu angelegte Projekt enthält keine Vite-Konfigurationsdatei. Defaultmäßig werden alle Dateien des Web-Projekts im Root-Ordner hello_world_05 abgelegt. Das ist – wie Sie bereits wissen – schlecht, da dort auch andere Dateien liegen, wie z. B. package.json oder node_modules.

Dies können Sie ändern, indem Sie eine Konfigurationsdatei vite.config.js im Projektordner anlegen:

// https://vitejs.dev/config/
export default
{ root:      'src', 
  publicDir: '../public',  
  base:      '',
  build: 
  { outDir:  '../web',
    minify:  true
  }  
}

Diese legt Folgendes fest:

  • Der Root-Ordner des Projekts befindet sich im Unterordner src des Projektordners. Dort hinein kommen die ganzen HTML-/CSS-/JS-Dateien und Medien, die unter Kontrolle von Vite stehen sollen.
  • Der Ordner public, in dem alle Dateien enthalten sind, die von Vite nicht verändert werden, befindet sich weiterhin im Unterordner public des Projektordners. Vom Projektordner src aus gesehen, leigt er außerhalb von src eine Ebene höher. Das heißt, er befindet sich in der Ordnerhierarchie auf derselben Ebene wie src selbst.
  • Die Option base enthält einen String, der vor jeden Asset-Pfad (.css, .js, .jpg ...) eingefügt wird. Standardmäßig lautet der Wert '/'. Das heißt, die zugehörigen Assetpfade sind absolut. Sie beginnen stets beim Top-Level-Verzeichnis der Web-App. Mit der Option base: legt man fest, dass die Pfade relativ sind. Das hat den Vorteil, dass man die Web-App in einen Unterordner einer anderen Web-App legen kann.
  • Wenn Sie das Projekt mittels npm run build für den Produktivbetrieb erstellen lassen, wird das Ergebnis in den Ordner web geschrieben. Dieser Ordner liegt ebenfalls auf derselben Ebene wie src.
  • Durch npm run build wird komprimierter (minimized) Code erzeugt. Das gilt allerdings nicht für HTML-Dateien, sondern nur für CSS-, JS-/TS- und SVG-Dateien.

Sie sollten in der Datei package.json im Skript build auch noch die Option --emptyOutDir ergänzen, damit der Web-Ordner vor jedem Rebuild geleert wird. Ohne diese Option sammen sich im Web-Ordner im Laufe der Zeit Dateileichen an.

"build": "vite build --emptyOutDir",

Restrukturierung des Projekts

Wenn man nun das Projekt mittels npm run dev startet, wird man nichts sehen. Der Grund ist, dass der Browser keine Datei index.html findet. Der Grund ist, dass der Vite-Server nun den Inhalt des Ordners src ausliefert. Diesen gibt es aber noch gar nicht.

Die Ordnerstruktur des Projekts muss an die Konfigurationsdatei angepasst werden.

Erstellen Sie folgende Ordnerstruktur:

src
| css
| img
| js

Verschieben Sie anschließend die Web-Dateien in diese Ordner:

src
| css
  - style.css
| img
  - javascript.svg
| js
  - counter.js
  - main.js
- index.html

Das Ergebnis ist, dass Sie Fehlermeldungen erhalten, dass bestimmte Dateien nicht gefunden werden. Das liegt daran, dass sich die Ordnerstruktur aus Sicht der Dateien im src-Ordner geändert haben. Die Pfade innerhalb der Dateien in diesem Ordner müssen daher angepasst werden.

Die Pfade zu Dateien im public-Ordner bleiben gleich. Aus Sicht der Dateien im src-Ordner befinden sie sich in der Root des Web-Auftritts. Im public-Ordner liegt in diesem Beispiel nur die Datei vite.svg. Deren Pfade änder sic nicht, daher bleibt in der Datei code>src/index.html und src/js/main.js die Zeilen

<link rel="icon" type="image/svg+xml" href="/vite.svg" />

und

<img src="/vite.svg" class="logo" alt="Vite logo" />

unverändert. Die anderen Pfade müssen dagegen eventuell angepasst werden:

// src/index.html
<script type="module" src="/main.js"></script>

// src/js/main.js
import './style.css'
import javascriptLogo from './javascript.svg'
import { setupCounter } from './counter.js'

Nehmen Sie geeignete Änderungen vor (sofern sie von VSC nicht automatisch durchgeführt wurden). Zum Schluss sollte die Anwendung wieder laufen.

Modifizieren und Testen Sie die App solange mit Hilfe von npm run dev solange, bis sie fehlerfrei läuft.

Packen der Web-Anwendung (Build, Version 02)

Bislang zeigt sich der Vorteil von Vite noch nicht wirklich. Der einzige Vorteil ist, dass CSS-Dateien auch in JavaScript-Dateien importiert werden können. Das ist im HTML-Standard nicht vorgesehen.

Seine Stärke spielt Vite aus, wenn wir das Projekt packen. Das bedeutet, dass die Anzahl der Dateien reduziert wird und überflüssige Leerzeichen, Zeilenumbrüche, Kommentare etc. entfernt werden. Dadurch reduziert sich die Anzahl der Bytes, die zu einem Client (Brwoser) übertragen werden müssen, erheblich. Insbesondere für Smartphones mit eine geringen Datenvolumen profitieren davon.

Intelligente Frameworks wie Vue, Svelte oder React oder React gehen sogar noch einen Schritt weiter und laden nur die Teile einer Web-Seite (nicht Web-Site!) nach, die sich geändert haben. JavaScript-Biliotheken werden so definiert, dass sie für eine ganze Web-Site nur einmal geladen werden müssen und für verschiedene Seiten wiederverwendet werden können. Hier spielen Web-Frameworks ihre Stärken aus. Sie stützen sich dabei auf Bundler wie Vite oder Webpack.

Führen Sie folgenden Befehl aus:

npm run build

Durch diesen Befehl wird die Web-App gepackt und im Ordner web abgelegt. Öffnen Sie die Datei web/index.html mit Hilfe des Live-Server-Plugins und sehen Sie sich das Ergebnis an. Schauen Sie sich auch die generierten Dateien im Ordner web an.

Testen Sie die Web-App dann, indem Sie entweder web/index.html mit dem Live Server von VSC starten oder indem Sie npm run preview eingeben.

Hello-World-App (Version 03)

Ersetzen Sie nun die Vite-Default-App durch die Hello-World-App aus dem 4. Teil des Tutoriums.

Wenn Sie die Produktivversion mittels npm run build erstellen möchten, muss es eine Datei index.html geben. Defaultmäßig wird nur diese in den Ordner web übernommen. Das heißt, wenn man beispielsweise die Musterlösung von von WK_HelloWorld04 in den (zuvor geleerten) Ordner src kopiert, funktioniert diese Anwendung zwar im Developermoduls (npm run dev), aber nicht im Produktivmodus. Wenn man (für dieses Beispiel) npm run build ausführt, erhält man die Fehlermeldung

Could not resolve entry module "src/index.html".

Die Lösung ist in diesem Fall ganz einfach. Eine der drei Dateien index0.html, index1.html oder index2.html wird in index.html umbenannt. Im Ordner web wird dann durch npm run build auch nur eine HTML-Datei namens index.html erzeugt. Das ist für unsere Zwecke in Ordnung, da wir mit Hilfe von Vue eine Single-Page-Anwendung erstellen werden. Die Inhalte einer derartigen Anwendung werden per JavaScript nachgeladen. Man benötigt also tatsächlich nur eine HTML-Datei.

Anmerkung
Sie können auch eine Anwendung mit diversen HTML-Dateien mit Hilfe von Vite bündeln und komprimieren. Dafür müssen Sie allerdings eine so genannte Rollup-Konfiguration in die Datei vite.config.js einfügen.

Verbesserungen (Version 04)

body.css dynamisch laden

Zur Erinnerung:

  • 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. Das kann automatisch oder händisch erfolgen.
  • Die restlichen CSS-Anweisungen werden in die Datei body.css eingetragen.
  • Dies Datei sollte asynchron, d. h. parallel zum Body-Element geladen werden.

Der Hack, der im Teil 4 zu asynchronen Laden von body.css angewendet wurde, funktioniert leider nicht, da Vite das Element folgendermaßen transformiert.

  <link rel="stylesheet" href="data:text/css;base64,LyoNCiAqIEBhdXRob3IgICAgV29s...=="
        media="none" onload="this.media='all'" 
  />

Das heißt, der Inhalt der Datei body.css wird Base64-enkodiert und direkt in das Link-Element eingefügt. Der ganze Inhalt der CSS-Datei ist jetzt Bestandteil der Datei index.html und wird vor den Body-Inhalten geladen. Ziel des Hack war es ja gerade, dies zu vermeiden.

In Vite gibt es jedoch eine andere Möglichkeit, CSS-Dateien dynamisch zu laden. Man kann sie mittels JavaScript laden (vgl. [main.js der Vite-App]). Schreiben Sie die Hello-World-App entsprechend um.

HTML komprimieren

Wenn man sich die Dateien im Web-Ordner ansieht, bemerkt man, dass nur die JavaScript- und CSS-Dateien gebündelt und verkleinert werden. Die HTML wurde dagegen nicht komprimiert. Um dies zu erreichen, benötigt man ein Vite-Plugin. Das zugehörige NPM-Package wird zunächst installiert.

npm i -D vite-plugin-html

Anschließend wird das Plugin in vite.config.js konfiguriert.

import { createHtmlPlugin } from "vite-plugin-html";

export default
{ root:      'src', 
  publicDir: '../public',  
  base:      '',
  build: 
  { outDir:  '../web',
    minify:  true,
  },
  plugins: 
  [ createHtmlPlugin
    ({minify: true,
    })
  ]
}

Der HTML-Minimizer komprimiert auch CSS-Anweisungen, die im HTML-Code enthalten sind. Daher ist es sinnvoll, die CSS-Anweisungen aus der Datei head.css direkt in die Datei index.html einzufügen (vgl. index2.html der Version v03).

Ein Problem besteht noch. Die Lizenzkommentare werden in die komprimierten CSS- und JavaScript-Dateien eingefügt. Das kann gewünscht sein, kostet aber Platz. Das Problem kann man zumindest für JavaScript-Dateien beheben, indem man den Minimizer terser verwendet. Dieser bietet zahlreiche Konfigurationsmöglichkeiten an (ist allerdings beim Komprimieren wesentlich langsamer als der Standard-Minimizer).

import { createHtmlPlugin } from "vite-plugin-html";

export default
{ root:      'src', 
  publicDir: '../public',  
  base:      '',
  build: 
  { outDir:  '../web',
    minify:  'terser',
    terserOptions: 
    ({output: 
      { comments: false,
      }
    }),
  },
  plugins: 
  [ createHtmlPlugin
    ({minify: true,
    }),
  ]
}

Für den CSS-Minimizer habe ich bislang keine entsprechenden Optionen gefunden. Schade.

Webpack

Allerdings ist das CSS-Layout immer noch mangelhaft. Die Datei main.css wird bislang noch nicht eingebunden. Das können Sie ganz einfach ändern. Fügen Sie in die Datei src/js/main.js folgenden Import-Befehl am Anfang der Datei ein:

import '../css/main.css';

Dieser Befehl ist in JavaScript eigentlich nicht erlaubt. Wenn Sie jetzt versuchen, die Datei src/index.html 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 CSS-Datei dynamisch in das HTML-Dokument „injiziert“ wird (siehe oben).

Geben Sie im Terminal noch einmal die Anweisung npm run dev ein und öffnen Sie die Datei web/index.html im Browser (nicht die Datei src/index.html!). Jetzt sollte sie wieder sauber formatiert sein (evtl. erst nach einem Reload im Browser)!

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

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

Gehen Sie nochmal ins Terminal und geben Sie diesmal die Anweisung npm run prod (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 web/js/main.js 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 der CSS-Datei main.css). 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-Aanwendung ist diese Variante allerdings vollkommen ungeeignet.

Nun können Sie noch die Anweisung npm run watch testen. Dieser startet einen Webpack-Watcher, der bei jeder Änderung an einer Datei, die von Webpack beim Bündeln berücksichtigt wird, dafür sorgt, dass die Dateien im Web-Ordner neu erstellt werden und Sie die Ergebnisse Ihrer Änderung durch einen einfachen (Auto-)Reload der Datei index.html im Browser betrachten können. Ändern Sie doch einmal in der CSS-Datei src/css/head.css (nicht in der Datei web/css/head.css) die Hintergrundfarbe, speichern Sie die Datei und laden Sie danach die HTML-Datei im Browser mittels des Reload-Buttons neu.

Verbesserungen der Webpack-Konfiguration

Hinzufügen verschiedener Minimizer

Musterlösung: index.html (5b) (Git-Repository: 5b)

Wenn man die Dateien betrachtet, wurde nur die Datei main.js im Produktions-Modus minimiert. Künftig sollen alle Dateien im Produktionsmodus minimiert werden.

Dafür brauchen wir drei neue Node.js-Pakete, zum Minimieren von CSS-, HTML- und JavaScript-Dateien:

npm i -D css-minimizer-webpack-plugin 
npm i -D html-minimizer-webpack-plugin 
npm i -D terser-webpack-plugin

Nun muss man diese drei Plugins noch in die Webpack-Konfigurationsdatei webpack.config.js einarbeiten.

Zunächst muss die neuen Pakete importieren. Dazu ersetzt fügt man am Anfang der Datei drei weitere Require-Anweisungen ein.

const
  path       = require('path'),
  CopyPlugin = require('copy-webpack-plugin');

wird zu

const
  path                = require('path'),
  CopyPlugin          = require('copy-webpack-plugin'),
  TerserPlugin        = require('terser-webpack-plugin'),
  CssMinimizerPlugin  = require('css-minimizer-webpack-plugin'),
  HtmlMinimizerPlugin = require('html-minimizer-webpack-plugin');

Außerdem muss das Konfigurations-Objekt, das exportiert wird, künftig mit einer Funktion berechnet werden, damit man ermitteln kann, ob der Benutzer Webpack im Development- oder im Produktionsmodus ausführt.

module.exports =
{ entry:
  ...
};

wird zu

module.exports =
function(p_env, p_argv)
{ const isProd = p_argv.mode === 'production';

  return {

  // the mode of the current webpack run: 'development' or 'production'
  mode: p_argv.mode || 'development',

  entry:
  ...
}};

Innerhalb dieser Funktion steht einem nun die Konstante isProd zur Verfügung. Sie hat den Wert false, wenn Webpack im Development-Modus ausgeführt wird, und true, wenn Webpack im Produktionsmodus läuft.

Zu guter Letzt muss man noch ein paar Optimierungs-Regeln im Anschluss an das Objekt module in die Konfigurationsdatei einfügen.

module.exports =
{ module:
  { ...
  },
};

wird zu

{ module:
  { ...
  },

  optimization:
  { minimize: isProd,

    minimizer:
    [ new TerserPlugin                             // minimize JS
          ({ extractComments: true, }),
      new CssMinimizerPlugin                       // minimize CSS
          (),
      new HtmlMinimizerPlugin                      // minimize HTML
          ({ minimizerOptions: 
             { collapseBooleanAttributes:   true,
               collapseWhitespace:          true,
               collapseInlineTagWhitespace: true,
             }
          }), 
    ],
  }, 
};

Das Attribut minimize wird im Development-Modus auf false gesetzt und im Produktions-Modus auf true. Das heißt, im Development-Modus werden keine Dateien minimiert. Im Produktionsmodus werden dagegen sowohl JS-, als auch CSS-, als auch HTML-Dateien minimiert. Die einzelnen Minimizer heißen unterschiedlich, stammen von unterschiedlichen Autoren und akzeptieren unterschiedliche Konfigurationsattribute. Dies ist ganz typisch für Webpack. Und in der nächsten Version funktioniert der ein oder andere Minimizer gar nicht mehr. Allerdings sind in der Zwischenzeit zumindest das Terser-Plugin und das CSS-Minimizer-Plugin offizieller Bestandteil von Webpack.

Wenn Sie jetzt npm run dev ausführen, sollte keine Datei im Web-Ordner komprimiert werden. Führen Sie dagegen npm run prod aus, sollten alle Dateien minimiert werden. Außerdem werden die Lizenzinformationen in eine eigene Datei ausgelagert.

Inlining von head.css

Musterlösung: index.html (5c) (Git-Repository: 5c)

Google PageSpeed Insights hatte angemahnt, den Inhalt der Datei head.css direkt in die Datei index.html einzufügen.

  • Google PageSpeed Insights Version 5b: Ressourcen beseitigen, die das Rendering blockieren: .../css/head.css
  • Google PageSpeed Insights Version 5c: Hier gibt es diese Empfehlung nicht mehr.

Um die Datei head.css in die Datei index.html einzufügen, muss man die HTML-Datei erzeugen. Ein einfaches Kopieren reicht nicht mehr aus. Anstelle des Copy-Plugins verwendet man das HTML-Webpack-Plugin. Dieses kann HTML-Dateien auf Basis von HTML-Templates erstellen und auch komprimieren. Da heißt, man benötigt auch das HTML-Minimizer-Plugin nicht mehr. Die CSS-Datei kann man mittels, des HTML-CSS-Inline-Plugins einfügen. Dafür braucht man allerdings das Mini-CSS-Extract-Plugin anstelle des Extract-Loaders und des File-Loaders. Insgesamt bedeutet dies einen größeren Umbau der Konfigurationsdatei. Zunächst sollten veraltete Node.js-Pakete entfernt und die dafür benötigten Pakete installiert werden:

npm uninstall -D file-loader extract-loader
npm uninstall -D copy-webpack-plugin html-minimizer-webpack-plugin

npm i -D html-webpack-plugin html-replace-webpack-plugin
npm i -D mini-css-extract-plugin html-inline-css-webpack-plugin

Das HTML-Replace-Plugin wird benötigt, um an der modifizierten zum Schluss mit Hilfe von regulären Ausdrücken noch zwei kleine Änderungen vorzunehmen. Damit werden Probleme behoben, die sich ergeben, weil es in den benutzten Paketen keine geeigneten Konfigurationsoptionen gibt. Hier sieht man deutlich das Problem von Webpack: Es gibt sehr viele Loader, Plugins und Optimierer. Allerdings hat jedes Paket seine individuellen Schwächen und oft arbeiten zwei Pakete auch nicht wie gewünscht zusammen. Hier hilft meist nur Try and Error. Ob man zum Schluss die beste Variante gewählt hat, ist dabei unklar.

Die Datei webpack.config.js wird folgendermaßen geändert:

const
  path                = require('path'),
  CopyPlugin          = require('copy-webpack-plugin'),
  TerserPlugin        = require('terser-webpack-plugin'),
  CssMinimizerPlugin  = require('css-minimizer-webpack-plugin'),
  HtmlMinimizerPlugin = require('html-minimizer-webpack-plugin');

wird zu

const
  path                       = require('path'),
  TerserPlugin               = require('terser-webpack-plugin'),
  CssMinimizerPlugin         = require('css-minimizer-webpack-plugin'),
  MiniCssExtractPlugin       = require('mini-css-extract-plugin'),
  HtmlWebpackPlugin          = require('html-webpack-plugin'),
  HTMLInlineCSSWebpackPlugin = require('html-inline-css-webpack-plugin').default,
  HtmlReplaceWebpackPlugin   = require('html-replace-webpack-plugin');

Das Plugins-Array muss vollständig ersetzt werden werden:

plugins:
[ ...
],

wird zu

plugins:
[ new MiniCssExtractPlugin
  ({ filename: "[name].css" }),
  new HtmlWebpackPlugin
  ({ template: absolutePath('src/index.html'),
     chunks:   ['head', 'main'],
     minify:
     { collapseWhitespace:   isProd,
       removeComments:       true,
       ignoreCustomComments: [/@preserve/],
     },
  }),
  new HTMLInlineCSSWebpackPlugin
  (),
  new HtmlReplaceWebpackPlugin
  ([{ pattern:     /<script .*head\.js"><\/script>/,
      replacement: '',
    },
    { pattern:     /defer/g,
      replacement: 'async',
    },
  ]),
],

Das HTML-Webpack-Plugin kopiert in die Template-Datei src/index.html zwei Script-Anweisungen, um die von Webpack erzeugten Dateien (“Chunks”, Klumpen) web/js/head.js und web/js/main.js zu Laden. Diese Anweisungen dürfen also in der Template-Datei nicht enthalten sein. Wie man sieht, kann man im HTML-Webpack-Plugin die Komprimierung mittels der Option minify direkt konfigurieren. Auch hier werden die Leerzeichen und -zeilen nur im Produktivmodus, aber nicht im Developmentmodus entfernt.

Mit Hilfe des HTML-Replace-Plugins wird der Eintrag <script "src=css/head.js"></script> wieder aus index.html gelöscht. Diese JavaScript-Datei enthält nur den CSS-Code, der mittels des HTML-Inline-CSS-Plugins direkt eingefügt wurde. Warum dieses Plugin das überflüssige Script-Tag nicht selbst entfernt, habe ich noch nicht herausgefunden.

Außerdem wird defer durch async ersetzt, da ich das asynchrone Laden gegenüber dem verzögerten Laden von JavaScript-Dateien bevorzuge. Warum ich das im HTML-Webpack-Plugin nicht konfigurieren kann, weiß ich auch nicht.

Und die Regel für die head.css im Array rules) muss ersetzt werden.

{ test:    /head\.css$/,
  include: [ /src/ ],
  use:     [ MiniCssExtractPlugin.loader,
             'css-loader',
           ],
},


Wenn Sie jetzt npm run dev ausführen, sollte keine Datei im Web-Ordner komprimiert werden. Führen Sie dagegen npm run prod aus, sollten alle Dateien minimiert werden. Außerdem werden die Lizenzinformationen in eine eigene Datei ausgelagert. In beiden Fällen sollte der Inhalt der CSS-Datei head.css in der HTML-Datei selbst enthalten sein. Einmal unkomprimiert und einmal komprimiert.

Ein kleine Verbesserung der Import-Anweisungen

Wenn Sie schon gerade dabei sind, fügen Sie noch folgendes Konfigurationsobjekt in die Export-Funktion von webpack.config.js ein:

resolve:
{ alias: { '/json': absolutePath('src/json/'),
           '/css':  absolutePath('src/css/'),
           '/img':  absolutePath('src/img/'),
         },
},

Damit erreichen Sie, dass Dateien, die sich in den Ordnern src/json/, src/css/, src/img/ befinden, direkt importiert werden können. Die Notwendigkeit, relative Pfadangaben wie import '../../../css/irgenwas.css'; zu verwenden, entfällt. Sie können künftig einfach import '/css/irgenwas.css'; schreiben. Das gilt auch für Bilder oder JSON-Dateien, die Sie (mittels des erweiterten Import-Befehls von Webpack) in eine JavaScript-Datei importieren. Sie können selbstverständlich beliebig viele weitere Alias-Befehle definieren, natürlich auch für JavaScript-Bibliotheken, die in anderen Ordnern enthalten sind.

Ersetzen Sie also in der Datei src/main.js, den Befehl import '../css/body.css'; durch import '/css/body.css';

Verwendung des SCSS-Minimizers

Musterlösung: index.html (5d) (Git-Repository: 5d)

Wenn man sich die Datei web/js/main.js ansieht, bemerkt man, dass die CS-Befehle, die von Webpack in diese Datei eingefügt wurden (wegen des Import-Befehls import '/css/body.css';), im Produktions-Modus nicht komprimiert werden. Da heißt, dass das CSS-Minimizer-Plugin hier nicht greift. Also sollte es ersetzt werden.

Glücklicherweise gibt es einen ausgezeichneten Ersatz: Den SASS-Loader. Anstelle von CSS sollte man SASS/SCSS verwenden, um auch CSS modularisieren und „DRY machen“ zu können. Glücklicherweise enthält diese Loader einen eigenen Minimizer.

Modifizieren Sie die Pakete entsprechend:

 
npm uninstall -D css-minimizer-webpack-plugin
npm i -D sass sass-loader

Löschen Sie das CSS-Minimizer-Plugin aus der Datei webpack.config.js, sowohl den Require-Befehl, als auch im Minimizer-Array im Optimization-Objekt.

Fügen Sie dafür den SASS-Loader in die CSS-Objekte des Rules-Arrays ein.

module:
{ rules:
  [ { test:    /head\.(css|scss|sass)$/,
      include: [ /src/ ],
      use:     [ MiniCssExtractPlugin.loader,
                 'css-loader',
                 { loader: 'sass-loader',
                   options: 
                   { sassOptions: { outputStyle: isProd ? 'compressed' : 'expanded' } },
                 },
               ],
    },

    { test:    /\.(css|scss|sass)/,
      include: [ /src/ ],
      exclude: [ /head\.(css|scss|sass)$/ ],
      use:     [ 'style-loader',
                 'css-loader',
                 { loader: 'sass-loader',
                   options: 
                   { sassOptions: { outputStyle: isProd ? 'compressed' : 'expanded' } },
                 },
               ],
    },
  ],
},

Dieser Loader übersetzt SCCS- und SASS-Anweisungen in CSS-Anweisungen. Er bietet zusätzlich eine Option outputStyle an, die festlegt, ob die CSS-Anweisungen komprimiert werden sollen oder nicht. Dies nutzen wir aus, um die CSS-Anweisungen im Produktionsmodus zu komprimieren.

Weiteres Optimierungspotenzial

Google PageSpeed Insights Version 5d ist immer noch nicht ganz zufrieden.

Zum einen sollten die Anzahl der Dateien (zwei) und die Dateigrößen weiter reduziert werden, was aber schwierig ist. Man könnte eine Datei budget.json definieren, die Google mitteilt, welche Dateianzahl und welche Dateigrößen man für akzeptabel hält. Dan erhält man erst ab diesen Werten eine Warnung.

Zum anderen teilt einen Google das größte Element in der HTML-Seite mit. Hier gibt es tatsächlich noch Optimierungspotenzial, gerade bei Single-Page-Anwendungen. Eine Single-Page wird in der Regel auf einmal geladen, aber nur portionsweise angezeigt. Wichtig ist eigentlich nur, dass der Inhalt „above the fold“ („über dem Zeitungsknick“) in der HTML-Seite enthalten ist. Das ist der Bereich, den der Benutzer nach dem Laden der Seite ohne Scrollen sieht. Bei der Hello-World-Anwendung wäre das die Section mit der ID section_form. Alle übrigen Bestandteile der HTML-Seite könnten dynamisch nachgeladen werden, sobald sie benötigt werden. Genauso funktioniert Google Maps: Es werden nur die Kacheln der Erdoberfläche in der Auflösung geladen, die momentan benötigt werden. Um Verzögerungen, die durch das Nachladen entstehen, gerin zu halten, werden Randkacheln und Kacheln in benachbarter Auflösung schon mal im Voraus geladen, während der Benutzer die aktuellen Karte betrachtet. Es ist natürlich nicht sichergestellt, dass die Kacheln tatsächlich irgendwann angezeigt werden müssen. Aber es ist wahrscheinlich. Und daher wird der potenziell unnötige Datenverkehr in Kauf genommen. Hier kommt es auf die Abwägung zwischen User Experience (möglichst kurze Wartezeiten) und Energie- und Kosteneffizienz (möglichst geringes Transfervolumen) an.

Das Transfervolumen kann weiter reduziert werden, indem die Dateien vor dem Ausliefern vom Server mittels gzip weiter komprimiert werden. Der Client muss sie dann erst wieder entpacken, bevor er sie verarbeiten kann. Dies ist aber eine Server-Optimierung, die nicht mit Webpack oder einen vergleichbaren Tool realisiert werden kann.

Fortsetzung des Tutoriums

Sie sollten nun Teil 6 des Tutoriums bearbeiten. In diesem Teil des Tutoriums wird ausgenutzt, dass in die Konfigurationsdatei von Webpack ein SCSS-Loader integriert wurde. Damit kann man das Prinzip “Don't Repeat Yourself” (DRY) auch für CSS-Dateien umsetzen.

Quellen

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