WordPress: Template mit webpack, Sass, Bootstrap, Composer und Deployment erstellen

WordPress-Themes gibt es da draußen in der wilden Internetwelt wie PKW, deren wahre Abgaswerte man nicht kennt (viele). Wieso zur Hölle sollte man sich also den Aufwand machen, ein Theme von Grund auf zu entwickeln? Macht das Sinn?

Eine philosophische Frage, der sich jeder Entwickler jeden Tag stellen muss. Investiere ich die Zeit in eine Komponente, die es schon gibt? Oder programmiere ich mir lieber mein eigenes Grab, dann weiß ich wenigstens, wie es sich darin liegt?

Der zweite Punkt ist für einen Entwickler sicherlich der Naheliegendste. Der Frust, etwas neues zu lernen führt meistens dazu, dass man etwas selbst neu anfängt. So hört man z.B. in jedem Bewerbungsgespräch ganz stolz “Ein eigenes CMS habe ich natürlich auch”.

Wenn man älter wird, hat man aber durchaus keine Lust mehr, das Rad neu zu erfinden, nur, weil man denkt, “das bekommt man runder hin”. Wenn man älter wird, hat man zu vielen Dingen weniger Lust (z.B. Blog-Artikel schreiben, kotz). Also warum nicht die Zeit, Ruhe und Geduld investieren, um Technologien zu erlernen, die sich schon am Markt bewährt haben?

OK, genug gelabert! Der Text wird eh von den meisten überscrollt. Ran ans Eingemachte!

Was machen wir heute?

Die Welt erobern! Und dann noch das hier:

  • WordPress über die CLI installieren
  • Eigenes Template erstellen
  • Überlegen, wie die Dateistruktur aussehen soll
  • webpack konfigurieren
  • Bootstrap einbinden
  • Das Theme auf den Server deployen

WordPress über CLI installieren

Der einfachste Weg, WordPress zu installieren, geht über die WordPress-CLI. Ich gehe jetzt einfach mal davon aus, dass du bereits einen funktionierenden Webserver hast. Falls nicht, hier gucken!

Um das CLI zu installieren, folge am besten der offiziellen Dokumentation.

Als nächstes: Eine Datenbank einrichten. Falls du noch keinen Server dazu hast, empfehle ich einen ausfallsicheren Galera-Cluster.

Los gehts! Mit den folgenden Shell-Befehlen hast du dein WP in wenigen Sekunden eingerichtet.

# Das Verzeichnis für WordPress erstellen
cd ~
mkdir wptest.local
cd wptest.local

# Die WP-Dateien downloaden
wp core download

# Die Config-Datei erstellen
wp config create --dbname=wptest --dbuser=root --dbpass=test --locale=de_DE

# Die Installation beginnen
wp core install --url="http://wptest.local"  --title="Wordpress Test" --admin_user="admin" --admin_password="test" --admin_email="admin@wptest.local"

Achtung: Natürlich ist es nie eine gute Idee, Passwörter in der Shell-History zu hinterlassen, aber für eine Entwicklungsumgebung ist das völlig OK.

Wenn alles geklappt hat (und der Virtual-Host richtig eingerichtet ist), kannst du dich unter http://wptest.local/wp-admin einloggen, bzw unter http://wptest.local ein Ergebnis sehen:

Erfolg! Was sehen wir hier?

Wenn du dich in die Administration einloggst, siehst du unter “Appearance > Themes” eine Aufstellung aller Templates, die WP bereits mitliefert.

Diese befinden sich im folgenden Verzeichnis: /wp-content/themes

Eigenes Template erstellen

Um ein eigenes Template zu erstellen, muss man keine Jungfrauen opfern, sondern einfach nur in o.a. Verzeichnis ein paar Dateien anlegen:

1. Verzeichnis erstellen: /wp-content/themes/meintemplate

2. Style-Sheet erstellen: /wp-content/themes/meintemplate/style.css

/*
Theme Name: wptest
Author: Björn Falszewski
Author URI: https://bjoern.info
Version: 1.0
*/

3. Index-Datei erstellen: /wp-content/themes/meintemplate/index.php

Hallo Welt!

Das war’s! Jetzt findet man das Template unter Themes und kann es aktivieren:

Das Ergebnis sieht dann so aus:

Reicht doch, oder? Fertig! Jetzt erstmal eine Runde Apex Legends. Prokrastination ist wichtig, man muss ja motiviert bleiben.

Das Template erweitern

Na gut – die Ausgabe sollte den wenigsten reichen, obwohl sie eigentlich eine frohe Nachricht vermittelt und alle damit zufrieden sein sollten. Also machen wir das einfachste und stellen die letzten Blog-Posts dar.

Dazu die Datei /wp-content/themes/meintemplate/index.php anpassen (wir machen es ganz einfach):

<html>
	<head>
		<title>Hallo Welt!</title>
	</head>

	<body>

		<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
			<b><?php the_title(); ?></b><br />
			<?php the_content(); ?>
			<hr />
		<?php endwhile; endif; ?>	
	
	</body>
</html>

Ein paar Anmerkungen dazu:

  • Für das HTML-Grundgerüst (d.h. Header und Footer) stellt WordPress eigene Funktionen bereit, die hier noch nicht genutzt werden.
  • Um z.B. Blog-Posts anzuzeigen, stellt WordPress auch Funktionen bereit, an deren Nutzung man sich erst gewöhnen muss, wenn man sich seit den 90ern technisch weiterentwickelt hat. Und damit meine ich die 1890er. Einfach zu verstehen sind sie aber!
  • WordPress kennt keine Template-Sprache wie Twig oder Smarty, deshalb muss man zum guten alten PHP greifen. Hier gibt es ein paar Syntax-Konstruktionen, die einem helfen, das ganze ein bisschen übersichtlicher zu halten (z.B. eine Schleife mit : zu beginnen und mit endwhile zu beenden).

Was haben wir jetzt genau vor?

Für unser Template benötigen wir JavaScript– und CSS-Dateien. Die binden wir aber nicht einfach so ein, sondern kompilieren sie direkt mit webpack, so dass wir externe Libs, wie z.B. Bootstrap, einfach einbinden können.

Unser (finales) Theme-Verzeichnis wird so aussehen:

  • /assets: Hier liegen alle Dateien, die später per HTML geladen werden.
    • /build: Hier liegen alle minifizierten Datei, direkt aus webpack.
    • /img: Hier liegen unsere eigenen, statischen Bilder, die wir später im Template nutzen wollen.
  • /node_modules: Wird automatisch durch yarn erstellt und enthält z.B. webpack, Sass, etc.
  • /src: Enthält alle unsere eigenen JS/CSS-Dateien, die wir später kompilieren.
  • /vendor: Wird vom Composer erstellt und stellt uns PHP-Libs zur Verfügung.

Achtung: Wenn du die Grundlagen von webpack noch nicht kennst, lies am besten meine Einführung!

Das Theme bis zum letzten auszuarbeiten ist aber gar nicht das Thema: Stattdessen konzentrieren wir uns auf ein vernünftiges Framework, um mit webpack JavaScript und Sass-Dateien schöne kompilierte Dateien zu erzeugen und diese schön auf unseren schönen Live-Server zu deployen.

Übrigens: Für Git und Deployment ist wichtig zu wissen, dass das Verzeichnis node_modules nicht commited, bzw. deployed werden muss. Das wäre auch übel, denn hier sammeln sich mit der Zeit tausende Dateien an…

webpack: Vorbereitungen

Wechsle jetzt in dein Theme-Verzeichnis (/wp-content/themes/meintheme). Wir werden alles in diesem Verzeichnis erledigen – der Rest von WordPress wird nicht angefasst!

Anschließend installieren wir alles, was wir für dieses Projekt brauchen:

yarn add webpack webpack-cli mini-css-extract-plugin clean-webpack-plugin node-sass css-loader sass-loader file-loader

Danach erstellen wir die Datei webpack.config.js:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
	mode: 'production',
	entry: {
		// Basis JS und CSS
		global: './src/js/global.js',
	},
	output: {
		filename: '[name].min.js',
		path: path.resolve(__dirname, 'assets/build')
	},
	plugins: [
		new MiniCssExtractPlugin({
			filename: '../build/[name].min.css'
		}),
		new CleanWebpackPlugin(),
	],
	module: {
		rules: [
			{
				test: /\.(png|jpeg|jpg|ttf|svg|eot|woff|woff2)$/,
				use: [
					{
						loader: 'file-loader',
					}
				],
			},			
			{
				test: /\.(sa|sc|c)ss$/,
				use: [
					{
						loader: MiniCssExtractPlugin.loader,
						options: {
							hmr: process.env.NODE_ENV === 'development',
						},
					},
					'css-loader',
					'sass-loader',
				],
			},
		]
	  }
};

Wenn du der Einführung in webpack gefolgt bist, sollten dir hier keine neuen Dinge auffallen.

Wir beginnen mit einer Datei, nämlich global.js, die auch unser CSS einbinden wird. Diese wird mit yarn webpack in das Verzeichnis assets/build kompiliert.

So ungefähr sieht das dann aus:

JS/CSS-Dateien in Theme einbinden

Bevor wir jetzt weitermachen, sollten wir die entsprechenden Dateien in das WordPress-Theme einbauen. Ich greife mal vorweg und liste kurz auf, welche Dateien das sein werden:

  • global.min.js: Unsere “Basis”-JS-Datei
  • bootstrap.min.js: Ausgewählte Skripte aus Bootstrap
  • fontawesome.min.js: Bonus! Wir kompilieren nur die Icons, die wir benötigen.
  • frontpage.min.js: Diese Datei wird nur in der Frontpage genutzt (d.h. die “Homepage”).
  • singular.min.js: Diese Datei wird von Blogeinträgen aufgerufen (“singular” ist ein Begriff von WordPress für diese Art von Seiten).
  • global.min.css: Unsere CSS-Datei, die unsere Sass-Dateien und ausgewählte von Bootstrap enthält.

Diese Dateien binden wir nicht über das vorhandene Template index.php ein, sondern nutzen dafür die Struktur von WordPress. WordPress bietet eigene Funktionen, um Skripte und CSS “einzureihen”, denn andere Libs werden u.U. schon geladen (jQuery ist zum Beispiel schon direkt dabei, das benötigen wir nicht in der Kompilierung).

Als erstes erstellen wir eine Datei functions.php. Diese wird von WordPress automatisch geladen.

<?php

/********************************************************
* SCRIPTS AND STYLES
********************************************************/
function add_theme_scripts() {
	// Global JS
	wp_register_script( 'global-js', get_template_directory_uri() . '/assets/build/global.min.js', array ( 'jquery' ));
	wp_enqueue_script('global-js');

	// Bootstrap
	wp_register_script( 'bootstrap-js', get_template_directory_uri() . '/assets/build/bootstrap.min.js', array ( 'jquery' ));
	wp_enqueue_script('bootstrap-js');

	// Font awesome
	wp_register_script( 'fontawesome-js', get_template_directory_uri() . '/assets/build/fontawesome.min.js', array ( 'jquery' ));
	wp_enqueue_script('fontawesome-js');

	// Global CSS
	wp_register_style( 'main-css', get_template_directory_uri() . '/assets/build/global.min.css', array(), filemtime(get_template_directory() . '/assets/build/global.min.css'), false);
	wp_enqueue_style('main-css');

}
add_action( 'wp_enqueue_scripts', 'add_theme_scripts' );

Damit passiert aber noch nichts, denn wir müssen WordPress in der index.php mitteilen, wo sich unser Footer und Header befinden. Das sieht dann so aus:

<?php
	get_header();
?>

	<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
		<b><?php the_title(); ?></b><br />
		<?php the_content(); ?>
		<hr />
	<?php endwhile; endif; ?>	
	
<?php
	get_footer();
?>

Wenn du die Seite neu lädst, kommt (überraschenderweise) folgendes Ergebnis:

Natürlich meckert der Browser, dass ein paar Dateien fehlen, aber das zeigt uns, dass Header und Footer jetzt richtig im Template eingebunden sind. Du kannst jetzt die Zeilen zum Laden der Dateien auskommentieren oder drin lassen – als nächstes kümmern wir uns um das CSS.

Bootstrap als CSS-Basis einbauen

Wir machen jetzt folgendes: Unsere neue Datei src/css/style.scss erstellen, in der global.js referenzieren und die Bootstrap-Lib benutzen.

Dazu erstmal alles nötige über Yarn installieren.

yarn add popper.js jquery bootstrap

Als nächstes: Die Datei src/css/style.scss erstellen.

// Import Bootstrap
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";
// @import "~bootstrap/scss/root"; 
// @import "~bootstrap/scss/reboot";
// @import "~bootstrap/scss/type";
// @import "~bootstrap/scss/images";
// @import "~bootstrap/scss/code";
// @import "~bootstrap/scss/grid";
// @import "~bootstrap/scss/tables";
// @import "~bootstrap/scss/forms";
// @import "~bootstrap/scss/buttons";
// @import "~bootstrap/scss/transitions";
// @import "~bootstrap/scss/dropdown";
// @import "~bootstrap/scss/button-group";
// @import "~bootstrap/scss/input-group";
// @import "~bootstrap/scss/custom-forms";
// @import "~bootstrap/scss/nav";
// @import "~bootstrap/scss/navbar";
// @import "~bootstrap/scss/card";
// @import "~bootstrap/scss/breadcrumb";
// @import "~bootstrap/scss/pagination";
// @import "~bootstrap/scss/badge";
// @import "~bootstrap/scss/jumbotron";
// @import "~bootstrap/scss/alert";
// @import "~bootstrap/scss/progress";
// @import "~bootstrap/scss/media";
// @import "~bootstrap/scss/list-group";
// @import "~bootstrap/scss/close";
// @import "~bootstrap/scss/toasts";
// @import "~bootstrap/scss/modal";
// @import "~bootstrap/scss/tooltip";
// @import "~bootstrap/scss/popover";
// @import "~bootstrap/scss/carousel";
// @import "~bootstrap/scss/spinners";
// @import "~bootstrap/scss/utilities";
// @import "~bootstrap/scss/print";

Huch, warum ist denn da soviel auskommentiert? Wir könnten es uns einfach machen, und das gesamte Framework einbauen: @import "~bootstrap/scss/bootstrap";

Aber das würde auch bedeuten, dass wir unnötiges Zeugs mitschleppen und den User laden lassen. Deshalb: Erstmal nur das Nötigste!

Nächster Schritt: Unsere CSS-Datei in global.js erwähnen.

// Eigenes CSS
import '../css/style.scss'; 

console.log("Hallo Welt");

Nach einem yarn webpack haben wir damit ein CSS-Grundgerüst, mit dem wir arbeiten können. Beispiel gefällig? Wie wäre es mit einem ultracoolen Button?

Ultracooler Button ist ultracool.

Bootstrap-JS einbauen

Als nächstes binden wir das Bootstrap-JavaScript ein. Dazu erstellen wir aber einen neuen Entrypoint, damit wir eine eigene Bootstrap-JS-Datei haben (die kann ein bisschen größer werden und man sollte große Dateien vermeiden).

Also auf in die webpack.config.js:

...
	entry: {
		// Basis JS und CSS
		global: './src/js/global.js',
		// Bootstrap
		bootstrap: './src/js/bootstrap.js',
	}
...

Und analog zur SCSS-Datei die nötigen JS-Dateien einbinden. In unserem Beispiel hätte ich gerne einen Tooltip über dem Button. Also sieht bootstrap.js erstmal so aus (schaue in den Ordner /node_modules/bootstrap/js/dist für alle möglichen Tools):

import 'bootstrap/js/dist/tooltip';

Dann noch den Button anpassen:

Einmal kompilieren und… nichts passiert. Das liegt daran, dass das Attribut data-toogle="tooltip" erst einmal über jQuery getriggert werden muss. Also die bootstrap.js erweitern:

import 'bootstrap/js/dist/tooltip';

jQuery(function () {
	jQuery('[data-toggle="tooltip"]').tooltip()
})

Das ist ja doof, jetzt meckert der Browser:

Man könnte fast meinen, ich hätte das mit didaktischer Absicht gemacht. Hab ich auch. Wo ist also das Problem, Alter?

Zwei Dinge: jQuery wird bereits von WordPress als Standard geladen – es macht keinen Sinn, das in unser webpack zu kompilieren. Zweites Problem dadurch: Die Variable jQuery ist der Datei bootstrap.js unbekannt. Wir müssen webpack also mitteilen, dass jQuery irgendwo extern liegt und zur Laufzeit verfügbar ist. Das geht so (webpack.config.js):

...
module.exports = {
...
	externals: {
		jquery: 'jQuery'
	},
...

Und siehe da – es klappt!

Bin jetzt sehr stolz auf dich.

Fontawesome-Icons rauspicken

Jetzt solltest du genügend Werkzeuge haben, um dich weiter mit Bootstrap und WordPress zu beschäftigen. Einen Bonus habe ich an der Stelle noch: Icons. Ich benutze gerne die Icons von fontawesome.com – aber wenn man die alle einbindet, wächst die Dateigröße schnell unnötig an.

Besser wäre es doch, die einzelnen Icons rauszupicken, oder? Easy! Erstelle die Datei fontawesome.js, füge sie als Entry hinzu und sorge dafür, dass sie von WordPress geladen wird.

Dann suchen wir uns die Icons raus. Wie wäre es mit dem hier?

Das ist “check” und wird über <i class="fas fa-check"></i> eingebunden. Unsere fontawesome.js sieht dann so aus:

// Core Lib laden
import { library, dom } from "@fortawesome/fontawesome-svg-core";

// Icons importieren
import { faCheck } from "@fortawesome/free-solid-svg-icons/faCheck";

// Icon hinzufügen
library.add(faCheck);

// Aktivieren
dom.watch();

Tipp: Falls du einen Fehler bekommst (den solltest du bekommen), hoffe ich, dass du ihn jetzt selbst lösen kannst. Hat mit Yarn und Paketen (dies und das) zu tun 🙂

Wow! Sieht superprofessionell aus!

Deployment der WordPress-Umgebung

Deployment heißt: Wie kriegen wir das ganze jetzt von unserer Entwicklungsumgebung auf den Server? Dafür gibt es viele automatische Tools – aber manchmal muss man es einfach manuell machen, auch, um es zu verstehen.

WordPress-Entwicklung ist ein bisschen anders als bei einem eigenen Projekt. Der Endnutzer, für den du die Seite baust, installiert vielleicht ein paar Plugins oder erstellt Inhalte, von denen du gar nichts weißt. Alles, was du tun musst, ist das Template-Verzeichnis unter Kontrolle bringen. Das ist deins und kein WordPress-Nutzer darf daran rumspielen.

Wenn du dein WP-Projekt also auf dem Live-Server installierst, kopierst du erstmal deine lokale Datenbank und alle Dateien außer deinem Template. Das deployen wir extra.

Außerdem musst du die Datei wp-config.php anpassen und mit hoher Wahrscheinlichkeit die Domain in der Datenbank ändern. Das machst du komfortabel über die WP-CLI, die du entweder lokal oder auf dem Live-Server benutzt:

wp search-replace http://<Deine-Entwicklungsdomain> https://<Live-Domain>

Deployment deines Templates

Auch hier gibt es wieder tausend Wege – ich zeige dir einen davon. Dieser Weg führt uns über Deployer. Wie der Name schon sagt, hilft es dir, deine PHP-Projekte zu deployen. Eine Alternative ist z.B. Capistrano – bei Deployer hast du aber den PHP-Vorteil und kannst es über composer installieren. Also machen wir das mal:

composer require deployer/deployer

Du kannst Deployer auch global installieren (siehe Webseite) – meine persönliche Vorliebe ist es allerdings, Tools, wenn möglich, Projekt- und Versionsbezogen zu installieren. Vielleicht ist das eine Art Fetisch; wenn, dann ein nützlicher.

In der Anleitung steht, dass man das Projekt über Deployer initialisieren kann. Aber wir lesen keine Anleitungen (außer diese hier) und erstellen die nötige Datei, nämlich deploy.php, direkt selbst (im Theme-Root, also meintheme).

<?php
namespace Deployer;

require 'recipe/common.php';
require 'recipe/rsync.php';

////////////////////////////////////////////////////////////////////////
// exec ssh-agent bash
// ssh-add <Dein Key>
// php vendor/bin/dep deploy -vvv
////////////////////////////////////////////////////////////////////////
host(<DOMAIN>)
	->user(<SFTP USERY)
	->port(<PORT>)
	->set('release_path', "<ABSOLUTER PFAD AUF DEM SERVER ZU DEINEM THEME PFAD (endet also mit 'meintheme')>")
;



// Ein paar andere Config-Dinge
set('deploy_path', "{{release_path}}");
set('rsync_src', __DIR__);
set('rsync_dest', "{{release_path}}");

set('rsync',[
    'exclude'      => [
        '.git',
        'node_modules',
        'src',
        'deploy.php',
        'vendor',
    ],
    'exclude-file' => false,
    'include'      => [],
    'include-file' => false,
    'filter'       => [],
    'filter-file'  => false,
    'filter-perdir'=> false,
    'flags'        => 'rz', // Recursive, with compress
    'options'      => [],
    'timeout'      => 60,
]);


task('deploy', [
	'rsync',
	'deploy:vendors'
])->desc('Deploy your project');

Das benötigt jetzt ein bisschen Erklärung. Aaaaaaaaaaaaaaaaaaaaalso…

Die aktuellen Template-Dateien kopieren wir über rsync von unserem lokalen Rechner auf den Live-Server. Dazu benötigen wir einen SFTP-Account (Verfizierung in diesem Beispiel über einen Public-Key). Es werden aber alle nicht alle Verzeichnisse kopiert – du kannst diese Liste natürlich selber noch erweitern.

Anschließend wird auf dem Live-Server ein composer install ausgeführt. Das ist dann nötig, wenn du in deinem Template PHP-Komponenten nutzen willst – dazu komme ich später noch.

Das war’s! Du deployst dein Template dann mit:

php vendor/bin/dep deploy -vvv

Vorher musst du u.U. deinen SSH-Key per Agent laden – die Befehle dazu siehst du im Kommentar.

Achtung: Wir müssen unsere Dateien noch absichern! Doch zunächst ein kleiner Einschub.

Composer-Packages im Template nutzen

Aufgabe: Dein Template soll immer eine neue UUID generieren. Was nun? Es gibt eine schöne Lib von ramsey, die du dafür nutzen kannst. Die installieren wir mal (im Theme-Root):

composer require ramsey/uuid

Die Funktion wird dann in der index.php eingebunden:

“Aber hey”, sagst du. “Wo packen wir denn den Autoloader vom Composer hin, damit meine Klassen erkannt werden?”. Gute Frage! In die functions.php:

Wow! Sieht aus wie eine richtige Webseite! Der Kunde, der das möchte, hat Ahnung!

Template-Verzeichnis ein bisschen absichern

Wenn du dir die Dateien im Theme-Verzeichnis mal genau anguckst, fällt dir auf, dass alles, was vom Browser geladen werden kann, eigentlich nur im Ordner assets liegt (z.B. deine Bilder, deshalb das Unterverzeichnis img).

Da macht es doch Sinn, Apache2 zu sagen: Alles, was im Theme-Ordner liegt, ist für den Nutzer verboten! Nur assets nicht!

Deshalb legen wir zwei .htaccess-Dateien an. Die erste im Theme-Root:

Order deny,allow
Deny from all

Und die zweite im Assets-Ordner:

Allow from all

Damit ist es nicht so schlimm, falls man mal vergisst, eine Datei beim rsyncen zu excluden.

Fazit

Du solltest jetzt einen ganzen Koffer voller neuer Werkzeuge haben, um dein nächstes Projekt mit WordPress zu starten und weiter zu lernen. Es gibt noch viel zu tun!

Björn Falszewski
5. Juni 2020
Disclaimer
Alle meine Artikel entstehen mit bestem Wissen und Gewissen, sind aber nicht perfekt und sollten immer nur als Ausgangspunkt für deine eigenen Recherchen bilden.

Sollte dir etwas Fehlerhaftes auffallen, freue ich mich über deine Nachricht!