Webpack wurde in letzter Zeit zur oft erwähnten Technologie und wird mit weiteren Werkzeugen wie Gulp oder Grunt verglichen. In diesem Artikel versuche ich das Prinzip und Philosophie des Webpacks zu skizzieren sowie ein paar Beispiele zusammenzufassen, wann und wie wir Webpack nutzen können und wann eher nicht.

Dieser Beitrag geht aus meinem früheren Text über Probekonfigurationen Webpack sowie Github aus, der im Rahmen der Fortbildungskurse bei uns in der App Agentur Ackee ❤ entstanden ist.

Was ist Webpack

Was sind Module

Node.js unterstützt seit seiner Entstehung CommonJS Code-Splitting für die Aufteilung des Programmierer-Codes in Module der Abhängigkeiten, die falls nötig geladen und angewendet werden können.

var $ = require('jquery'); – jQuery wird geladen als Abhängigkeit in einem Modul und es ist möglich diese anzuwenden.  
module.export = jquery;
– jQuery wird aus dem Modul exportiert und kann irgendwo als Abhängigkeit benutzt werden.

Code-Splitting und die Aufteilung in einzelne Module wird vor allem wegen Überschaubarkeit und einer einfachen Code-Verwaltung  gemacht. Es ist eine klassische Ingenieurpraxis, bekannt aus verschiedenen Programmiersprachen wie z. B. C, C++, PHP, Ruby, Python, Java. Deswegen wurde sie auch von Node.js als Terminal-Interpret des JavaScripts eingeführt.

Das Problem des Javascript in einem Internetbrowser ist, dass es nativ kein Code-Splitting unterstützt. Alle Scripts werden im Browser im Rahmen eines Kontexts  bearbeitet, und zwar in der Reihenfolge, in der sie aus einem HTML-Format geparst werden. Daraus können Probleme mit globalen Variablen und ihrem Kontext entstehen. Bei größeren Projekten muss dann nicht ganz klar sein, woher eine konkrete Abhängigkeit kommt und warum der Code eigentlich funktioniert.

Vorbereitung der Module für Browser

Hier kommt Webpack ins Spiel, der Code-Splitting im Browser so unterstützt, dass er uns ermöglicht, die Module genauso zu schreiben, als würden wir Javascript für Node.js-Interpret schreiben (d.h. CommonJS, aber ab der Version 2.0 kann er nativ ebenfalls ES Module sowie AMD). Wir haben also den Javascript-Code auf Basis z.B. der CommonJS Module wie Node.js geschrieben. Wie kann man diesen Code dem Browser übergeben, sodass er mit ihm arbeiten kann?

Webpack bearbeitet modular geschriebenen Code und bildet aus ihm ein Paket, eine Js-Datei, vereinfacht gesagt. Dazu ermöglicht er auch die Npm-Bundles als Module zu verwenden wie z.B. React oder jQuery. Diese müssen nicht als ein weiteres HTML-Script in das Dokument geladen werden. Das heißt: das Ende der globalen Variablen, Outscoping sowie des unvorhersehbaren Codes.

Man könnte einwenden, dass es für solche Zwecke bereits Browserify gibt. Das stimmt zwar, aber Webpack geht jedoch viel weiter. Der eigentliche Zweck des Webpacks ist zwar die Arbeit mit den Javascript-Modulen sowie das Kreieren von Paketen für den Browser , aber er ist so beschaffen, dass er eine Arbeit mit unterschiedlichen Typen von Assets ermöglicht. Bei einer richtigen Konfiguration ist er in der Lage, diese Assets zu bearbeiten und ein Bundle (oder mehrere) zu bilden, die einfach auf den Webserver geladen werden und alles funktioniert so, wie es funktionieren soll. Das heißt, dass es als eine Abhängigkeit in ein js-Modul z.B. in SASS oder CSS geladen werden kann.

Was sind Webpack-Module

Webmodule können praktisch alles sein, was wir in einen Code importieren können, der von Webpack bearbeitet wird. Es können also CommonJS Module, ES-Module, AMD (diese drei beherrscht Webpack nativ) sein, aber auch Sass @import in einem Sass-Code (nicht nativ, aber mit Hilfe einer Erweiterung) und Webpack wird sie bei einer richtigen Konfiguration durchgehen und bearbeiten können.

Webpack erlaubt uns auch Assets in js zu importieren, die kein Javascript darstellen, z.B.:

  • jsx, coffee
  • css, sass, less, stylus, postcss,
  • png, jpeg, svg
  • JSON, yamp, xml und weitere

Es ist wichtig sich zu merken, dass das Ergebnis von Webpack immer primär ein js-Bundle für eine Verwendung im Browser ist . Das heißt, dass es notwendig ist, auch mit anderen Assets was zu machen. Sie müssen bearbeitet werden und in js überführt oder in einer anderen Weise aus den Quellenmodulen in eigenständige Dateien extrahiert werden. Für diese Arbeit verwendet Webpack Loader und Plugins, die ihm ermöglichen ein spezifisches Preprocessing über die Module sowie Bundles durchzuführen, bevor das Output ins Filesystem gespeichert wird.

Webpack Zusammenfassung

  • Der primäre Zweck = Kreieren von js-Bundles aus einem modularen js-Code für die Verwendung im Browser
  • Durch seine Beschaffenheit ermöglicht er zu transformieren, bearbeiten, modifizieren oder jede beliebige Art von Asset in ein Bundle als Modul einzupacken
  • Für den Preprocessing der Assets nutzt er Loader oder Plugins
  • Input ist ein modularer Webpack-Code (d.h. Module unterschiedlicher Arten), Output ist js-Bundle(aber auch weitere Dateien, falls der Preprocessing richtig angewendet wurde)

Konfiguration des Webpacks

Die Konfigurationsdatei des Webpacks ist Node.js Modul, das ein Konfigurationsobjekt exportiert. Wir können mit ihm jeden Javascript-Code schreiben, der durch Node.js interpretiert werden kann, was uns viele Möglichkeiten bietet. Zum Beispiel können wir Dateien, die wir als Webpack-Inputs einstellen wollen, aus dem Filesystem dynamisch laden.

Die Ausgangsbezeichnung der Konfigurationsdatei ist webpack.config.js (falls nicht im Auftrag angegeben, sucht Webpack diese Datei) und Webpack wird durch  webpack --config ./configs/webpack.config.js in Gang gesetzt.

Das Konfigurationsobjekt beinhaltet 4 wichtigste Stichwörter:

  • entry beinhaltet die Einstellung des Inputs bei Webpack
  • output beinhaltet die Einstellung des Outputs bei Webpack
  • module beinhaltet die Einstellung der Arbeit mit den Modulen (vor allem die Einstellung der Loader)
  • plugins beinhaltet die PlugIns und ihre Einstellung

Entry

Entry beinhaltet die Einstellung des Inputs beim Webpack . Es definiert, wo Webpack anfangen soll, ein oder mehrere Bundle zu kreieren. Jede einzelne Datei, die im Entry aufgeführt ist, ist die Wurzel des Graphen der Abhängigkeiten, der von Webpack durchgegangen wird und das Ergebnispaket kreiert.

Falls wir eine Parallele zu Node.js ziehen, dann können wir uns jede Datei am Input des Webpacks als eine Datei vorstellen, die wir Node.js zur Interpretation überlassen. Das Ergebnisbundle Webpack können wir dem Browser übergeben und es wird als ein valider modularer Code funktionieren.

Entry hat folgende Arten:

  • string – Weg zur Datei => ein Entry-point, ein Bundle
  • field – das Feld der Wege zu den Dateien =>
  • object – das Objekt der benannten Wege zu den Dateien => mehr Entry-Points, mehr Bundles

Im Rahmen der Konfiguration kann der Schlüssel context aufgeführt werden, relative Wege im Entry beziehen sich dann auf nichts.

Regel: ein Entry-Point pro HTML-Seite, SPA = ein globaler Entry-Point, MPA = mehrere Entry-Points

Beispiel der Konfiguration für Entry:

Output

Output beinhaltet die Einstellung des Outputs des Webpacks. Es handelt sich um ein Objekt mit den Schlüsseln:

path

Weg in den Folder, wo das Ergebnis des Webpacks gespeichert wird. Durch die Einstellung von context kann dieser beeinflusst werden.

filename

Die Bezeichnung des Outputs:

  • falls der Input String oder Field ist, dann ist hier der String angegeben, unter dem das Bundle in path gespeichert wird,
  • falls der Input ein Objekt ist, dann wird hier ein Template String aufgeführt, der sagt, wie die Endbundles heißen sollen. Zum Beispiel: [name].js sagt, dass die Bundles nach den Schlüsseln aus dem Inputobjekt benannt werden sollen. Es kann auch z.B. [chunkhash].js , benutzt werden, was das Bundle unter einem Hash speichert, das durch Webpack berechnet wird.  

publicPath

Der öffentliche Weg zu den Dateien im Rahmen des Webpack-Outputs (wie bereits erwähnt, können einen Output auch z.B. css-Dateien darstellen) wird durch die Loader (z.B. url-Loader) und PlugIns benutzt. Es handelt sich um einen Weg, durch den ein Bundle auf dem Server ausgestellt wird.

Er ist von der Wurzel des Webservers abhängig, wo das Bundle ausgestellt ist. Ist diese Wurzel mit dem Path einig, dann sollte publicPath auf  / eingestellt sein.

Das Beispiel der Output-Konfiguration:

Falls wir Webpack nur zur Bildung der Bundles aus dem modularen ES5 des Javascript-Code benutzen wollen, dann reicht es bei der Konfiguration lediglich entry und output aufzuführen.

Loader

Loader sind Transformationen, die per module angewandt sind. Sie werden auf Module unterschiedlicher Typen angewandt, bevor ein Bundle kreiert wird. Innerhalb der Position module werden Regeln eingestellt, die bestimmen, in welchem Fall der Loader angewandt werden soll.

Jede Regel stellt sich aus einem Test durch einen regulären Ausdruck zusammen, der sagt, ob die aufgeführten Loader auf das Modul angewandt werden sollen oder nicht. Der Loader hat seine Bezeichnung und Einstellung durch Options:

Falls mehrere Loaders aufgeführt sind, dann werden sie auf das Modul schrittweise von unten angewandt. In dem oben erwähnten Beispiel wird auf Dateien mit der Endung .sass SASS , CSS und Style-Loader schrittweise angewandt.

Die in der Konfiguration eingestellten Loader werden automatisch angewandt, falls der reguläre Ausdruck für den Import des Moduls erfüllt ist. Zum Beispiel für die oben aufgeführte Konfiguration wäre ein Loader im Falle require('../sass/main.sass')  angewandt.

Die Loader müssen allerdings nicht in der Konfiguration eingestellt werden, sondern sie können auch direkt in der require Klausel angewandt werden: require('style-loader!css-loader!sass-loader!../sass/main.sass')  . Diese Anwendung der Loader ist der Konfiguration vorgezogen und die Loader werden vom Modul nach links angewandt. Die Konfiguration wird demnach über Query String durchgeführt require('babel-loader?presets=['react']!./component.jsx') .

Durch den Loader kann also JSX auf ES5 js transpiliert werden, ES6 js auf ES5 js, Sass auf CSS, die Bilder auf base64 Strings, JSON-Files auf js Objekte usw.

Javascript im Pipe muss immer das Ergebnis des letzten Loaders sein (weil Webpack ein Javascript Module-Bundler ist).

Es ist also notwendig, dass alle Module, die durch Javascript nicht interpretiert werden können, auf ES5 Javascript konvertiert werden, weil Webpack primär Javascript-Bundles bildet. Deswegen nimmt z.B. ein Style-Loader CSS und konvertiert es in Javascript, das dieses gegebene CSS dann dynamisch in den Kopf des HTML-Dokuments einfügt. Etwas Ähnliches muss dann auch mit den übrigen Non-js-Modulen gemacht werden.

Plug-Ins

Plug-Ins sind Transformationen per bundle . Sie werden wegen Funktionalität angewandt, die kein Loader sicherstellen kann. Es handelt sich z.B. um eine Stil-Extrahierung, Aussortierung der Vendors in selbstständige Bundles, Obfuskation (Minifizierung a Uglifizierung) des Javascript oder um Einstellung der globalen Variablen.

Die Plug-Ins werden durch die Angabe plugins konfiguriert. Es handelt sich um ein Feld, in das wir die Instanzen der einzelnen Plug-Ins übergeben, weil wir ein Plug-In mehrmals in unterschiedlichen Fällen verwenden können.

Ein Beispiel der Plug-In Verwendung:

Wie bereits im letzten Artikel erwähnt, es ist möglich mithilfe von Webpack auch andere Bundles als Javascript zu bilden. Wenn wir zum Beispiel verhindern wollen, dass CSS ins js-Bundle als ein Code (durch den style-loader ) zugegeben wird, stattdessen aber als ein selbstständiges CSS-Bundle extrahiert wird, können wir das höher erwähnte ExtractTextPlugin verwenden. Das Webpack generiert anhand von Plug-Ins output.path -Datei bundle.css , die alle CSS aus dem Javascript-Bundle beinhaltet, weil das Plug-In für den ganzen js-Bundle angewandt wird.

Wiefunktioniertdas Ganze ungefähr

  1. Webpack bildet die Abhängigkeitsgraphen, deren Wurzeln in entry aufgeführte Dateien sind
  2. Geht schrittweise alle Module in Richtung Tiefe hindurch, die in entry-Points required werden und auf die nach festgestellten Regeln Loader angewandt werden, das ein Transpilieren oder andere Transformation der Module machen können.
  3. Wenn Webpack den Abhängigkeitsgraphen für den ausgewählten entry-Point durchgeht, könnte er schon das Bundle bilden. Zuerst wendet er allerdings die spezifizierten Plug-Ins an. Hier kann es zur Minifikation oder Uglifikation (Obfuskation) oder zum Beispiel zur Extraktion der Vendor-Pakete kommen.
  4. Schließlich speichert Webpack das Bundle nach Spezifizierung im Output.

Ein Beispiel der Konfiguration

Module Bundler vs. Task runner

Gulp und Grunt sind Task Runner. Sie werden zur Automatisierung der Aufträge angewandt, die der Nutzer anhand der Programmierung oder Konfiguration definiert. Zu den Aufträgen kann Kompilation, Transpilation, Minifikation, Linting, Testen usw. gehören. Es ist wichtig allerdings, dass jeder Auftrag mit einem konkreten Asset (CSS, Javascript usw.) arbeitet und es liegt am Programmierer, dass er sicherstellt, dass das Ergebnis funktionieren wird, d.h. dass z.B. die Wege zu den Bildern richtig eingestellt werden.

Wie schon erwähnt wurde, Webpack ist primär ein Werkzeug, das mit Javascript arbeitet und ermöglicht, einen modularen Code zu schreiben, der Browser ins Bundle einpackt. Darüber hinaus macht es Webpack möglich, auch andere Arten von Assets in den JavaScript-Code hinzuzufügen und diese dann anhand von Loader sowie PlugIns zu bearbeiten. Aus dieser Sicht ist das Starten des Webpacks ein konkreter Auftrag.

Weder Gulp noch Grunt untersuchen den Code, der ihnen beigefügt wurde. Sie führen auf ihn nur definierte Aufträge durch. Webpack dagegen, analysiert den Code und je nach Konfiguration wird dieser angepasst und bearbeitet.

Darin besteht also der Hauptunterschied. Webpack ist ein konkreter Auftrag, der über einen JavaScript-Code gestartet wurde. Man kann es sich so vorstellen, als würde man einen Auftrag für sassCompiler starten, nur etwas komplizierter, weil es komplexere Transformationen des weitergeleiteten Codes erlaubt. 

Wann ist Webpack bei App Entwicklung anzuwenden

  • Sie schreiben einen modularen JavaScript-Code, den Browser bearbeiten können.
  • Sie möchten die Standards des JavaScript benutzen, die noch nicht implementiert wurden (ES6+).
  • Schreiben Sie ein SPA zum Beispiel im React , dann bietet sich Webpack geradezu an.
  • Sie schreiben MPA und wollen den modernen Code usw. 

Ich habe bisher nur JavaScript erwähnt, weil andere Assets sowieso ins Javascript importiert werden müssen. Es macht keinen Sinn, Webpack für SASS anzuwenden, falls das Projekt lediglich SASS oder CSS beinhaltet. Dann starten Sie lieber nur  sass --watch sass:css .

Ich persönlich nutze Webpack ununterbrochen, egal, ob ich nur winzige Stücke js-Code schreibe, oder ob es sich um ein großes Projekt handelt. Es ermöglicht mir, einen reinen, lesbaren und modernen Code zu schreiben, der ohne größeren Aufwand obfuskiert wird.

Weil ich Webpack regelmäßig benutze, bekomme ich direkt auch Loader für SASS und PostCSS zugegeben und kann somit auch moderne StyleSheets schreiben.

Einen Mehrwert stellt auch die Optimalisierung weiterer Assets dar, wie Bilder oder Fonts.

Geht Task Runner zusammen mit Webpack?

Jawohl. Wenn Sie z. B. Gulp verwenden wollen und in Ihrem Projekt einen modernen modulierbaren JavaScript-Code beifügen wollen, dann sehe ich keinen Grund, Webpack auszulassen, weil z.B. ein Plug-In für Gulp existiert, der Webpack als einen Auftrag startet. Wenn Sie zudem das Importieren von SASS in den JavaScript nicht mögen, dann können Sie bei der Kombination von Webpack und Gulp das Transpilieren von SASS in einen eigenen Auftrag abtrennen.

Aber

Wie oben bereits erwähnt, gehören unter die Aufträge, die Task Runner ausführen, z.B. die Minifizierung, Transpilieren, Linting oder das Testen. Das kann Webpack bei einem Quäntchen Konfiguration auch, es ist also nicht notwendig , Webpack zusammen mit Task Runner zu benutzen.

Einfach nur starten z.B.  export NODE_ENV=production; webpack --progress --config webpack.config.js oder den oben erwähnten Auftrag mit ins package.json beifügen und yarn run deploy.prod in Gang setzen. Man bekommt ein ähnliches Ergebnis wie mit Task Runner. Es hängt nur von der Lösung ab, die Ihnen am besten passt oder sich besser in Ihre Firmen-Dev-Flow fügt.

Nutzen Sie Yarn

Es gibt Situationen, wo die Entwicklung reibungslos läuft, aber wenn es in die Produktion über CI eingesetzt werden soll, geht plötzlich nichts mehr. So etwas habe ich mehrmals erlebt und fast immer waren Bundles, die aus dem NPM installiert wurden, daran schuld.

Der Grund ist einfach: beim Einsatz über CI werden die Bundles laut den in package.json eingeführten Versionen installiert, was eine Installierung von einem neueren Bundle verursachen kann, und dann hört alles auf zu funktionieren. Lösung? Yarn.

Yarn ist ein Bundling-System für Node.js . So eins haben wir bereits: npm . Yarn, genauso wie npm, funktioniert über den NPM-Repositoren und verwendet package.json. Das heißt, dass Sie mit ihm das Gleiche installieren, wie mit npm. Warum ist er also zu verwenden? Er hat eine etwas andere Philosophie, andere Befehle und vor allem verfügt er über lockfile.

Wenn Sie  yarn install starten und dabei auch die Datei yarn.lock neben package.json existiert, dann werden die Bundles nicht nach package.json installiert, sondern nach yarn.lock. Hier befinden sich die gesperrten Versionen aus der Installierung, was heißt, dass bei CI genau die gleichen Bundles installiert werden, wie die, die Sie haben. Falls am Projekt mehrere Menschen arbeiten, haben alle gleiche Versionen der Bundles und bei allen sollte das Projekt gleich funktionieren.

Es handelt sich um eine Funktionalität, die z.B. composer für PHP schon längst hat, bei npm fehlt sie leider.

Yarn behauptet dazu, dass er schneller als npm ist und dass er im Offline-Mode arbeiten kann, was aber nach meiner Meinung kein so großer Vorteil ist, wie der bereits erwähnte Lockfile.

Nutzen Sie Yarn, er vereinfacht Ihr Leben!

Nachtrag 6. 6. 2017: Zusätzlich würde ich gerne erwähnen, dass NPM zwar shrinkwrap besitzt, dieser aber bis vor kurzem nicht als Lockfile des höher erwähnten Composers funktioniert hat,

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.