HTML5-Tutorium: JavaScript: Hello World 05 (Webpack): Unterschied zwischen den Versionen
Kowa (Diskussion | Beiträge) |
Kowa (Diskussion | Beiträge) |
||
Zeile 132: | Zeile 132: | ||
use: [ { loader: 'file-loader', | use: [ { loader: 'file-loader', | ||
options: { name: '[name].[ext]', | options: { name: '[name].[ext]', | ||
outputPath: 'css/' | outputPath: 'css/', | ||
} | } | ||
}, | }, | ||
'extract-loader', | 'extract-loader', | ||
'css-loader' | 'css-loader', | ||
] | ] | ||
}, | }, | ||
Zeile 144: | Zeile 144: | ||
exclude: [ /head\.css$/ ], | exclude: [ /head\.css$/ ], | ||
use: [ 'style-loader', | use: [ 'style-loader', | ||
'css-loader' | 'css-loader', | ||
] | ] | ||
}, | }, | ||
Zeile 152: | Zeile 152: | ||
</source> | </source> | ||
Es handelt sich um | 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 | 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> |
Version vom 10. April 2021, 18:05 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 Projektes
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 FuesBox[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
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 Projektes die Datei webpack.config.js
(die Sie unbedingt auch in Git speichern sollten). Fügen Sie folgenden Code in diese Datei ein.
webpack.config.js
const
path = require('path'),
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.
veraltet:
- file-loader
- extract-loader
- copy-webpack-plugin
- html-minimizer-webpack-plugin
neu
- mini-css-extract-plugin
- html-webpack-plugin
- html-inline-css-webpack-plugin
- html-replace-webpack-plugin
Verwendung des SCSS-Minimizers
Musterlösung: index.html
(5d)
(Git-Repository: 5d)
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)