Webpack mit Gulp 4 kombinieren

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

Webpack ist gerade der letzte Schrei! Webpack ist großartig, wenn es um Modulbündelung und die Arbeit mit Frameworks wie Vue oder React geht, aber es ist etwas umständlicher, wenn es um statische Assets (wie CSS) geht. Du bist es vielleicht gewohnt, deine statischen Assets mit etwas wie Gulp zu verwalten, und dafür gibt es einige ziemlich gute Gründe.

Dennoch wächst die Menge an JavaScript in unseren statischen Projekten, also lass uns als Ausgleich Webpack nutzen und dabei bei Gulp bleiben. In diesem Artikel speziell Gulp 4. Wir werden moderne Techniken verwenden, um einen leicht wartbaren Workflow aufzubauen, einschließlich des leistungsstarken und nützlichen Hot Module Reloading (HMR).

Vielleicht möchtest du hier anfangen

Dieser Artikel ist nicht ganz für Anfänger. Wenn du neu bei Webpack oder Gulp bist, beginne vielleicht mit diesen Tutorials.

Gulp-Tutorials

Webpack-Tutorials

Demo

Schau dir das Demo-Repo auf GitHub an. Der Branch "hmr" zeigt, wie man Hot Module Reloading einrichtet.

Voraussetzungen

Führe Folgendes aus, um die notwendigen Pakete zu installieren

npm install babel-core \
            babel-preset-es2015 \
            browser-sync \
            gulpjs/gulp#4.0 \
            webpack \
            webpack-dev-middleware \
            webpack-hot-middleware -D

Ab Node v7.9.0 werden ES6-Module nicht unterstützt. Deshalb installieren wir Babel, um `import`-Anweisungen und andere innovative JS-Features in unseren Aufgaben nutzen zu können.

Wenn du HMR nicht benötigst, lass Hot Middleware ruhig weg. Dev Middleware ist nicht davon abhängig.

Startpunkte

Los geht's! Erstelle einen Ordner tasks im Stammverzeichnis deines Projekts mit drei Dateien: index.js, webpack.js und server.js. Wir haben weniger Unordnung im Stammverzeichnis, da die Indexdatei wie gulpfile.js und die Webpack-Datei wie webpack.config.js fungiert.

Der Ordner site enthält alle Assets deiner Website.

╔ site
║   ╚═══ main.js
╠ tasks
║   ╠═══ index.js
║   ╠═══ server.js
║   ╚═══ webpack.js
╚ package.json

Um Gulp mitzuteilen, wo sich die Aufgaben befinden, müssen wir Flags in unserer `package.json` hinzufügen.

"scripts": {
  "dev": "gulp --require babel-register --gulpfile tasks",
  "build": "NODE_ENV=production gulp build --require babel-register --gulpfile tasks"
}

Der Befehl babel-register verarbeitet die `import`-Anweisungen und das `--gulpfile`-Flag definiert den Pfad zu `gulpfile.js` oder in unserem Fall zu `index.js`. Wir müssen nur auf den Ordner tasks verweisen, da, wie bei HTML, die Datei namens index den Einstiegspunkt markiert.

Richte eine grundlegende Webpack-Konfiguration ein

In `webpack.js`

import path from 'path'
import webpack from 'webpack'

let config = {
    entry: './main.js',
    output: {
        filename: './bundle.js',
        path: path.resolve(__dirname, '../site')
    },
    context: path.resolve(__dirname, '../site')
}

function scripts() {

    return new Promise(resolve => webpack(config, (err, stats) => {

        if (err) console.log('Webpack', err)

        console.log(stats.toString({ /* stats options */ }))

        resolve()
    }))
}

module.exports = { config, scripts }

Beachte, wie wir das Objekt nicht direkt exportieren, wie es viele Tutorials zeigen, sondern es zuerst in eine Variable packen. Das ist notwendig, damit wir die Konfiguration sowohl in der untenstehenden Gulp-Aufgabe scripts als auch im Server-Middleware im nächsten Schritt verwenden können.

Kontext

Die `config.context`-Einstellung ist notwendig, um alle Pfade relativ zu unserem `site`-Ordner festzulegen. Andernfalls würden sie vom `tasks`-Ordner ausgehen, was zu Verwirrung führen könnte.

Konfiguration und Aufgabe trennen

Wenn du eine sehr lange Webpack-Konfiguration hast, kannst du diese und die Aufgabe auch in zwei Dateien aufteilen.

// webpack.js
export let config = { /* ... */ }
// scripts.js
import { config } from './webpack'
export function scripts() { /* ... */ }

Hot Module Reloading

So bringst du HMR zum Laufen. Ändere den Entrypoint und die Plugins.

entry: {
  main: [
    './main.js',
    'webpack/hot/dev-server',
    'webpack-hot-middleware/client'
  ]
},

/* ... */

plugins: [
  new webpack.HotModuleReplacementPlugin()
]

Stelle sicher, dass du die zusätzlichen Entrypoints und das HMR-Plugin für die Produktion deaktivierst. Das Paket Webpack Merge hilft beim Einrichten verschiedener Umgebungen für Entwicklung und Produktion.

BrowserSync

Nun eine BrowserSync-Aufgabe einrichten.

import gulp from 'gulp'
import Browser from 'browser-sync'
import webpack from 'webpack'
import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware'

import { config as webpackConfig } from './webpack'

const browser = Browser.create()
const bundler = webpack(webpackConfig)

export function server() {

    let config = {
        server: 'site',
        middleware: [
            webpackDevMiddleware(bundler, { /* options */ }),
            webpackHotMiddleware(bundler)
        ],
    }

    browser.init(config)

    gulp.watch('site/*.js').on('change', () => browser.reload())
}

Das Dev Middleware ermöglicht es BrowserSync, das zu verarbeiten, was in `webpack.js` als Entrypoint definiert wurde. Um ihm diese Information zu geben, importieren wir das Konfigurationsmodul. Hot Middleware hingegen prüft auf Änderungen in App-Komponenten wie `.vue`-Dateien für Vue.js, um sie zu injizieren.

Da wir keine Dateien wie `main.js` per Hot Reloading ändern können, beobachten wir sie und laden das Fenster bei einer Änderung neu. Auch hier gilt: Wenn du HMR nicht benötigst, entferne webpackHotMiddleware.

Alle Aufgaben importieren

Die Datei `index.js` enthält alle Aufgaben.

import gulp from 'gulp'

import { scripts } from './webpack'
import { server }  from './server'

export const dev   = gulp.series( server )
export const build = gulp.series( scripts )

export default dev

Die exportierten Variablen definieren, welche Aufgaben unter welchem Befehl ausgeführt werden. Der Standard-Export wird mit gulp ausgeführt.

Wenn du Entwicklungs- und Produktionsumgebungen für Webpack trennst, möchtest du vielleicht eine Aufgabe gulp build ausführen, die Produktionsoptionen nutzt. Dazu importieren wir die `scripts`-Aufgaben separat, da wir hier den Server nicht starten müssen.

Während der Entwicklung wird Webpack von BrowserSync ausgeführt, daher ist es nicht notwendig, die Skriptaufgabe im Entwicklungsbefehl zu enthalten.

Aufgaben ausführen

Um mit der Entwicklung zu beginnen, kannst du nicht einfach gulp oder gulp build ausführen, da es nach einer gulpfile.js im Stammverzeichnis sucht. Wir müssen die npm-Befehle npm run dev und npm run build ausführen, um die definierten Flags zu nutzen.

Erweitern

Jetzt kannst du dir vorstellen, wie einfach es ist, Aufgaben zu erweitern und weitere zu schreiben. Exportiere eine Aufgabe in einer Datei und importiere sie in `index.js`. Sauber und einfach zu warten!

Um dir eine Vorstellung davon zu geben, wie du dein Projektverzeichnis einrichten kannst, hier ist mein persönliches Setup.

╔ build
╠ src
╠ tasks
║   ╠═══ config.js => project wide
║   ╠═══ icons.js  => optimize/concat SVG
║   ╠═══ images.js => optimize images
║   ╠═══ index.js  => run tasks
║   ╠═══ misc.js   => copy, delete
║   ╠═══ server.js => start dev server
║   ╠═══ styles.js => CSS + preprocessor
║   ╚═══ webpack.js
╚ package.json

Nochmal, warum *beides* Webpack und Gulp verwenden?

Statische Datei-Verwaltung

Gulp kann statische Assets besser handhaben als Webpack. Das Copy Webpack Plugin kann zwar auch Dateien von deiner Quelle in deinen Build-Ordner kopieren, aber wenn es um die Überwachung von Dateilöschungen oder Änderungen geht, wie z.B. das Überschreiben eines Bildes, ist gulp.watch die sicherere Wahl.

Server-Umgebung

Webpack bietet auch eine lokale Serverumgebung über Webpack Dev Server, aber die Verwendung von BrowserSync bietet einige Features, die du vielleicht nicht missen möchtest.

  • CSS/HTML/Bild-Injektion für Nicht-App-Projekte
  • Testen auf mehreren Geräten direkt out-of-the-box
  • enthält ein Admin-Panel für mehr Kontrolle
  • Bandbreitendrosselung für Geschwindigkeits- und Lade-Tests

Kompilierungszeit

Wie in diesem Beitrag auf GitHub zu sehen ist, wird Sass von node-sass viel schneller verarbeitet als durch die Kombination von sass-loader, css-loader und extract-text-webpack-plugin von Webpack.

Bequemlichkeit

In Webpack musst du beispielsweise deine CSS- und SVG-Dateien in JavaScript importieren, um sie zu verarbeiten, was manchmal ziemlich knifflig und verwirrend sein kann. Mit Gulp musst du deinen Workflow nicht anpassen.