HTML5-Tutorium: JavaScript: Hello World 05 (Webpack): Unterschied zwischen den Versionen
Kowa (Diskussion | Beiträge) |
Kowa (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
(6 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
Zeile 12: | Zeile 12: | ||
Im Tutorium wird „Webpack 5“ verwendet. | Im Tutorium wird „Webpack 5“ verwendet. | ||
==Erstellen eines neuen | ==Erstellen eines neuen Projekts== | ||
Erstellen Sie ein neues Projekt <code>HelloWorld05</code> als Fork von <code>HelloWorld04</code> (siehe [[HTML5-Tutorium: JavaScript: Hello World 02|Hello-World-02-Tutorium]]). | Erstellen Sie ein neues Projekt <code>HelloWorld05</code> als Fork von <code>HelloWorld04</code> (siehe [[HTML5-Tutorium: JavaScript: Hello World 02|Hello-World-02-Tutorium]]). | ||
Zeile 19: | Zeile 19: | ||
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 {{zB}} [[Webpack]]<ref name="webpack"/>, | 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 {{zB}} [[Webpack]]<ref name="webpack"/>, | ||
[[Browserify]]<ref>[https://browserify.org/ Browserify-Homepage]</ref> oder [[ | [[Browserify]]<ref>[https://browserify.org/ Browserify-Homepage]</ref> oder [[FuseBox]]<ref>[https://fuse-box.org/ FuseBox-Homepage]</ref>. | ||
Üblicherweise optimieren die Bundlers nicht nur JavaScript-Dateien, sondern auch HTML-, CSS-, JSON- und andere Dateien. | Üblicherweise optimieren die Bundlers nicht nur JavaScript-Dateien, sondern auch HTML-, CSS-, JSON- und andere Dateien. | ||
Zeile 97: | Zeile 97: | ||
Benennen sie zunächst den Ordner <code>web</code> ins <code>src</code> um. Im Ordner <code>src</code> erstellen Sie künftig neue Dateien, nehmen Änderungen vor etc. Dabei sollten Sie unbedingt darauf, dass sinnvolle Module angelegt werden, viele sinnvolle Kommentare vorhanden sind, die Blöcke mittels Leerzeichen sauber eingerückt sind etc. <strong>Verwenden Sie niemals Tab-Zeichen zum Einrücken, da jeder Text-Editor Tab-Zeichen anders einrückt</strong>. | Benennen sie zunächst den Ordner <code>web</code> ins <code>src</code> um. Im Ordner <code>src</code> erstellen Sie künftig neue Dateien, nehmen Änderungen vor etc. Dabei sollten Sie unbedingt darauf, dass sinnvolle Module angelegt werden, viele sinnvolle Kommentare vorhanden sind, die Blöcke mittels Leerzeichen sauber eingerückt sind etc. <strong>Verwenden Sie niemals Tab-Zeichen zum Einrücken, da jeder Text-Editor Tab-Zeichen anders einrückt</strong>. | ||
Der Inhalt des Ordners <code>web</code>, der vom Browser dargestellt wird, soll künftig mittels Webpack generiert werden. Erstellen Sie dazu im Wurzelverzeichnis des | Der Inhalt des Ordners <code>web</code>, der vom Browser dargestellt wird, soll künftig mittels Webpack generiert werden. Erstellen Sie dazu im Wurzelverzeichnis des Projekts die Datei <code>webpack.config.js</code> | ||
(die Sie unbedingt auch in Git speichern sollten). Fügen Sie folgenden Code in diese Datei ein. | (die Sie unbedingt auch in Git speichern sollten). Fügen Sie folgenden Code in diese Datei ein. | ||
Zeile 158: | Zeile 158: | ||
Es handelt sich um eine JavaScript-Datei, die direkt von Node.js interpretiert wird. | Es handelt sich um eine JavaScript-Datei, die direkt von Node.js interpretiert wird. | ||
Aus diesem Grund wird auch nicht das | Aus diesem Grund wird auch nicht das ECMAScript-6-Modul-Konzept eingesetzt, sondern | ||
[[CommonJS]], das Standard-Modulkonzept vom Node. Hier werden Module mittels <code>require</code> | [[CommonJS]], das Standard-Modulkonzept vom Node. Hier werden Module mittels <code>require</code> | ||
und nicht mittels <code>import</code> importiert. Für Sie ist das allerdings nebensächlich, | und nicht mittels <code>import</code> importiert. Für Sie ist das allerdings nebensächlich, | ||
Zeile 582: | Zeile 582: | ||
=== Weiteres Optimierungspotenzial === | === Weiteres Optimierungspotenzial === | ||
Google PageSpeed Insights [https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Fglossar.hs-augsburg.de%2Fbeispiel%2Ftutorium%2F2021%2Fhello_world%2FWK_HelloWorld05d%2Fweb%2Findex.html 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 <code>budget.json</code> 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 <code>section_form</code>. 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== | ==Fortsetzung des Tutoriums== | ||
Zeile 591: | Zeile 610: | ||
<references/> | <references/> | ||
<ol> | <ol> | ||
<li value=" | <li value="6"> {{Quelle|Kowarschick, W.: Multimedia-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 1. März 2023, 13:49 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) |
Inhalt | Teil 1 | Teil 2 | Teil 3 | Teil 4 | Teil 5 | Teil 6 | Vue 1 | Vue 2 | Vue 3 | Vue 4 | Vue 5 | Vue 6
Musterlösung: index.html
(5a), index.html
(5b), index.html
(5c), index.html
(5d),
(Git-Repositories: 5a, 5b, 5c, 5d)
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 Webpack[1] automatisch für den Server-Betrieb zu optimieren, sodass möglichst wenige, möglichst kleine Dateien zum Client übertragen werden.
Im Tutorium wird „Webpack 5“ verwendet.
Erstellen eines neuen Projekts
Erstellen Sie ein neues Projekt HelloWorld05
als Fork von HelloWorld04
(siehe Hello-World-02-Tutorium).
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. Webpack[1], Browserify[2] oder FuseBox[3].
Üblicherweise optimieren die Bundlers nicht nur JavaScript-Dateien, sondern auch HTML-, CSS-, JSON- und andere Dateien.
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. Dieser Bundler kommt in vielen Projekten zum Einsatz. 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.
Allerdings hat Webpack auch einige Nachteile:
- Es gibt diverse zugehörige Node.js-Pakete ganz unterschiedlicher Qualität. Für eine Aufgabe das beste oder zumindest ein gutes Paket zu finden, ist häufig eine Sisyphos-Arbeit.
- Mit jedem Versionssprung der Hauptrevisionsnummer haben sich bislang die Schnittstellen und Prinzipien so gravierend geändert, dass man die Webpack-Konfiguration im Wesentlichen neu erstellen musste.
- Auch kann es einem passieren, dass ein Paket, das bislang gewinnbringend verwendet wurde, plötzlich nicht mehr unterstützt wird und aufgrund von neu entdeckten Sicherheitsproblemen auch nicht mehr benutzt werden kann.
Dennoch werden wir im Tutorium Webpack 5 einsetzen. Die Vorteile überwiegen trotz aller Nachteile.
Webpack
Node.js-Pakete
Um Webpack verwenden zu können, müssen Sie zunächst ein Node.js-Projekt anlegen.
npm init -y
Tagen Sie wie im Tutorium HTML5-Tutorium: JavaScript: Entwicklungsumgebung: Node.js beschrieben geeignete Werte in die JSON-Datei package.json
ein (name
, version
, description
,
main
, repository
, keywords
, author
und license
).
Achten Sie darauf, dass Visual Studio Code keine Fehler meldet.
Installieren Sie nun die benötigten Node.js-Pakete:
npm i -D webpack webpack-cli
npm i -D copy-webpack-plugin
npm i -D css-loader style-loader file-loader extract-loader
Das Paket webpack
enthält das eigentlich Webpack-System. webpack-cli
enthält Kommandozeilenbefehle, mit denen Webpack von einer Shell aus oder auch mittels NPM-Skripts gesteuert werden kann.
Das Copy-Webpack-Plugin[4] dient dazu, mittels Webpack Dateien von einem Ordner in einen anderen zu kopieren. Verschiedene Loaders werden verwendet, um CSS- und JavaScript-Dateien für den „Bündelung“ vorzubereiten.[5]
Git commit
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. (Der Punkt zu Beginn des Dateinamens darf nicht fehlen!)
In diese Datei schreiben Sie ein einziges Wort: node_modules
.gitignore
node_modules
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
Fügen Sie nun alle Dateien (mit Ausnahme des Ordners node_modules
ins Repository ein (zum Beispiels mittels git add -u
) und committen Sie die Änderungen.
Webpack
Musterlösung: index.html
(5a)
(Git-Repository: 5a)
Als nächstes Webpack konfiguriert werden, damit die Dateien web/index.html
, web/css/head.css
und web/js/mains.js
automatisch generiert werden können.
Benennen sie zunächst den Ordner web
ins src
um. Im Ordner src
erstellen Sie künftig neue Dateien, nehmen Änderungen vor etc. Dabei sollten Sie unbedingt darauf, dass sinnvolle Module angelegt werden, viele sinnvolle Kommentare vorhanden sind, die Blöcke mittels Leerzeichen sauber eingerückt sind etc. Verwenden Sie niemals Tab-Zeichen zum Einrücken, da jeder Text-Editor Tab-Zeichen anders einrückt.
Der Inhalt des Ordners web
, der vom Browser dargestellt wird, soll künftig mittels Webpack generiert werden. Erstellen Sie dazu im Wurzelverzeichnis des Projekts die Datei webpack.config.js
(die Sie unbedingt auch in Git speichern sollten). Fügen Sie folgenden Code in diese Datei ein.
webpack.config.js
const
path = require('path'),
CopyPlugin = require('copy-webpack-plugin');
function absolutePath(l_path)
{ return path.resolve(__dirname, l_path); }
module.exports =
{ entry:
{ head: absolutePath('src/css/head.css'),
main: absolutePath('src/js/main.js'),
},
output:
{ filename: 'js/[name].js',
path: absolutePath('web'),
},
plugins:
[ new CopyPlugin
({ patterns:
[{ context: 'src',
from: '*.html',
to: '.',
},
],
}),
],
module:
{ rules:
[ { test: /head\.css$/,
include: [ /src/ ],
use: [ { loader: 'file-loader',
options: { name: '[name].[ext]',
outputPath: 'css/',
}
},
'extract-loader',
'css-loader',
],
},
{ test: /\.css$/,
include: [ /src/ ],
exclude: [ /head\.css$/ ],
use: [ 'style-loader',
'css-loader',
],
},
],
},
};
Es handelt sich um eine JavaScript-Datei, die direkt von Node.js interpretiert wird.
Aus diesem Grund wird auch nicht das ECMAScript-6-Modul-Konzept eingesetzt, sondern
CommonJS, das Standard-Modulkonzept vom Node. Hier werden Module mittels require
und nicht mittels import
importiert. Für Sie ist das allerdings nebensächlich,
da Sie im Rahmen der Tutorien keine eigenen komplexen Konfigurationsdateien für webpack implementieren werden.
Im Prinzip macht diese Datei nichts anderes als ein Objekt zu exportieren, das Webpack-Konfigurationsinformationen enthält:
entry
: JavaScript-Dateien, die gebündelt und komprimiert werden sollen. Man muss jeweils nur die Wurzeldatei angeben. webpack sucht in dieser Datei rekursiv nach Import-Befehlen und packt die benötigten Dateien ebenfalls in die Ausgabedatei.output
: Hier wird festgelegt, wie die gepackten Dateien heißen sollen. Für jeden Eintrag im Attributeentry
wird eine Datei mit dem Namenweb/js/[name].js
erzeugt, wobei[name]
durch den Schlüsselname des Entry-Eintrags ersetzt wird. In der obigen Datei gibt es zwei Entry-Elemente.plugins
: Hier werden Webpack-Hilfsmodule geladen, die bestimmte Zusatzaufgaben erledigen. Sie verwenden derzeit nur das ModulCopyPlugin
. Dieses kopiert alle HTML-Dateien (das ist in Ihrem Fall nur die Dateiindex.html
) vom Ordnersrc
in den Ausgabe-Ordnerweb
. (In einer späteren Webpack-Konfiguration wird auch noch die HTML-Datei komprimiert werden.)module
: In diesem Attribut werden Regeln (rules
) angegeben,die festlegen, auf welche Weise eine Datei transformiert werden sollen. Bei jeder Regel gibt es einen Test, der festlegt, für welche Dateien die jeweilige Regel zutrifft. Mit Hilfe eines regulären Ausdrucks wird im Tutoriumsbeispiel geprüft, ob die aktuelle Datei aufhead.css
oder.css
endet, sich aber vonhead.css
unterscheidet. Je nach Dateiname werden andere Loader zum Laden und Verarbeiten der zugehörigen Datei verwendet. (Für webpack gibt es diverse Loader, einige davon haben Sie zuvor mit Hilfe vonnpm
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
head.css
vom File-Loader Anweisungen in die Datei web/css/head.css
geschrieben.
Dies hätten wir auch mit dem Copy-Plugin erreicht. Allerdings kann man für den File-Loader einen Optimierer hinzufügen, der die CSS-Datei komprimiert (minimiert), bevor sie in den Web-Ordner eingefügt wird (siehe Version 5b).
Alle übrigen CSS-Dateien werden nicht auf die Platte geschrieben, sondern in die JavaScript-Datei eingefügt, die die CSS-Datei importiert. Dies ist die Aufgabe des Style-Loaders. JavaScript-Dateien werden von Webpack automatisch zu einer Datei zusammengefasst und (im Produktionsmodus) minimiert. Dies ist das Defaultverhalten des JavaScript-Bundlers und braucht daher nicht in eigenen Regeln beschrieben zu werden.
Mit dem Style-Loader wird erreicht, dass die Datei src/css/main.css
in der
Ausgabedatei web/js/main.js
enthalten ist. Damit wird die Zahl der Dateien reduziert: Aus src/js/main.js
, src/js/greet.js
und src/css/main.css
erzeugt Webpack eine (minimierte)
Datei namens web/js/main.js
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 in das scripts
-Objekt der Datei package.json
folgende Skript-Anweisungen ein:
"scripts": {
"predev": "rm -rf web/*",
"dev": "webpack --mode development",
"preprod": "rm -rf web/*",
"prod": "webpack --mode production",
"postprod": "rm web/js/head.js",
"prewatch": "rm -rf web/*",
"watch": "webpack --mode development --watch"
}
Dieser Konfiguration definiert drei NPM-Skripte: dev
, prod
und watch
.
Diese Skript können Sie von der Kommandozeile aus starten:
npm run dev
npm run prod
npm run watch
Die Skripte predev
, preprod
und prewatch
werden jeweils ausgeführt, bevor die eigentlichen Skripte ausgeführt werden. In allen drei Fällen wird der Web-Ordner vollständig gelöscht, da er ja neu erstellt werden soll. Für das NPM-Skript prod
gibt es auch noch das Skript postprod
, das
nach dem Skript prod
ausgeführt wird. Es löscht die Datei web/js/head.js
, die von Webpack angelegt wurde. Sie wird allerdings nicht mehr gebraucht, da mit daraus mit dem Extract-Loader CSS-Inhalt aus der Datei web/js/head.js
extrahiert und mit dem File-Loader in die Datei
web/css/head.css
extrahiert wird. Diese CSS-Datei wird von der Datei web/index.html
direkt eingelesen.
Geben Sie nun im Terminal die Anweisung npm run dev
(dev = development) ein.
Wenn Sie alles richtig gemacht haben, müsste Webpack fehlerfrei durchlaufen und die Dateien
web/index.html
, web/js/main.js
sowie web/css/head.css
(sowie die überflüssige Datei web/js/head.js
) erstellen. Im Zweifelsfall müssen Sie auf das Reload-Icon hinter HELLOWRLD05A
klicken.
Wenn Sie jetzt die Datei index.html
im Browser öffnen, sollten Sie nach kurzer Zeit
wieder das Begrüßungsmenü sehen. Nach Eingabe des Namens und Klick auf den Say-hello-Button
oder Drücken der Enter-Taste sollten Sie freundlich begrüßt werden.
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
- Kowarschick (MMProg): Wolfgang Kowarschick; Vorlesung „Multimedia-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2018; Quellengüte: 3 (Vorlesung)