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

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
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 ein 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 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)

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: 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 Attribute entry wird eine Datei mit dem Namen web/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 Modul CopyPlugin. Dieses kopiert alle HTML-Dateien (das ist in Ihrem Fall nur die Datei index.html) vom Ordner src in den Ausgabe-Ordner web. (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 auf head.css oder .css endet, sich aber von head.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 von npm 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

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