Einrichtung von Webpack für die Gutenberg-Entwicklung

Um einen Gutenberg-Block zu entwickeln, braucht es theoretisch nichts außer einem Text-Editor. Wer sich das Leben aber etwas leichter machen möchte (oder mindestens zunächst schwerer, wenn die Tools noch nicht bekannt sind), kann

  • Babel einsetzen, um mit modernem JavaScript zu entwickeln, das nach Kompilierung trotzdem auch von älteren Browsern verstanden wird,
  • SASS einsetzen, um sich Schreibarbeit bei den Styles zu sparen und
  • ein Tool wie Webpack nutzen, um Babel und die SASS-Kompilierung (und eventuelle weitere Dinge, wie automatische Generierung eines SVG-Sprites aus vielen einzelnen SVG-Icons, et cetera) nicht immer manuell anstoßen zu müssen.

Hier zeige ich am Beispiel von Webpack ein minimales Setup, das modernes JavaScript mit Babel kompiliert und SASS zu CSS macht.

Voraussetzungen

Ich gehe davon aus, dass ihr

Außerdem könnt ihr innerhalb des wp-content/plugins-Ordners schon mal ein leeres Verzeichnis für das Plugin anlegen (oder zum Verzeichnis eines existierenden Plugins wechseln), für das ihr später Webpack nutzen wollt.

package.json erstellen und Abhängigkeiten installieren

Begebt euch über die Kommandozeile in das gerade erstellte Plugin-Verzeichnis und führt den Befehl npm init aus. Jetzt solltet ihr einige Dinge zum Ausfüllen bekommen und am Ende bestätigen können, dass eine package.json mit dem angezeigten Inhalt erstellt werden soll.

Nachdem ihr eine package.json vorliegen habt, müssen wir uns ans Installieren der Abhängigkeiten machen. Überlegen wir kurz, was wir brauchen:

  • Webpack
  • Babel
  • SASS-Kompilierer

Webpack installieren

Beginnen wir mit der Installation von Webpack. Auf der »Installation«-Seite der Webpack-Dokumentation ist beschrieben, welche Befehle dafür notwendig sind (da wir mit einer Version neuer als Webpack 4 arbeiten, wird auch das CLI installiert):

npm install --save-dev webpack webpack-cli

Damit wäre die Installation von Webpack tatsächlich bereits fertiggestellt (die Konfiguration steht noch aus, darum kümmern wir uns gleich).

Um nachher problemlos auch für Windows-User, die kein »Windows Subsystem for Linux« nutzen, eine Umgebungsvariable setzen zu können, installieren wir noch das cross-env-Paket:

npm install --save-dev cross-env

Babel installieren

Seit Babel 7 heißt das Paket des Babel-Cores nicht mehr babel-core sondern @babel/core. Neben diesem Paket brauchen wir den babel-loader, um Babel mit Webpack nutzen zu können sowie das @wordpress/babel-preset-default als an die WordPress-Entwicklung angepasstes Babel-Preset.

Außerdem installieren wir @wordpress/babel-plugin-import-jsx-pragma zur automatischen Anwendung von JSX, @wordpress/element damit wir über JSX Elemente erstellen können und @babel/runtime-corejs2, das bei einem auf Babel 7 aktualisierten Projekt plötzlich notwendig war (vor dem Update auf Babel 7 waren keine der Abhängigkeiten aus diesem Absatz nötig).

npm install --save-dev babel-loader @babel/core @babel/runtime-corejs2 @wordpress/babel-preset-default
npm install @wordpress/babel-plugin-import-jsx-pragma @wordpress/element

Über das Preset bekommen wir beispielsweise direkt JSX-Support (eine einfacherer Art, Markup in JavaScript zu schreiben) und die Liste der von WordPress unterstützten Browser, die Babel für die Einbindung von Polyfills nutzt und wir auch für den Autoprefixer gebrauchen können.

SASS installieren

Kommen wir zum SASS-Teil, der die meisten direkten Abhängigkeiten benötigt. Wir brauchen zunächst verschiedene Loader, um das SASS über Webpack zu verarbeiten und als eigene Datei auszugeben. Mit PostCSS wollen wir automatisch den Autoprefixer über das Ergebnis laufen lassen.

npm install --save-dev css-loader extract-loader file-loader postscss-loader sass-loader

Jetzt kommen noch die Abhängigkeiten node-sass, autoprefixer und postcss-cli:

npm install --save-dev autoprefixer postcss-cli node-sass

Konfigurationsdateien erstellen

Nachdem wir jetzt endlich alle Abhängigkeiten installiert haben, machen wir uns an das Erstellen dreier Konfigurationsdateien. Eine für PostCSS, eine für Babel und eine für Webpack, die alle auf dieselbe Ebene wie die package.json gehören.

PostCSS konfigurieren

Die postcss.config.js sieht so aus:

module.exports = {
	plugins: {
		autoprefixer: { grid: true }
	}
}

Wir möchten den Autoprefixer nutzen und auch alte CSS-Grid-Syntax nachrüsten. Die Liste der unterstützten Browser geben wir über die package.json an:

"browserslist": [
  "extends @wordpress/browserslist-config"
],

Damit werden dieselben Browser berücksichtigt, die offiziell von WordPress unterstützt werden. Über npx autoprefixer --info könnt ihr euch anzeigen lassen, welche Browser das sind und welche Eigenschaften und Werte betroffen sind.

Babel konfigurieren

Über die .babelrc.js konfigurieren wir Babel. Der Großteil davon ist aus der Dokumentation des @wordpress/babel-plugin-import-jsx-pragma-Plugins, und ich bin froh, dass es irgendwie funktioniert 🙂

module.exports = {
	presets: ['@wordpress/default'],
    plugins: [
        [ '@wordpress/babel-plugin-import-jsx-pragma', {
            scopeVariable: 'createElement',
            source: '@wordpress/element',
            isDefault: false,
        } ],
        [ '@babel/transform-react-jsx', {
            pragma: 'createElement',
        } ]
    ],
};

Als Preset wählen wir das von WordPress und geben dann zwei Plugins für die automatische Umwandlung von JSX an.

Webpack konfigurieren

Für Webpack erstellen wir eine webpack.config.js-Datei und füllen sie mit dem folgenden Inhalt:

const path = require('path');

module.exports = {
	mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
	entry: ['./blocks/index.js', './blocks/editor.scss', './blocks/frontend.scss'],
	externals: {
		lodash: 'lodash'
	},
	output: {
		path: path.resolve(__dirname, 'assets'),
		filename: 'js/editor.blocks.js',
	},
	module: {
		rules: [
			/**
			 * Running Babel on JS files.
			 */
			{
				test: /\.js$/,
				exclude: /node_modules/,
				use: {
					loader: 'babel-loader',
				}
			},
			{
				test: /\.scss$/,
				use: [
					{
						loader: 'file-loader',
						options: {
							name: 'css/[name].blocks.css',
						}
					},
					{
						loader: 'extract-loader'
					},
					{
						loader: 'css-loader?-url'
					},
					{
						loader: 'postcss-loader'
					},
					{
						loader: 'sass-loader'
					}
				]
			}
		]
	}
};

Das ist jetzt ziemlich viel, gehen wir es also der Reihe nach durch:

  1. In path speichern wir uns das Node-Modul path, um später den Pfad zum Ausgabeverzeichnis der Dateien anzugeben.
  2. Innerhalb von module.exports leben die konkreten Angaben zur Konfiguration von Webpack.
  3. Über mode geben wir an, in welchem Modus Webpack laufen soll. Hier gibt es die beiden möglichen Werte production und development. Je nach gesetztem Wert werden im Hintergrund einige Standard-Einstellungen vorgenommen, die in dem verlinkten Artikel aufgelistet sind. Im production-Mode wird der erzeugte JavaScript-Code beispielsweise minifiziert.
  4. Mit entry geben wir als Array die Quelldateien an (die könnt ihr in dem Zuge direkt einmal erstellen). Wir haben im blocks-Verzeichnis eine JavaScript-Datei sowie zwei SASS-Dateien.
  5. externals gibt an, dass lodash bereits an anderer Stelle geladen wird (seit April gibt es lodash als externes Moduls, damit es nicht mehrfach in Bundle-Skripten vorhanden sein muss), also nicht in dem Ergebnis-Bundle enthalten sein soll – dafür wird dann bei wp_enqueue_scripts() als eine Skript-Abhängigkeit lodash angegeben.
  6. Über output legen wir fest, dass der Ausgabepfad assets sein soll und die JavaScript-Datei innerhalb von assets in ein Unterverzeichnis js mit dem Namen editor.blocks.js abgespeichert wird.
  7. In module legen wir die Regeln fest, die für die entry-Dateien angewandt werden sollen.
    1. Für alle JavaScript-Dateien außerhalb von node_modules möchten wir, dass der Babel-Loader ausgeführt wird.
    2. Für alle .scss-Dateien sollen mehrere Loader ausgeführt werden, die letztlich das SASS in CSS kompilieren und das Ergebnis nach dem Schema [name].blocks.css innerhalb des css-Verzeichnisses ablegen. Wir haben also dann zwei CSS-Dateien assets/css/editor.blocks.css und assets/css/frontend.blocks.css als Ergebnis. Ich bin nicht ganz sicher, warum zum Beispiel der extract-loader notwendig ist, aber ohne funktioniert es nicht 🙂

Einfach und intuitiv ist die Webpack-Konfiguration nicht wirklich, aber wenn man sich erst mal ein funktionierendes Grund-Setup zusammengesucht hat, kann man es ja in neuen Projekten wiederverwenden und bei Bedarf erweitern/anpassen.

NPM-Skripte für den Einsatz von Webpack erstellen

Für den einfachen Aufruf von Webpack erstellen wir jetzt noch drei NPM-Skripte und schreiben dafür folgendes in die package.json:

"scripts": {
  "build:dev": "webpack",
  "build:production": "cross-env NODE_ENV=production webpack",
  "watch": "webpack --watch"
}

Über npm run build:dev können wir Webpack für die Entwicklung ausführen lassen, über npm run build:production für den Live-Betrieb. npm run watch startet Webpack mit Watcher, sodass Änderungen an der JavaScript- oder den SASS-Dateien (oder in den jeweiligen Dateien importierten anderen Dateien) einen neuen Webpack-Durchlauf anstoßen.

Jetzt können wir mit den Dateien blocks/index.js, blocks/editor.scss und blocks/frontend.scss arbeiten und bekommen durch den Aufruf von Webpack Ausgabedateien in assets, die das Plugin einbinden kann.

Die folgenden Skripte gebe ich bei einem aktuellen Projekt als Abhängigkeiten beim Einbinden des Skripts via wp_enqueue_script() an:

[ 'wp-blocks', 'wp-element', 'wp-edit-post', 'lodash' ]

Im nächsten Artikel werden wir um die Konfiguration ein kleines funktionierendes Plugin erstellen.

20 Kommentare

  1. Deutlich einfacher (genau genommen zwei Zeilen) ist die Konfiguration von Webpack & Babel mit Hilfe von Laravel Mix (einem Wrapper um Webpack, Laravel hat sonst damit erst einmal nichts zu tun). Funktioniert auch für die Entwicklung von Gutenberg-Plugins wunderbar.

    Einfach wie hier vorgegeben für ein Stand-Alone-Project im Plugin-Ordner installieren (genau wie Webpack): https://laravel-mix.com/docs/4.0/installation#stand-alone-project

    und im webpack.mix.js folgendes Eintragen:

    let mix = require(‚laravel-mix‘);
    mix.react(‚blocks/index.js‘, ‚js/editor.blocks.js‘);

    mix.react (https://laravel-mix.com/docs/4.0/mixjs#react-support) sorgt für das passende JSX-Babel-Plugin.

    Die passenden NPM-Scripts findet man hier: https://laravel-mix.com/docs/4.0/installation#npm-scripts

    Natürlich kann man auch beliebige CSS Preprocessors mit Laravel Mix nutzen: https://laravel-mix.com/docs/4.0/css-preprocessors Ebenfalls einfach und deutlich weniger fehleranfällig als

    1. Hi Johannes,

      danke für deinen Kommentar und das Aufzeigen der Alternative! Ja, von Laravel Mix habe ich auch schon das ein oder andere Mal gehört. Falls möglich gehe ich aber gerne den direkten Weg, ohne mir noch mehr Abhängigkeiten aufzuladen als Webpack und Co. sowieso schon mitbringen 🙂

      Viele Grüße
      Florian

    2. Tatsächlich finde ich Ahmad Awais‘ create-guten-block noch einfacher zu nutzen. Und Ahmads Toolkit hat den Vorteil, dass es neben den Webpack und co keine zusätzlichen Abhängigkeiten eingeführt werden. Es bietet sogar die explizite Option, ein Projekt mit den grundlegenden Tools weiter zu entwickeln und auf das Toolkit zu verzichten.

      Aber weil es nie schaden kann, sich mit dem Handwerkszeug auszukennen, mit dem man täglich arbeitet, halte ich den Weg, den Flo hier beschrieben hat, erstmal für den, mit dem man sich beschäftigen sollte.

  2. Schön geschrieben. Bliebe zu ergänzen, dass die Richtung „macht Blöcke statt Meta-Boxen“ nicht nur mit erhöhtem Aufwand, sondern auch mit verminderter Funktionalität einhergeht, z.B. bei Validierung von Daten. s. z.B. https://github.com/WordPress/gutenberg/issues/4063

    Wenn dann noch die für die Abbildung komplexer Informationsmodelle eh wenig taugliche Datenhaltung durch Gutenberg und seine Kommentar-Speicherung noch verschlimmert wird, ist WordPress für viele Agenturen als Plattform für anspruchsvollere Web-Applikationen tot. Ist aber wahrscheinlich auch gar nicht gewollt.

    Reales Beispiel:
    Kunde: „Können wir im News-Archiv die erste Pullquote dazuschreiben?“
    Anderes CMS: „Klar. Gib mir 10 Minuten“
    WordPress mit Gutenberg: „Nein. Ich kann leider meine eigenen Daten nicht verarbeiten. Aber du kannst den Hintergrund bunt machen!“

    1. Hallo Werner,

      danke für deinen Kommentar und das Lob 🙂

      Zu den Meta-Boxen: damit habe ich mich bisher noch nicht auseinandergesetzt, aber ist ein wichtiger Punkt, das stimmt.

      Ja, das mit der Datenspeicherung ist auch ein Punkt den ich schade finde. Es gibt die Möglichkeit, die Blöcke eines Eintrags als Array zu bekommen ($blocks = parse_blocks( get_the_content() );) und dann beispielsweise das erste Pullquote woanders anzuzeigen, aber ist halt ein Umweg.

      Viele Grüße
      Florian

      1. Korrigiert mich: Der eigentliche Pullquote-Inhalt steht irgendwo im HTML-Klumpen und muss da erst rausgeparst werden. Die Definition dazu hat aber nur der Client per Javascript und nicht der Server. Und selbst per Hand das erste p-tag im ersten blockquote-tag im ersten figure-tag rausfieseln möchte ich gar nicht erst anfangen. Von Performance in einer Liste von 30 News-Artikeln mal gar nicht zu reden. Und von ‚deprecated blocks‘ auch nicht.

        Ich will da nicht drauf rumreiten, aber ein halbwegs systemtechnisch bewanderter Business-Kunde bewertet sowas als (Zitat) „konzeptionell absurd“ und landet bei einer WP-Alternative. Und das m.E. zu Recht.

        Grüße und lasst euch von mir nicht die Laune verderben.
        Werner

        1. Hallo Werner,

          ja, ein Beitrag ist weiterhin ein HTML-Klumpen in einem DB-Eintrag, korrekt. Aber du kannst über die von mir gepostete Code-Zeile via PHP ein Array bekommen, das alle Blöcke als Untereinträge enthält. Und in jedem Untereintrag hast du dann den Typ des jeweiligen Blocks, Inhalt, et cetera. Das macht WordPress intern – meine ich – grob genau so bevor es den Beitrag anzeigt. Und aus dem Array könnte man sich dann den Pullquote-Eintrag raussuchen und den im Theme dann irgendwo anzeigen. Ich habe das für ein Projekt genutzt, um einen Slider-Shortcode vor dem Titel eines Beitrags auszugeben, statt normal an der Stelle, wo er im Inhalt steht.

          Zur Performance: Ja, die wird darunter vermutlich ein wenig leiden. Eventuell könnte man einfach den Core-Filter für das Parsen abstellen, dann hätte man nur noch den aus dem Theme. Aber wie gesagt: klar, das ist ein Umweg und nicht so schön wie es bei anderen CMS möglich ist, die die Inhaltsblöcke getrennt in der DB speichern.

          »Grüße und lasst euch von mir nicht die Laune verderben.«

          Keine Sorge, lass ich nicht 😀 Sind ja alles auch berechtigte Punkte, die du ansprichst, die ich selbst auch störend finde.

          Viele Grüße
          Florian

          1. Schick, aber dröselt das auch den HTML-Inhalt eines einzelnen Blocks auf, wenn ich nicht den gesamten Block verwenden, sondern nur auf ein einzelnes im „innerHTML“ enthaltenes Attribut zugreifen will?

          2. Nein, da gibt es nur das gesamte HTML des Blocks zurück (ohne die Kommentare). Du bekommst die Block-Attribute als Unter-Array (also zum Beispiel bei einem Bild die Ausrichtung), aber nicht alle HTML-Elemente noch mal aufgedröselt.

        2. Hi Werner,

          Ich will da nicht drauf rumreiten, aber ein halbwegs systemtechnisch bewanderter Business-Kunde bewertet sowas als (Zitat) „konzeptionell absurd“ und landet bei einer WP-Alternative. Und das m.E. zu Recht.

          falls du auf Anhieb 1-2 Alternativen nennen könntest, dann wäre das super. Ist zwar schon was länger her, dass ich was getestet habe, aber irgendwie hat mich nicht wirklich etwas überzeugt. Vielleicht hat sich in den letzten Jahren was getan.

          1. Hallo Vlad,

            für die individuelleren Projekte ist für uns Craft CMS die erste Wahl. https://craftcms.com/ und ein Überblick in https://nystudio107.com/blog/craft-cms-3-orientation-guide-welcome

            Typischer Fall wäre z.B. eine Site für ein internationales Filmfestival, wo es ein sehr individuelles Layout, Mehrsprachigkeit und stark strukturierten, vernetzten Content gibt (sowas wie „Frau X spielt im romänischen Kurzfilm Y von 2017 die Rolle Z und wird deutsch von Frau A synchronisiert, er wird am … im Kino … gezeigt und läuft in der Reihe .. unter dem Jurypräsidenten …). Das bitte in allen Richtungen abfragbar.

            Ist also dann eine Alternative, wenn WordPress mit der Kunst am Ende ist. Projekte bei denen es mit WP genauso geht, werden auch weiterhin damit gemacht.

            Zu den Drupals & Co. dieser Welt kann ich nichts sagen, ProcessWire könnte man sich auch genauer anschauen.

            Gruß
            Werner

  3. Toll! Wie ich so oft sehe, funktioniert der Webpack Watcher leider nicht mit der üblichen Konfiguration. Dies muss ich üblicherweise mit `watchOptions.poll` in der webpack.config.js umgehen, was bei deinem Beispiel aber auch nicht funktionierte. Ist das ein Missverständnis auf meiner Seite, oder ein übliches Problem?

Schreibe einen Kommentar

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