Gulp für WordPress: Erstellen der Tasks

Avatar of Ali Alaa
Ali Alaa am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Dies ist der zweite Beitrag in einer zweiteiligen Serie über die Erstellung eines Gulp-Workflows für die Entwicklung von WordPress-Themes. Teil eins konzentrierte sich auf die anfängliche Installation, Einrichtung und Organisation von Gulp in einem WordPress-Theme-Projekt. Dieser Beitrag geht tief in die Tasks ein, die Gulp ausführen wird, indem er aufschlüsselt, was jeder Task tut und wie man sie anpasst, um die Theme-Entwicklung zu optimieren.

Nachdem wir im ersten Teil dieser Serie die Einrichtung eines WordPress-Theme-Projekts mit installiertem Gulp abgeschlossen haben, ist es an der Zeit, sich mit den Tasks zu befassen, die es für uns erledigen soll, während wir das Theme entwickeln. In diesem Beitrag werden wir sehr praktisch und bereiten uns auf das Schreiben von Code vor!

Artikelserie

  1. Ersteinrichtung
  2. Erstellen der Tasks (Dieser Beitrag)

Erstellen des Style-Tasks

Beginnen wir damit, src/bundle.scss von Sass nach CSS zu kompilieren, dann die CSS-Ausgabe für den Produktionsmodus zu minifizieren und die fertige Datei bundle.css in das Verzeichnis dist zu legen.

Wir werden ein paar Gulp-Plugins verwenden, um die Hauptarbeit zu erledigen. Wir werden gulp-sass zum Kompilieren und gulp-clean-css zum Minifizieren verwenden. Dann wird uns gulp-if erlauben, Funktionen bedingt auszuführen, was in unserem Fall prüft, ob wir uns im Produktions- oder Entwicklungsmodus befinden, bevor diese Tasks ausgeführt werden, und dann entsprechend handelt.

Wir können alle drei Plugins auf einmal installieren

npm install --save-dev gulp-sass gulp-clean-css gulp-if

Stellen wir sicher, dass wir etwas in unserer Datei bundle.scss haben, damit wir die Tasks testen können

$colour: #f03;

body {
  background-color: $colour;
}

In Ordnung, zurück zum Gulpfile, um die Plugins zu importieren und den Task zu definieren, der sie ausführt

import { src, dest } from 'gulp';
import yargs from 'yargs';
import sass from 'gulp-sass';
import cleanCss from 'gulp-clean-css';
import gulpif from 'gulp-if';
const PRODUCTION = yargs.argv.prod;

export const styles = () => {
  return src('src/scss/bundle.scss')
    .pipe(sass().on('error', sass.logError))
    .pipe(gulpif(PRODUCTION, cleanCss({compatibility:'ie8'})))
    .pipe(dest('dist/css'));
}

Lassen Sie uns diesen Code durchgehen, um zu erklären, was passiert.

  • Die Funktionen src und dest werden von Gulp importiert. src liest die Datei, die Sie als Argument übergeben, und gibt einen Node-Stream zurück.
  • Wir ziehen yargs heran, um unser Flag zu erstellen, das die Tasks zwischen Entwicklungs- und Produktionsmodus trennt.
  • Die drei Plugins werden aktiviert.
  • Das Flag PRODUCTION wird definiert und im Befehl prod gehalten.
  • Wir definieren styles als Task-Namen, den wir verwenden werden, um diese Tasks in der Befehlszeile auszuführen.
  • Wir geben dem Task an, welche Datei verarbeitet werden soll (bundle.scss) und wo sie sich befindet (src/scss/bundle.scss).
  • Wir erstellen „Pipes“, die als Plugins dienen und ausgeführt werden, wenn der Befehl styles ausgeführt wird. Diese Pipes laufen in der Reihenfolge, in der sie geschrieben sind: Sass in CSS umwandeln, CSS minifizieren (wenn wir uns im Produktionsmodus befinden) und die resultierende CSS-Datei in das Verzeichnis dist/css legen.

Führen Sie nun gulp styles in der Befehlszeile aus und sehen Sie, dass eine neue CSS-Datei zu Ihrem CSS-Verzeichnis dist/css hinzugefügt wurde.

Führen Sie nun gulp styles --prod aus. Das Gleiche geschieht, aber nun ist diese CSS-Datei für den Produktionsgebrauch minifiziert.

Nun können, vorausgesetzt, Sie haben ein funktionierendes WordPress-Theme mit header.php und footer.php, die CSS-Datei (sowie JavaScript-Dateien, wenn wir zu diesen Tasks kommen) sicher eingebunden werden, wahrscheinlich in Ihrer functions.php-Datei

function _themename_assets() {
  wp_enqueue_style( '_themename-stylesheet', get_template_directory_uri() . '/dist/css/bundle.css', array(), '1.0.0', 'all' );
}
add_action('wp_enqueue_scripts', '_themename_assets');

Das ist alles gut, aber wir können unseren Style-Befehl noch besser machen.

Versuchen Sie zum Beispiel, den Body auf der Homepage mit dem aktiven WordPress-Theme zu inspizieren. Die Stile, die wir hinzugefügt haben, sollten dort sein

Wie Sie sehen können, steht dort, dass unser Stil von bundle.css kommt, was stimmt. Es wäre jedoch viel besser, wenn der Name der ursprünglichen SCSS-Datei hier anstelle dessen angezeigt würde, zu unseren Entwicklungszwecken — das macht es so viel einfacher, Code zu lokalisieren, insbesondere wenn wir mit einer Menge von Partial-Dateien arbeiten. Hier kommen Source Maps ins Spiel. Dies wird den Speicherort unserer Stile in DevTools detailliert beschreiben. Um dieses Problem weiter zu veranschaulichen, fügen wir auch etwas SCSS innerhalb von src/scss/components/slider.scss hinzu und importieren diese Datei dann in bundle.scss.

//src/scss/components/slider.scss
body {
  background-color: aqua;
}
//src/scss/bundle.scss
@import './components/slider.scss';
$colour: #f03;
body {
  background-color: $colour;
}

Führen Sie gulp styles erneut aus, um Ihre Dateien neu zu kompilieren. Ihr Inspector sollte dann so aussehen

Der DevTools-Inspector zeigt an, dass beide Stile von bundle.css kommen. Aber wir möchten, dass stattdessen die Originaldatei angezeigt wird (d. h. bundle.scss und slider.scss). Fügen wir dies also unserer Wunschliste an Verbesserungen hinzu, bevor wir zum Code kommen.

Das andere, was wir wollen werden, ist, dass Vendor-Präfixe für uns gehandhabt werden. Es gibt nichts Schlimmeres, als diese selbst schreiben und verwalten zu müssen, und Autoprefixer ist das Tool, das das für uns tun kann.

Und damit Autoprefixer seine Magie wirken kann, benötigen wir das PostCSS-Plugin.

OK, das summiert sich auf drei weitere Plugins und Tasks, die wir ausführen müssen. Lassen Sie uns alle drei installieren

npm install --save-dev gulp-sourcemaps gulp-postcss autoprefixer

Also wird gulp-sourcemaps offensichtlich für Sourcemaps verwendet. gulp-postcss und autoprefixer werden verwendet, um unseren CSS Autoprefixing hinzuzufügen. Postcss ist ein berühmtes Plugin zur Transformation von CSS-Dateien und Autoprefixer ist nur ein Plugin für PostCSS. Sie können hier mehr über andere Dinge lesen, die Sie mit PostCSS tun können hier.

Importieren wir nun ganz oben unsere Plugins in das Gulpfile

import postcss from 'gulp-postcss';
import sourcemaps from 'gulp-sourcemaps';
import autoprefixer from 'autoprefixer';

Und dann aktualisieren wir den Task, um diese Plugins zu verwenden

export const styles = () => {
  return src('src/scss/bundle.scss')
    .pipe(gulpif(!PRODUCTION, sourcemaps.init()))
    .pipe(sass().on('error', sass.logError))
    .pipe(gulpif(PRODUCTION, postcss([ autoprefixer ])))
    .pipe(gulpif(PRODUCTION, cleanCss({compatibility:'ie8'})))
    .pipe(gulpif(!PRODUCTION, sourcemaps.write()))
    .pipe(dest('dist/css'));
}

Um das Sourcemaps-Plugin zu verwenden, müssen wir einige Schritte befolgen

  1. Zuerst initialisieren wir das Plugin mit sourcemaps.init().
  2. Als Nächstes leiten wir alle Plugins weiter, die wir zuordnen möchten.
  3. Schließlich erstellen wir die Source-Map-Datei, indem wir sourcemaps.write() aufrufen, kurz bevor wir den Bundle an das Ziel schreiben.

Beachten Sie, dass alle Plugins, die zwischen sourcemaps.init() und sourcemaps.write() weitergeleitet werden, mit gulp-sourcemaps kompatibel sein müssen. In unserem Fall verwenden wir sass(), postcss() und cleanCss() und alle sind kompatibel mit Sourcemaps.

Beachten Sie, dass wir den Autoprefixer nur hinter dem Produktionsflag ausführen, da es während der Entwicklung keinen wirklichen Bedarf für all diese Vendor-Präfixe gibt.

Führen wir nun gulp styles aus, ohne das Produktionsflag. Hier ist die Ausgabe in bundle.css

body {
  background-color: aqua; }
body {
  background-color: #f03; }
/*#sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVuZGxlLmNzcyIsInNvdXJjZXMiOlsiYnVuZGxlLnNjc3MiLCJjb21wb25lbnRzL3NsaWRlci5zY3NzIl0sInNvdXJjZXNDb250ZW50IjpbIkBpbXBvcnQgJy4vY29tcG9uZW50cy9zbGlkZXIuc2Nzcyc7XG5cbiRjb2xvdXI6ICNmMDM7XG5ib2R5IHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAkY29sb3VyO1xufVxuOjpwbGFjZWhvbGRlciB7XG4gICAgY29sb3I6IGdyYXk7XG59IiwiYm9keSB7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogYXF1YTtcbn0iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFDQUEsQUFBQSxJQUFJLENBQUM7RUFDRCxnQkFBZ0IsRUFBRSxJQUFJLEdBQ3pCOztBRENELEFBQUEsSUFBSSxDQUFDO0VBQ0QsZ0JBQWdCLEVBRlgsSUFBSSxHQUdaOztBQUNELEFBQUEsYUFBYSxDQUFDO0VBQ1YsS0FBSyxFQUFFLElBQUksR0FDZCJ9 */#

Der zusätzliche Text unten sind Source Maps. Nun, wenn wir die Seite in DevTools inspizieren, sehen wir

Schön! Nun zur Produktionsumgebung

gulp styles --prod

Überprüfen Sie die DevTools anhand von Stilregeln, die Präfixe benötigen (z. B. display: grid;) und stellen Sie sicher, dass diese alle vorhanden sind. Und stellen Sie sicher, dass Ihre Datei auch minifiziert ist.

Eine letzte Anmerkung zu diesem Task. Nehmen wir an, wir möchten mehrere CSS-Bundles erstellen: eines für Front-End-Stile und eines für WordPress-Admin-Stile. Wir können eine neue Datei admin.scss im Verzeichnis src/scss erstellen und ein Array von Pfaden im Gulpfile übergeben

export const styles = () => {
  return src(['src/scss/bundle.scss', 'src/scss/admin.scss'])
    .pipe(gulpif(!PRODUCTION, sourcemaps.init()))
    .pipe(sass().on('error', sass.logError))
    .pipe(gulpif(PRODUCTION, postcss([ autoprefixer ])))
    .pipe(gulpif(PRODUCTION, cleanCss({compatibility:'ie8'})))
    .pipe(gulpif(!PRODUCTION, sourcemaps.write()))
    .pipe(dest('dist/css'));
}

Jetzt haben wir bundle.css und admin.css im Verzeichnis dist/css. Stellen Sie einfach sicher, dass Sie alle neuen Bundles, die auf diese Weise getrennt werden, ordnungsgemäß einbinden.

Erstellen des Watch-Tasks

Nun, als Nächstes kommt der Watch-Task, der unser Leben so viel einfacher macht, indem er nach Dateien sucht, bei denen Änderungen gespeichert wurden, und dann Tasks für uns ausführt, ohne dass wir sie in der Befehlszeile selbst aufrufen müssen. Wie großartig ist das?

Wie wir es für den Styles-Task getan haben

import { src, dest, watch } from 'gulp';

Wir werden den neuen Task watchForChanges nennen

export const watchForChanges = () => {
  watch('src/scss/**/*.scss', styles);
}

Beachten Sie, dass watch als Name nicht verfügbar ist, da wir bereits eine Variable verwenden, die ihn verwendet.

Lassen Sie uns nun gulp watchForChanges ausführen. Die Befehlszeile wird eine ständige Überwachung auf Änderungen in allen .scss-Dateien im Verzeichnis src/scss durchführen. Und wenn diese Änderungen auftreten, wird der Styles-Task sofort ohne weiteres Zutun von unserer Seite ausgeführt.

Beachten Sie, dass src/scss/**/*.scss ein Glob-Muster ist. Das bedeutet im Grunde, dass dieser String jede .scss-Datei im Verzeichnis src/scss oder in einem Unterordner davon abgleicht. Im Moment überwachen wir nur .scss-Dateien und führen den Styles-Task aus. Später werden wir seinen Umfang erweitern, um auch andere Dateien zu überwachen.

Erstellen des Images-Tasks

Wie wir bereits erwähnt haben, komprimiert der Images-Task Bilder in src/images und verschiebt sie dann nach dist/images. Installieren wir ein Gulp-Plugin, das für die Komprimierung von Bildern zuständig ist

npm install --save-dev gulp-imagemin

Importieren Sie dieses Plugin nun oben im Gulpfile

import imagemin from 'gulp-imagemin';

Und schließlich schreiben wir unseren Images-Task

export const images = () => {
  return src('src/images/**/*.{jpg,jpeg,png,svg,gif}')
    .pipe(gulpif(PRODUCTION, imagemin()))
    .pipe(dest('dist/images'));
}

Wir übergeben der src()-Funktion ein Glob, das alle .jpg-, .jpeg-, .png-, .svg- und .gif-Bilder im Verzeichnis src/images abgleicht. Dann führen wir das imagemin-Plugin aus, aber nur für die Produktion. Das Komprimieren von Bildern kann einige Zeit dauern und ist während der Entwicklung nicht notwendig, daher können wir es aus dem Entwicklungsfluss herauslassen. Schließlich legen wir die komprimierten Versionen der Bilder in dist/images ab.

Nun werden alle Bilder, die wir in src/images ablegen, kopiert, wenn wir gulp images ausführen. Wenn wir jedoch gulp images --prod ausführen, werden die Bilder sowohl komprimiert als auch kopiert.

Als letztes müssen wir unseren watchForChanges-Task modifizieren, um Bilder in seine Überwachung einzuschließen

export const watchForChanges = () => {
  watch('src/scss/**/*.scss', styles);
  watch('src/images/**/*.{jpg,jpeg,png,svg,gif}', images);
}

Nun, vorausgesetzt, der Task watchForChanges läuft, wird der Images-Task automatisch ausgeführt, wann immer wir ein Bild in den Ordner src/images einfügen. Er erledigt die gesamte Arbeit für uns!

Wichtig: Wenn der Task watchForChanges läuft und das Gulpfile modifiziert wird, muss er gestoppt und neu gestartet werden, damit die Änderungen wirksam werden.

Erstellen des Copy-Tasks

Wahrscheinlich waren Sie schon in Situationen, in denen Sie Dateien erstellt, sie verarbeitet und dann manuell die Produktionsdateien greifen und an den richtigen Ort verschieben mussten. Nun, wie wir im Images-Task gesehen haben, können wir die Kopierfunktion verwenden, um dies für uns zu tun und zu verhindern, dass falsche Dateien verschoben werden.

export const copy = () => {
  return src(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'])
    .pipe(dest('dist'));
}

Versuchen Sie, das Array von Pfaden, das an src() übergeben wird, sorgfältig zu lesen. Wir weisen Gulp an, alle Dateien und Ordner innerhalb von src (src/**/*) abzugleichen, mit Ausnahme der Ordner für Bilder, JS und SCSS (!src/{images,js,scss}) und aller Dateien oder Unterordner darin (!src/{images,js,scss}/**/*).

Wir möchten, dass unser Watch-Task auch auf diese Änderungen achtet, also fügen wir ihn hinzu

export const watchForChanges = () => {
  watch('src/scss/**/*.scss', styles);
  watch('src/images/**/*.{jpg,jpeg,png,svg,gif}', images);
  watch(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'], copy);
}

Versuchen Sie, eine beliebige Datei oder einen Ordner zum src-Verzeichnis hinzuzufügen, und er sollte in das Verzeichnis /dist kopiert werden. Wenn wir jedoch eine Datei oder einen Ordner innerhalb von /images, /js oder /scss hinzufügen würden, würde er ignoriert werden, da wir diese Ordner bereits in separaten Tasks behandeln.

Wir haben hier aber immer noch ein Problem. Versuchen Sie, die hinzugefügte Datei zu löschen, und sie wird nicht gelöscht. Unser Task behandelt nur das Kopieren. Dieses Problem könnte auch für unsere Ordner /images, /js und /scss auftreten. Wenn wir alte Bilder oder JavaScript- und CSS-Bundles haben, die aus dem src-Ordner entfernt wurden, werden sie nicht aus dem dist-Ordner entfernt. Daher ist es eine gute Idee, den dist-Ordner jedes Mal vollständig zu bereinigen, wenn wir mit der Entwicklung oder dem Erstellen eines Themes beginnen. Und das werden wir im nächsten Task tun.

Zusammensetzen von Tasks für Entwicklung und Build

Installieren wir nun ein Paket, das für das Löschen des dist-Ordners zuständig ist. Dieses Paket heißt del

npm install --save-dev del

Importieren Sie es oben

import del from 'del';

Erstellen Sie einen Task, der den dist-Ordner löscht

export const clean = () => {
  return del(['dist']);
}

Beachten Sie, dass del ein Promise zurückgibt. Daher müssen wir die Funktion cb() nicht aufrufen. Die Verwendung der neuen JavaScript-Features ermöglicht es uns, dies umzugestalten zu

export const clean = () => del(['dist']);

Der Ordner sollte nun beim Ausführen von gulp clean gelöscht werden. Als Nächstes müssen wir den dist-Ordner löschen, die Tasks für Bilder, Kopieren und Stile ausführen und schließlich bei jedem Entwicklungsstart auf Änderungen warten. Dies kann durch Ausführen von gulp clean, gulp images, gulp styles, gulp copy und dann gulp watch erfolgen. Aber natürlich werden wir das nicht manuell tun. Gulp hat ein paar Funktionen, die uns helfen, Tasks zusammenzustellen. Also importieren wir diese Funktionen von Gulp

import { src, dest, watch, series, parallel } from 'gulp';

series() nimmt einige Tasks als Argumente und führt sie nacheinander aus. Und parallel() nimmt Tasks als Argumente und führt sie alle gleichzeitig aus. Lassen Sie uns zwei neue Tasks erstellen, indem wir die bereits erstellten Tasks zusammensetzen

export const dev = series(clean, parallel(styles, images, copy), watchForChanges)
export const build = series(clean, parallel(styles, images, copy))
export default dev;

Beide Tasks werden genau dasselbe tun: den dist-Ordner bereinigen, dann werden Stile, Bilder und Kopieren parallel ausgeführt, sobald die Bereinigung abgeschlossen ist. Wir werden auch auf Änderungen warten für den dev (kurz für develop) Task, nach diesen parallelen Tasks. Zusätzlich exportieren wir dev als Standard-Task.

Beachten Sie, dass wir beim Ausführen des Build-Tasks möchten, dass unsere Dateien minifiziert, Bilder komprimiert und so weiter werden. Wenn wir diesen Befehl ausführen, müssen wir also das Flag --prod hinzufügen. Da dies beim Ausführen des Build-Tasks leicht vergessen werden kann, können wir npm-Skripte verwenden, um Aliase für die dev- und build-Befehle zu erstellen. Gehen wir zu package.json und im Skript-Feld finden wir wahrscheinlich etwas wie das hier

"scripts": {
  "test": "echo "Error: no test specified" && exit 1"
}

Ändern wir es zu diesem

"scripts": {
  "start": "gulp",
  "build": "gulp build --prod"
},

Dies ermöglicht uns, npm run start in der Befehlszeile auszuführen, was zum Skriptfeld geht und den Befehl findet, der zu start gehört. In unserem Fall wird start gulp ausführen und gulp wird den Standard-Gulp-Task ausführen, der dev ist. Ebenso wird npm run build gulp build --prod ausführen. Auf diese Weise können wir das --prod-Flag vollständig vergessen und auch vergessen, die Gulp-Tasks mit dem Befehl gulp auszuführen. Natürlich werden unsere dev- und build-Befehle später mehr tun, aber vorerst haben wir die Grundlage, mit der wir während des Rests der Tasks arbeiten werden.

Erstellen des Scripts-Tasks

Wie erwähnt, benötigen wir zum Bündeln unserer JavaScript-Dateien einen Modul-Bundler. webpack ist die bekannteste Option, aber es ist kein Gulp-Plugin. Vielmehr ist es ein eigenständiges Plugin mit einer komplett separaten Einrichtung und Konfigurationsdatei. Glücklicherweise gibt es ein Paket namens webpack-stream, das uns hilft, webpack innerhalb eines Gulp-Tasks zu verwenden. Installieren wir also dieses Paket

npm install --save-dev webpack-stream

webpack arbeitet mit sogenannten Loadern. Loader sind für die Transformation von Dateien in webpack zuständig. Und um neuere JavaScript-Versionen in ES5 zu transformieren, benötigen wir einen Loader namens babel-loader. Wir benötigen auch @babel/preset-env, aber das haben wir bereits zuvor installiert

npm install --save-dev babel-loader

Importieren wir webpack-stream ganz oben im Gulpfile

import webpack from 'webpack-stream';

Um unseren Task zu testen, fügen wir auch diese Zeilen in src/js/bundle.js und src/js/components/slider.js ein

// bundle.js
import './components/slider';
console.log('bundle');


// slider.js
console.log('slider')

Unser Scripts-Task wird schließlich so aussehen

export const scripts = () => {
  return src('src/js/bundle.js')
  .pipe(webpack({
    module: {
      rules: [
        {
          test: /\.js$/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: []
            }
          }
        }
      ]
    },
    mode: PRODUCTION ? 'production' : 'development',
    devtool: !PRODUCTION ? 'inline-source-map' : false,
    output: {
      filename: 'bundle.js'
    },
  }))
  .pipe(dest('dist/js'));
}

Lassen Sie uns das ein wenig aufschlüsseln

  • Zuerst geben wir bundle.js als unseren Einstiegspunkt in der src()-Funktion an.
  • Dann leiten wir das webpack-Plugin weiter und geben einige Optionen dafür an.
  • Das Feld rules in der Option module teilt webpack mit, welche Loader verwendet werden sollen, um unsere Dateien zu transformieren. In unserem Fall müssen wir JavaScript-Dateien mit dem babel-loader transformieren.
  • Die Option mode ist entweder production oder development. Für die Entwicklung wird webpack die ausgegebene JavaScript-Bundle nicht minifizieren, aber für die Produktion schon. Daher benötigen wir kein separates Gulp-Plugin zur Minifizierung von JavaScript, da webpack dies je nach unserer PRODUCTION-Konstante tun kann.
  • Die Option devtool fügt Source Maps hinzu, aber nicht in der Produktion. In der Entwicklung werden wir jedoch inline-source-maps verwenden. Diese Art von Source Maps ist am genauesten, kann aber etwas langsam bei der Erstellung sein. Wenn Sie sie zu langsam finden, schauen Sie sich die anderen Optionen hier an. Sie werden nicht so genau sein wie inline-source-maps, aber sie können ziemlich schnell sein.
  • Schließlich kann die Option output einige Informationen über die Ausgabedatei angeben. In unserem Fall müssen wir nur den Dateinamen ändern. Wenn wir den Dateinamen nicht angeben, generiert webpack ein Bundle mit einem Hash als Dateinamen. Lesen Sie hier mehr über diese Optionen hier.

Nun sollten wir gulp scripts und gulp scripts --prod ausführen und sehen können, dass eine bundle.js-Datei in dist/js erstellt wird. Stellen Sie sicher, dass Minifizierung und Source Maps ordnungsgemäß funktionieren. Lassen Sie uns nun unsere JavaScript-Datei in WordPress einbinden, was in der functions.php-Datei des Themes oder wo auch immer Sie Ihre Funktionen schreiben, geschehen kann.

<?php
function _themename_assets() {
  wp_enqueue_style( '_themename-stylesheet', get_template_directory_uri() . '/dist/css/bundle.css', array(), '1.0.0', 'all' );
  
  wp_enqueue_script( '_themename-scripts', get_template_directory_uri() . '/dist/js/bundle.js', array(), '1.0.0', true );
}
add_action('wp_enqueue_scripts', '_themename_assets');

Schauen wir uns nun die Konsole an, um zu bestätigen, dass die Source Maps korrekt funktionieren, indem wir die Datei überprüfen, von der die Konsolenprotokolle stammen

Ohne die Source Maps werden beide Protokolle von bundle.js kommen.

Was ist, wenn wir mehrere JavaScript-Bundles erstellen möchten, genau wie bei den Stilen? Lassen Sie uns eine Datei namens admin.js in src/js erstellen. Sie denken vielleicht, dass wir einfach den Einstiegspunkt in src() wie folgt in ein Array ändern können

export const scripts = () => {
  return src(['src/js/bundle.js','src/js/admin.js'])
  .
  .
}

Das wird jedoch nicht funktionieren. webpack funktioniert etwas anders als normale Gulp-Plugins. Was wir oben getan haben, erstellt immer noch eine Datei namens bundle.js im Verzeichnis dist. webpack-stream bietet ein paar Lösungen für die Erstellung mehrerer Einstiegspunkte. Ich habe mich entschieden, die zweite Lösung zu verwenden, da sie uns ermöglicht, mehrere Bundles zu erstellen, indem wir ein Array an src() übergeben, genau wie wir es für die Stile getan haben. Dies erfordert die Installation von vinyl-named

npm install --save-dev vinyl-named

Importieren Sie es

import named from 'vinyl-named';

…und aktualisieren Sie dann den Scripts-Task

export const scripts = () => {
  return src(['src/js/bundle.js','src/js/admin.js'])
  .pipe(named())
  .pipe(webpack({
    module: {
      rules: [
        {
          test: /\.js$/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env']
            }
          }
        }
      ]
    },
    mode: PRODUCTION ? 'production' : 'development',
    devtool: !PRODUCTION ? 'inline-source-map' : false,
    output: {
      filename: '[name].js'
    },
  }))
  .pipe(dest('dist/js'));
}

Der einzige Unterschied ist, dass wir jetzt ein Array in src() haben. Dann leiten wir das named-Plugin vor webpack weiter, was uns erlaubt, einen [name]-Platzhalter im output-Feld filename zu verwenden, anstatt den Dateinamen direkt fest zu codieren. Nach dem Ausführen des Tasks erhalten wir zwei Bundles in dist/js.

Ein weiteres Feature, das webpack bietet, ist die Verwendung von Bibliotheken aus externen Quellen anstatt sie in das finale Bundle zu bündeln. Nehmen wir zum Beispiel an, Ihr Bundle muss jQuery verwenden. Sie können npm install jquery --save ausführen und es dann in Ihr Bundle importieren import $ from 'jquery'. Dies erhöht jedoch die Bundle-Größe und in manchen Fällen haben Sie jQuery bereits über ein CDN geladen oder — im Fall von WordPress — es existiert als Abhängigkeit, wie hier

wp_enqueue_script( '_themename-scripts', get_template_directory_uri() . '/dist/js/bundle.js', array('jquery'), '1.0.0', true );

Also, nun wird WordPress jQuery mit einem normalen Skript-Tag einbinden. Wie können wir es dann mit import $ from 'jquery' in unserem Bundle verwenden? Die Antwort ist die Verwendung der externals-Option von webpack. Modifizieren wir unseren Scripts-Task, um sie hinzuzufügen

export const scripts = () => {
  return src(['src/js/bundle.js','src/js/admin.js'])
    .pipe(named())
    .pipe(webpack({
      module: {
        rules: [
          {
            test: /\.js$/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: []
            }
          }
        }
      ]
    },
    mode: PRODUCTION ? 'production' : 'development',
    devtool: !PRODUCTION ? 'inline-source-map' : false,
    output: {
      filename: '[name].js'
    },
    externals: {
      jquery: 'jQuery'
    },
  }))
  .pipe(dest('dist/js'));
}

In der externals-Option ist jquery der Schlüssel, der den Namen der Bibliothek identifiziert, die wir importieren möchten. In unserem Fall ist es import $ from 'jquery'. Und der Wert jQuery ist der Name einer globalen Variable, in der sich die Bibliothek befindet. Versuchen Sie nun, import $ from 'jquery' in das Bundle zu schreiben und jQuery mit dem $ zu verwenden — es sollte perfekt funktionieren.

Lasst uns auch auf Änderungen bei JavaScript-Dateien achten

export const watchForChanges = () => {
  watch('src/scss/**/*.scss', styles);
  watch('src/images/**/*.{jpg,jpeg,png,svg,gif}', images);
  watch(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'], copy);
  watch('src/js/**/*.js', scripts);
}

Und schließlich fügen wir unseren Scripts-Task in die dev- und build-Tasks ein

export const dev = series(clean, parallel(styles, images, copy, scripts), watchForChanges);
export const build = series(clean, parallel(styles, images, copy, scripts));

Browser-Neuladen mit Browsersync

Verbessern wir nun unseren Watch-Task, indem wir Browsersync installieren, ein Plugin, das den Browser jedes Mal neu lädt, wenn Tasks abgeschlossen sind.

npm install browser-sync gulp --save-dev

Importieren wir es wie üblich

import browserSync from "browser-sync";

Als Nächstes initialisieren wir einen Browsersync-Server und schreiben zwei neue Tasks

const server = browserSync.create();
export const serve = done => {
  server.init({
    proxy: "https:///yourFolderName" // put your local website link here
  });
  done();
};
export const reload = done => {
  server.reload();
  done();
};

Um den Browser mit Browsersync zu steuern, müssen wir einen Browsersync-Server initialisieren. Dies ist anders als ein lokaler Server, auf dem WordPress normalerweise leben würde. Der erste Task ist serve, der den Browsersync-Server startet und über die Option proxy auf unseren lokalen WordPress-Server zeigt. Der zweite Task wird einfach den Browser neu laden.

Nun müssen wir diesen Server ausführen, wenn wir unser Theme entwickeln. Wir können den Serve-Task zu den Dev-Serien-Tasks hinzufügen

export const dev = series(clean, parallel(styles, images, copy, scripts), serve, watchForChanges);

Führen Sie nun npm start aus, und der Browser sollte eine neue URL öffnen, die sich von der ursprünglichen unterscheidet. Diese URL wird von Browsersync aktualisiert. Lassen Sie uns nun den Reload-Task verwenden, um den Browser neu zu laden, sobald die Tasks abgeschlossen sind

export const watchForChanges = () => {
  watch('src/scss/**/*.scss', series(styles, reload));
  watch('src/images/**/*.{jpg,jpeg,png,svg,gif}', series(images, reload));
  watch(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'], series(copy, reload));
  watch('src/js/**/*.js', series(scripts, reload));
  watch("**/*.php", reload);
}

Wie Sie sehen, haben wir eine neue Zeile hinzugefügt, um den Reload-Task jedes Mal auszuführen, wenn sich eine PHP-Datei ändert. Wir verwenden auch series(), um zu warten, bis unsere Stile, Bilder, Skripte und Kopier-Tasks abgeschlossen sind, bevor der Browser neu geladen wird. Führen Sie nun npm start aus und ändern Sie etwas in einer Sass-Datei. Der Browser sollte automatisch neu geladen werden und die Änderungen sollten nach dem Neuladen reflektiert werden, sobald die Tasks ausgeführt wurden.

Keine CSS- oder JavaScript-Änderungen nach dem Neuladen zu sehen? Stellen Sie sicher, dass das Caching im Inspektor Ihres Browsers deaktiviert ist.

Wir können die Stile-Tasks noch weiter verbessern. Browsersync erlaubt es uns, CSS direkt auf die Seite zu injizieren, ohne den Browser überhaupt neu laden zu müssen. Und das kann durch Hinzufügen von server.stream() ganz am Ende des Stile-Tasks erreicht werden

export const styles = () => {
  return src(['src/scss/bundle.scss', 'src/scss/admin.scss'])
    .pipe(gulpif(!PRODUCTION, sourcemaps.init()))
    .pipe(sass().on('error', sass.logError))
    .pipe(gulpif(PRODUCTION, postcss([ autoprefixer ])))
    .pipe(gulpif(PRODUCTION, cleanCss({compatibility:'ie8'})))
    .pipe(gulpif(!PRODUCTION, sourcemaps.write()))
    .pipe(dest('dist/css'))
    .pipe(server.stream());
}

Nun, im Task watchForChanges müssen wir den Browser für den Stile-Task nicht mehr neu laden, also entfernen wir den Reload-Task daraus

export const watchForChanges = () => {
  watch('src/scss/**/*.scss', styles);
  .
  .
}

Stellen Sie sicher, dass Sie watchForChanges stoppen, falls er bereits läuft, und führen Sie ihn dann erneut aus. Versuchen Sie, eine beliebige Datei im scss-Ordner zu ändern, und die Änderungen sollten sofort im Browser erscheinen, ohne dass ein Neuladen erforderlich ist.

Verpacken des Themes in eine ZIP-Datei

WordPress-Themes werden in der Regel als ZIP-Datei verpackt, die direkt in der WordPress-Verwaltung installiert werden kann. Wir können einen Task erstellen, der die benötigten Theme-Dateien nimmt und sie für uns ZIP-packt. Dazu müssen wir ein weiteres Gulp-Plugin installieren: gulp-zip.

npm install --save-dev gulp-zip

Und wie immer, importieren Sie es oben

import zip from "gulp-zip";

Importieren wir auch das JSON-Objekt aus der package.json-Datei. Wir benötigen es, um den Namen des Pakets zu erhalten, der auch der Name unseres Themes ist

import info from "./package.json";

Nun schreiben wir unseren Task

export const compress = () => {
return src([
  "**/*",
  "!node_modules{,/**}",
  "!bundled{,/**}",
  "!src{,/**}",
  "!.babelrc",
  "!.gitignore",
  "!gulpfile.babel.js",
  "!package.json",
  "!package-lock.json",
  ])
  .pipe(zip(`${info.name}.zip`))
  .pipe(dest('bundled'));
};

Wir übergeben src() die Dateien und Ordner, die wir komprimieren müssen, was im Grunde alle Dateien und Ordner sind (**/), außer einigen spezifischen Dateitypen, die mit ! vorangestellt sind. Als Nächstes leiten wir das gulp-zip-Plugin weiter und benennen die Datei nach dem Namen des Themes aus der package.json-Datei (info.name). Das Ergebnis ist eine frische ZIP-Datei in einem neuen Ordner namens bundled.

Versuchen Sie, gulp compress auszuführen und stellen Sie sicher, dass alles funktioniert. Öffnen Sie die generierte ZIP-Datei und stellen Sie sicher, dass sie nur die für den Betrieb des Themes benötigten Dateien und Ordner enthält.

Normalerweise müssen wir die Dateien jedoch erst *nachdem* die Theme-Dateien erstellt wurden, zippen. Fügen wir also den Komprimierungs-Task zum Build-Task hinzu, damit er nur ausgeführt wird, wenn wir ihn brauchen

export const build = series(clean, parallel(styles, images, copy, scripts), compress);

Das Ausführen von npm run build sollte nun alle unsere Tasks im Produktionsmodus ausführen.

Ersetzen des Platzhalter-Präfixes in der ZIP-Datei

Ein Schritt, den wir vor dem Zippen unserer Dateien ausführen müssen, ist das Scannen und Ersetzen des themename-Platzhalters durch den Theme-Namen, den wir verwenden möchten. Wie Sie vielleicht erraten haben, gibt es tatsächlich ein Gulp-Plugin, das dies für uns tut, genannt gulp-replace.

npm install --save-dev gulp-replace

Dann importieren Sie es

import replace from "gulp-replace";

Wir möchten, dass dieser Task unmittelbar vor dem Zippen unserer Dateien ausgeführt wird. Modifizieren wir also den Komprimierungs-Task, indem wir ihn an die richtige Stelle einfügen

export const compress = () => {
return src([
    "**/*",
    "!node_modules{,/**}",
    "!bundled{,/**}",
    "!src{,/**}",
    "!.babelrc",
    "!.gitignore",
    "!gulpfile.babel.js",
    "!package.json",
    "!package-lock.json",
  ])
  .pipe(replace("_themename", info.name))
  .pipe(zip(`${info.name}.zip`))
  .pipe(dest('bundled'));
};

Versuchen Sie nun, das Theme mit npm run build zu bauen und entpacken Sie dann die Datei im gebündelten Ordner. Öffnen Sie eine beliebige PHP-Datei, in der der Platzhalter _themename möglicherweise verwendet wurde, und stellen Sie sicher, dass er durch den tatsächlichen Theme-Namen ersetzt wurde.

Es gibt einen Stolperstein, auf den ich beim Arbeiten mit dem Replace-Plugin gestoßen bin. Wenn sich ZIP-Dateien im Theme befinden (z. B. Sie bündeln WordPress-Plugins innerhalb Ihres Themes), werden sie beim Durchlaufen des Replace-Plugins beschädigt. Dies kann durch Ignorieren von ZIP-Dateien mithilfe einer gulp-if-Anweisung gelöst werden

.pipe(
  gulpif(
    file => file.relative.split(".").pop() !== "zip",
    replace("_themename", info.name)
  )
)

Erstellen einer POT-Datei

Übersetzung ist eine große Sache in der WordPress-Community. Lassen Sie uns für unsere letzte Aufgabe alle unsere PHP-Dateien durchgehen und eine POT-Datei generieren, die für die Übersetzung verwendet wird. Glücklicherweise haben wir auch ein Gulp-Plugin dafür

npm install --save-dev gulp-wp-pot

Und natürlich importieren Sie es

import wpPot from "gulp-wp-pot";

Hier ist unsere letzte Aufgabe

export const pot = () => {
  return src("**/*.php")
  .pipe(
      wpPot({
        domain: "_themename",
        package: info.name
      })
    )
  .pipe(dest(`languages/${info.name}.pot`));
};

Wir möchten, dass die POT-Datei jedes Mal generiert wird, wenn wir das Theme erstellen

export const build = series(clean, parallel(styles, images, copy, scripts), pot, compress);

Zusammenfassung

Hier ist die vollständige Gulpfile, einschließlich aller Aufgaben, die wir in diesem Beitrag behandelt haben

import { src, dest, watch, series, parallel } from 'gulp';
import yargs from 'yargs';
import sass from 'gulp-sass';
import cleanCss from 'gulp-clean-css';
import gulpif from 'gulp-if';
import postcss from 'gulp-postcss';
import sourcemaps from 'gulp-sourcemaps';
import autoprefixer from 'autoprefixer';
import imagemin from 'gulp-imagemin';
import del from 'del';
import webpack from 'webpack-stream';
import named from 'vinyl-named';
import browserSync from "browser-sync";
import zip from "gulp-zip";
import info from "./package.json";
import replace from "gulp-replace";
import wpPot from "gulp-wp-pot";
  const PRODUCTION = yargs.argv.prod;
  const server = browserSync.create();
  export const serve = done => {
    server.init({
      proxy: "https://:8888/starter"
    });
    done();
  };
  export const reload = done => {
    server.reload();
    done();
  };
  export const clean = () => del(['dist']);
    
  export const styles = () => {
  return src(['src/scss/bundle.scss', 'src/scss/admin.scss'])
    .pipe(gulpif(!PRODUCTION, sourcemaps.init()))
    .pipe(sass().on('error', sass.logError))
    .pipe(gulpif(PRODUCTION, postcss([ autoprefixer ])))
    .pipe(gulpif(PRODUCTION, cleanCss({compatibility:'ie8'})))
    .pipe(gulpif(!PRODUCTION, sourcemaps.write()))
    .pipe(dest('dist/css'))
    .pipe(server.stream());
  }
  export const images = () => {
  return src('src/images/**/*.{jpg,jpeg,png,svg,gif}')
    .pipe(gulpif(PRODUCTION, imagemin()))
    .pipe(dest('dist/images'));
  }
  export const copy = () => {
    return src(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'])
    .pipe(dest('dist'));
  }
    export const scripts = () => {
      return src(['src/js/bundle.js','src/js/admin.js'])
      .pipe(named())
      .pipe(webpack({
        module: {
        rules: [
          {
            test: /\.js$/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: []
                }
              }
            }
          ]
        },
        mode: PRODUCTION ? 'production' : 'development',
        devtool: !PRODUCTION ? 'inline-source-map' : false,
        output: {
          filename: '[name].js'
        },
        externals: {
          jquery: 'jQuery'
        },
      }))
      .pipe(dest('dist/js'));
    }
    export const compress = () => {
      return src([
        "**/*",
        "!node_modules{,/**}",
        "!bundled{,/**}",
        "!src{,/**}",
        "!.babelrc",
        "!.gitignore",
        "!gulpfile.babel.js",
        "!package.json",
        "!package-lock.json",
      ])
      .pipe(
        gulpif(
          file => file.relative.split(".").pop() !== "zip",
          replace("_themename", info.name)
        )
      )
      .pipe(zip(`${info.name}.zip`))
      .pipe(dest('bundled'));
    };
    export const pot = () => {
      return src("**/*.php")
        .pipe(
          wpPot({
            domain: "_themename",
            package: info.name
          })
        )
      .pipe(dest(`languages/${info.name}.pot`));
    };
    export const watchForChanges = () => {
      watch('src/scss/**/*.scss', styles);
      watch('src/images/**/*.{jpg,jpeg,png,svg,gif}', series(images, reload));
      watch(['src/**/*','!src/{images,js,scss}','!src/{images,js,scss}/**/*'], series(copy, reload));
      watch('src/js/**/*.js', series(scripts, reload));
      watch("**/*.php", reload);
    } 
    export const dev = series(clean, parallel(styles, images, copy, scripts), serve, watchForChanges);
    export const build = series(clean, parallel(styles, images, copy, scripts), pot, compress);
    export default dev;

Puh, das war's! Ich hoffe, Sie haben in dieser Serie etwas gelernt und dass sie Ihnen hilft, Ihren WordPress-Entwicklungsfluss zu optimieren. Lassen Sie mich wissen, wenn Sie Fragen im Kommentarbereich haben. Wenn Sie an einem vollständigen Kurs zur Entwicklung von WordPress-Themes interessiert sind, schauen Sie sich unbedingt meinen Kurs auf Udemy mit einem Sonderrabatt für Sie an. 😀