Stand: 17.03.2024

Lernziele

In diesem Kapitel geht es um Werkzeuge, die das Arbeiten an Webseiten erleichtern.

8.1 Terminal/Shell

Ein Terminal (auch Command Shell, kurz Shell, oder einfach nur Konsole) ist ein Fenster, in dem man Befehle für das Betriebssystem eingeben kann. Man befindet sich beim Start des Terminals in einem bestimmten Verzeichnis und wechselt über Kommandos in andere Verzeichnisse, kann dort Programme starten/löschen, neue Verzeichnisse anlegen und vieles mehr.

Wir benötigen ein Terminal für unsere Arbeit mit Node und Grunt. Die erste Frage lautet natürlich: Wie starte ich ein Terminal? Gleich dannach die Frage: Welche Befehle gibt es? Dies ist unterschiedlich für Windows- und Mac-Rechner.

8.1.1 Mac OS

Auf einem Mac starten Sie ein Terminal wie folgt:

Es öffnet sich ein Fenster, wo Sie Befehle eingeben können.

Verzeichnisse und Dateien

Mit pwd können Sie nachsehen, in welchem Verzeichnis Sie sich befinden. Das ist z. B.

/Users/schmidt

Mit ls sehen Sie, welche Programme und Unterverzeichnisse im aktuellen Verzeichnis liegen. Mit dem Modifikator "-l" bekommen Sie eine Detailansicht (Listenansicht). Mit dem Modifikator "-a" sehen Sie auch "versteckte" Dateien (Dateien, die mit einem Punkt beginnen). Das heißt es gibt die folgenden Varianten:

ls
ls -l
ls -a

Oder auch die Kombination von "-l" und "-a":

ls -al

Mit cd wechseln Sie in ein Unterverzeichnis. Mit cd .. wechseln Sie in das Elternverzeichnis.

Mit mkdir erstellen Sie ein neues Unterverzeichnis, z. B.

mkdir foo

erstellt im aktuellen Verzeichnis ein neues Unterverzeichnis "foo".

Mit rm können Sie Dateien und Verzeichnisse löschen. Eine Datei "bar.txt" löschen Sie so:

rm bar.txt

Um ein komplettes Verzeichnis zu löschen, verwenden Sie den Modifikator "-R" (steht für "rekursiv", d. h. alle enthaltenen Unterverzeichnisse werden auch gelöscht):

rm -R foo

Sollte Sie ständig Rückfragen bekommen, ob Sie wirklich etwas löschen wollen, können Sie mit "-i" (für ignore) diese Rückfragen deaktivieren:

rm -i bar.txt
rm -iR foo

Mit mv (move) können Sie Dateien umbenennen

mv bar.txt neues_bar.txt

oder in ein anderes Verzeichnis verschieben

mv bar.txt /some/other/folder/

Sie können ebenso ganze Verzeichnisse verschieben.

Textdateien betrachten

Um sich schnell Textdateien anzuschauen - z. B. eine Konfigurationsdatei wie "package.json" - gibt es den Befehl less (die alte Version des Befehls heißt more):

less package.json

Die Textdatei wird angezeigt. Wenn Sie sehr lang ist, kann man mit der Leertaste weiterscrollen. Mit u (up) und d (down) kann man seitenweise scrollen, mit den Cursortasten (hoch/runter) zeilenweise.

Mit q beendet man die Anzeige.

8.1.2 Windows

Auf einem Windows-Rechner starten Sie ein Terminal wie folgt:

Es öffnet sich ein Fenster, wo Sie Befehle eingeben können. Gezeigt wird immer das aktuelle Verzeichnis und dahinter der Cursor, z. B.

C:\Users\Schmidt>

Verzeichnisse und Dateien

Mit dir sehen Sie, welche Programme und Unterverzeichnisse im aktuellen Verzeichnis liegen.

Mit cd (change directory) wechseln Sie in ein Unterverzeichnis. Mit cd .. wechseln Sie in das Elternverzeichnis.

Mit mkdir (make directory) erzeugen Sie ein neues Unterverzeichnis.

Mit rename ändern Sie den Namen einer Datei oder eines Verzeichnisses.

Mit move verschieben Sie eine Datei oder ein Verzeichnis.

Mit del löschen Sie eine Datei.

Textdateien betrachten

Um sich schnell Textdateien anzuschauen - z. B. eine Konfigurationsdatei wie "package.json" - gibt es den Befehl type:

type package.json

8.2 Node

Node.js ist ein sehr beliebtes - praktisch nicht mehr wegzudenkendes - Framework in der Webentwicklung. Tatsächlich kommt es auch der Entwicklung von Servern, also aus dem Backend-Bereich. Warum benötigen wir es dennoch an dieser Stelle, wo wir uns doch (noch) ausschließlich mit Frontend-Entwicklung beschäftigen?

8.2.1 Node Package Manager

Der Grund ist der Node Package Manager, kurz NPM. Dieses Tool ist ein Teil des Node.js-Frameworks und erlaubt das einfache Installieren von Softwarepaketen. Insbesondere werden Abhängigkeiten verwaltet, da heutzutage jedes Softwarepaket die Installation von mehreren anderen Softwarepaketen voraussetzt. Hinzu kommen Anforderungen an die jeweilige Version des vorausgesetzten Pakets. Dem NPM kann man einfach sagen "installiere Paket X (in aktueller Version)" und es werden automatisch die Pakete A, B, C installiert, die X benötigt, und zwar in der korrekten Version.

8.2.2 Node installieren

Besuchen Sie die Node.js-Webseite, um Node.js zu installieren. Wählen Sie hier die LTS-Variante (Long Term Support).

8.2.3 Node-Projekt erstellen

Ein Node-Projekt wird in einem eigenen Verzeichnis verwaltet. Erstellen Sie ein neues Verzeichnis "myproject" und gehen Sie in das Verzeichnis.

Sie machen das Verzeichnis zu einem Node-Projekt, indem Sie eingeben:

npm init

Node stellt Ihnen jetzt einige Fragen (name, version, ...), die Sie einfach immer mit ENTER beantworten können. Sie können die Infos später anpassen.

Das Verzeichnis "myproject" ist ein also Node-Projekt. Sie können das auch bei einem bereits bestehendem Projekt machen (sofern dieses nicht bereits ein Node-Projekt ist).

package.json

In dem Projektverzeichnis liegt nach "npm init" eine Konfigurationsdatei namens package.json, wo die gerade abgefragten Infos (name, version, ...) hinterlegt sind. Noch wichtiger: Node merkt sich in dieser Datei alle Software-Abhängigkeiten für dieses Projekt, d. h. welche anderen Programme installiert werden müssen, um dieses Projekt auszuführen zu können.

Schauen wir uns die Datei an:

{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Wenn Sie mit npm ein Paket/Plugin installieren, befinden sich die entsprechenden Dateien in einem Unterverzeichnis namens node_modules.

Mit npm install können Sie in Zukunft jederzeit Pakete nachinstallieren lassen, die von der Konfigurationsdatei gefordert werden und derzeit noch nicht installiert sind.

Hinweis (Windows): Auf Windows-Rechnern müssen Sie den Pfad PATH erweitern, damit Windows die Kommandos von neu installierten Paketen findet. Hier ein Beispiel (ersetzen Sie den Pfad mit Ihrem Pfad):

PATH=%PATH%;C:\users\kipp\AppData\Roaming\npm

Sie können den Pfad kontrollieren mit

echo %PATH%

8.3 CSS-Präprozessoren (SCSS)

Mit „CSS-Präprozessoren“ bezieht man sich auf eine Sprache, die so ähnlich ist wie CSS, aber etwas mehr Möglichkeiten bietet. Ein Beispiel ist die Sprache SASS. SASS kann man sich als erweitertes CSS vorstellen. Statt mit einer CSS-Datei arbeitet man mit einer SASS-Datei, um die HTML-Seiten zu stylen. SASS vereinfacht CSS und erweitert es mit vielen Funktionen wie Variablen, Schleifen oder Verschachtelung. Die SASS-Datei muss dann allerdings noch von einem Präprozessor in eine ganz normale CSS-Datei übersetzt werden, bevor man wie gewohnt HTML+CSS auf den Server hochlädt.

Sie können sich auf der SASS-Homepage einen guten Überblick über SASS verschaffen.

SASS bedeutet ausgeschrieben „Syntactically Awesome Stylesheets“ und wurde 2007 entwickelt.

Basierend auf SASS wurde später SCSS entwickelt. SASS und SCSS sind beides Präprozessoren und bieten die gleichen Funktionalitäten. Sie unterscheiden sich jedoch in ihrer Syntax. SCSS ähnelt mehr dem normalen CSS durch die Verwendung von Klammern und Semikola.

Alternativen zu SASS/SCSS sind:

Wir haben uns hier für SCSS entschieden, da SASS und SCSS zu den beliebtesten Präprozessoren gehören (siehe auch State of CSS - Präprozessoren), aber die Ähnlichkeit zu CSS im Vergleich zu SASS größer ist. Wenn Sie sich die anderen Präprozessoren aber genauer anschauen, werden Sie feststellen, dass der Funktionsumfang relativ ähnlich ist.

8.3.1 Grundprinzipien

SCSS bietet u. a. folgende wichtige Erweiterungen für CSS an:

Wir schauen uns jedes Feature separat an. Erst im Anschluss beschäftigen wir uns mit der Frage, wie man den SCSS-Code einbindet.

Variablen

In SCSS kann man Variablen benutzen, um häufige Angaben zentral zu speichern. Eine häufige Anwendung ist das Speichern von Farben, die ja an verschiedenen Stellen (Flächen und Schrift) zur Anwendung kommen. Bei Änderung des Farbschemas muss man in CSS immer verschiedene Stellen anpassen, was natürlich auch leicht zu Fehlern führt. Auch Schriftgrößen oder Abstände sind typische Beispiele.

Variablen in SCSS werden mit einem $ markiert und wie folgt verwendet:

$hintergrund: #000;
$schrift: yellow;
$spezial: red;

body {  
  background-colour: $hintergrund;  
  color: $schrift;
}

h1 {  
  color: $schrift;
}

Wie Sie sehen, werden in Variablen nur Werte, keine ganzen Befehle gespeichert. Bei der Umwandlung von SCSS in CSS werden diese Variablen einfach überall eingesetzt.

Hinweis: Seit CSS3 gibt es auch im ganz regulären CSS Variablen, so genannte Custom Properties. Beide Arten von Variablen sind sich relativ ähnlich, unterscheiden sich aber leicht in ihren Möglichkeiten. Mit Hilfe von Custom Properties kann im JavaScript-Code auf Werte aus dem CSS zugegriffen werden, das ist mit SCSS-Variablen zum Beispiel nicht möglich. Dafür gibt es bei SCSS-Variablen Möglichkeiten für Berechnungen, die bei CSS (zumindest aktuell) nicht existieren.

Verschachtelung

Wenn Sie in CSS stylen, dann definieren Sie häufig Regeln für einen bestimmten Bereich, z. B. für das Hauptmenü in nav. Dann haben Sie eine Reihe von Regeln, hier nur ein kleines Beispiel:

nav h1 {  
  font-size: 90%;
}

nav a {  
  text-decoration: none;  
  color: blue;
}

Um die Zusammengehörigkeit zu kennzeichnen und somit die weitere Arbeit zu erleichtern, können sie Verschachtelung anwenden:

nav {  
  h1 {  
    font-size: 90%;  
  }

  a {  
    text-decoration: none;  
    color: blue;  
  }
}

Sie können natürlich auch noch weiter (tiefer) verschachteln. Bei der Umwandlung von SCSS in CSS werden wieder viele Einzelregeln (wie oben) erzeugt.

Zu beachten ist der Umgang mit Pseudoelementen, also z. B. a:hover. Hier muss bei Verschachtelung ein Verweis auf das umgebende Element mit & eingefügt werden.

Beispiel:

a:hover {  
  color: red;
}

a:visited {  
  color: grey;
}

wird zu:

a {  
  &:hover {  
    color: red;  
  }

  &:visited {  
    color:grey;  
  }
}

Das Zeichen & ist generell ein Verweis auf das umgebende Element (auch Elternelement), das & wird daher auch Elternselektor genannt.

Besonders bei Namenskonventionen wie BEM kann der Elternselektor besonders nützlich sein. Mit Hilfe des Elternselektors können Wiederholungen der Namen vermieden werden.

Ein Beispiel:

.button {  
  border-radius: 8px;
}

.button__text {  
  color: black:
}

.button—orange {  
  background-color: orange;
}

wird zu:

.button {  
  border-radius: 8px;

  &__text {  
    color: black;  
  }

  &—orange {  
    background-color: orange;  
  }
}

Mixins

Mixins sind im Grunde so etwas wie Funktionen, die also ein Stückchen CSS-Code auslagern, so dass es wiederholt verwendet werden kann. Zusätzlich kann man diese Codeteile parametrisieren.

Ein Beispiel ist folgender (CSS-)Code:

#box1 {  
  -moz-user-select: none;  
  -webkit-user-select: none;  
  -o-user-select: none;  
  user-select: none;
}

#box2 {  
  -moz-user-select: all;  
  -webkit-user-select: all;  
  -o-user-select: all;  
  user-select: all;
}

Hier wollen wir verhindern, dass der Nutzer oder die Nutzerin der Website Text in #box1 auswählen (markieren) kann. Bei #box2 haben wir den Wert all verwendet. Mit user-select: all; wird automatisch bei einem einfachen Klick auf den Textblock der gesamte Text markiert.

Die vielen Eigenschaften sind Sicherheitsmechanismen, um auch Browser anzusprechen, die das Feature user-select noch nicht in der Form implementiert haben. Die Zusätze vor dem user-select-Befehl sind sogenannte Herstellerpräfixe. Der Präfix -moz- wird dabei z. B. für den Browser Firefox verwendet.

Um uns die vier Zeilen bei jedem Vorkommen zu ersparen, definieren wir ein Mixin mit einem Parameter (für den Wert):

@mixin userselect ($value: none) {  
  -moz-user-select: $value;  
  -webkit-user-select: $value;  
  -o-user-select: $value;  
  user-select: $value;
}

Sie sehen, dass wir dem Mixin einen Namen geben (userselect) und einen Parameter ($value) und übrigens auch einen Standardwert (none) für den Fall, dass kein Parameter beim Aufruf übergeben wird.

Wir können den ursprünglichen Code dann in SCSS so formulieren:

#box1 {  
  @include userselect;
}

#box2 {  
  @include userselect(all);
}

Wie Sie sehen, verwenden wir @include, um Mixins aufzurufen.

Auch hier werden die Aufrufe bei der Umwandlung in CSS durch den Code in der Mixin-Definition ersetzt, so dass ein CSS resultiert, das so aussieht wie unser Ausgangspunkt oben.

Funktionen

Funktionen ermöglichen es Ihnen komplexere Operationen wiederzuverwenden. Im Gegensatz zu Mixins können Funktionen nur Werte und keine Strukturen zurückgeben.

Ein typisches Beispiel ist das Farbmanagement.

Mit SCSS können wir unsere Farbpalette als verschachteltes Objekt definieren. Dabei haben wir jede Farbe in verschiedenen Abstufungen abgebildet (ähnlich wie beim von Material Design empfohlenen Farbsystem).

$colors: (  
  'purple': (  
    '100': #d1c4e9,  
    '200': #b39ddb,  
    '300': ##9575cd,  
  ),  
  'grey': (  
    '100': #f5f5f5,  
    '200': #eeeeee,  
    '300': #e0e0e0,  
  ),
);

Mit Hilfe der Methode map.get() (siehe auch Sass Map Functions) könnten wir nun auf die Farben zugreifen.

Ein Beispiel:

#box1 {  
  background-color: map.get($colors, 'purple', '100');
}

#box2 {  
  background-color: map.get($colors, 'grey', '200');
}

Da es sich um eine verschachtelte Map handelt, müssen wir sowohl den Namen der Farbe als auch die gewünschte Abstufung übergeben.

Um auf Dauer die Verwendung von Farben zu vereinfachen, könnten wir das ganze nun in einer Funktion formulieren:

@function palette($name, $shade: '100') {  
  @return map.get($colors, $name, $shade);
}

Sie sehen, wir haben die Funktion auch noch verwendet, um einen Default-Wert für die Abstufung unserer Farben zu definieren. So können Sie sich zusätzlich noch Code sparen.

Die Funktion verwenden wir jetzt bei unseren Boxen:

#box1 {  
  background-color: palette('purple');
}

#box2 {  
  background-color: palette('grey', '200');
}

Falls Sie sich nun fragen, wieso wir kein Mixin verwendet haben: Auch bei anderen Befehlen wie color greifen wir auf die Farben zu, wir möchten also nur die Werte, nicht den Befehl selbst einsetzen:

#box3 {  
  background-color: palette('grey', '200');  
  color: palette('purple');
}

8.3.2 Einbinden

Ein Browser kann SCSS nicht verstehen, sondern nur CSS. Also muss Ihre SCSS-Datei umgewandelt werden. Die Frage ist: wo wird diese Umwandlung vollzogen?

Die Umwandlung von SCSS zu CSS erfolgt auf dem Server, sodass immer nur die CSS-Datei an den Client verschickt wird. Das nennt man auch Server-seitige Einbindung (der Server ist Ihr Rechner).

Server-seitige Einbindung von SCSS

Zu beachten ist hier, dass Sie jedesmal, wenn Sie die SCSS-Datei ändern, die Umwandlung erneut durchführen müssen.

Das heißt, in der HTML-Datei ist die Einbindung wie mit 'normalen' CSS-Dateien:

<link href="css/style.css" rel="stylesheet"/>

Um die Umwandlung durchführen zu können, benötigen Sie eine Umwandlungssoftware (Compiler). Also installieren Sie einen SCSS-Compiler auf Ihrem Rechner (= Server). Dieser Compiler heißt sass und kann mit Node installiert werden.

Haben Sie Node.js installiert, können Sie folgenden Befehl im Terminal in Ihrem Node-Projekt eingeben:

npm install sass

Damit installieren Sie den SCSS-Compiler für Ihr jeweiliges Projekt. Wenn Sie ein neues Projekt anlegen, müssen Sie den Befehl entsprechend dort wieder ausführen.

Haben Sie die Installation erfolgreich vollzogen, können Sie den Compiler sass benutzen, um Ihre Datei style.scss in eine CSS-Datei namens style.css zu übersetzen:

sass style.scss style.css

Wie Sie sehen, müssen Sie sowohl den Namen der SCSS-Datei als auch den Namen den neuen CSS-Datei angeben.

8.3.3 Übung

Schauen Sie sich die folgende Seite an: Homepage.

Es handelt sich um die vereinfachte Startseite dieses Skripts. Laden Sie sich aus dem Browser sowohl die HTML-Seite als auch die CSS-Datei runter und vereinfachen Sie die CSS-Datei mit Hilfe von SCSS.

Die HTML-Seite bekommen Sie mit Rechtsklick auf den Link oben und "Ziel speichern unter...". Die CSS-Datei bekommen Sie, wenn Sie sich den Quelltext im Browser anschauen und dort auf den Link zur CSS-Datei klicken.

8.4 Tasks automatisieren (Grunt)

Grunt ist ein Werkzeug, das es erlaubt, eine Reihe von Aufgaben (Tasks) zu automatisieren, z. B. die Verwendung von Precompiler (SCSS/SASS/LESS) oder das Minifizieren von Dateien. Auch ganz einfache Aufgaben wie das Verschieben, Kopieren, Umbenennen und Löschen von Dateien lässt sich in Grunt definieren.

Grunt funktioniert ähnlich wie npm modular: für jeden möglichen Task gibt es ein sogenanntes Plugin. Man installiert nur genau die Plugins, die man benötigt. In einer Konfigurationsdatei gibt man an, welche Plugins installiert sein müssen und welcher Tasks wann und wie genau ausgeführt werden soll.

Ein Tool wie Grunt ist bei einem größeren Webseiten-Projekt unvermeidlich. Üblicherweise hält man alle Dateien, die man ständig verändert, in einem eigenen Ordner src (sources) und lässt sich von Grunt die auslieferungsfähige Version der Webseite in einen anderen Ordner dist (distribution) generieren. Diese Trennung ist enorm wichtig.

Grunt wird auch als Task Runner bezeichnet. Dies geschieht in der Sprache JavaScript. Eine Alternative zu Grunt ist Gulp. Java-Entwickler kennen das Tool Ant, das eine ähnliche Funktion im Java-Entwicklungs-Workflow hat.

8.4.1 Installation

Zunächst müssen wir Grunt installieren. Dazu sollten Sie bereits Node.js und damit den Packagemanager npm installiert haben (s. o.).

Sobald Sie Node.js installiert haben, öffnen Sie ein Terminal, navigieren zu Ihrem Node-Projekt-Ordner und schreiben:

npm install grunt-cli

Das "cli" steht für command line interface und heißt nur, dass Sie Grunt über das Terminal (über commands) bedienen werden. Wie Sie ein Node-Projekt erstellen, haben wir Ihnen weiter oben bereits im Abschnitt "Node-Projekt erstellen" gezeigt.

8.4.2 Projekt

Sie haben in dem Verzeichnis jetzt eine Datei "package.json".

Jetzt installieren Sie in dieses Projekt Grunt und geben mit "--save-dev" an, dass in "package.json" verzeichnet werden soll, dass dieses Projekt Grunt benötigt.

npm install grunt --save-dev

(bei einer Fehlermeldung wieder sudo verwenden.)

In "package.json" sehen Sie jetzt die Abhängigkeit:

"devDependencies": {
    "grunt": "^1.0.3",
}

Datei Gruntfile.js

Grunt soll ja verschiedene Dienste, genannt Tasks, bei Ihrer Webentwicklung automatisieren. Diese Tasks werden in einer Konfigurationsdatei definiert: Gruntfile.js

Erstellen Sie eine Textdatei "Gruntfile.js", die zunächst folgendes enthält:

module.exports = function(grunt) {

  // ein einziger Task
  grunt.registerTask('default', 'Einfacher Task', function() {
   grunt.log.writeln('Hello world').ok();
  });

};

Sie sehen hier eine "Hülle" ("module.exports ..."), in der alle Grunt-relevanten Informationen stehen.

Außerdem ist hier ein einziger Task definiert. Dieser Default-Task wird ausgeführt, wenn man "grunt" auf dem Terminal eingibt.

Der Task besteht aus

In Grunt heißt ein solcher Task Basic Task.

Tippen Sie jetzt auf der Konsole:

grunt

Sie sollten sehen:

Running "default" task
Hello world
OK

Done.

Verzeichnisstruktur

Für unsere Beispiele legen wir folgende Unterverzeichnisse an:

src
dist

Im Verzeichnis src (source) legen wir die Dateien ab, mit denen wir aktiv arbeiten, z. B. HTML-Dateien oder SCSS-Dateien.

Im Verzeichnis dist (distribution) kommen dann die Dateien, die teilweise von Grunt generiert werden. Wenn Grunt durchgelaufen ist, sollte in dist die fertige Webseite liegen, die man dann hochladen kann.

8.4.3 Tasks

Nachdem wir Grunt lediglich zum Laufen gebracht haben, bauen wir jetzt echte Tasks ein.

Erster Task: SCSS

Mit unserem ersten Task möchten wir unsere SCSS-Datei/en automatisch in CSS-Dateien umwandeln. Dazu müssen wir ein Plugin installieren, das diesen Task zur Verfügung stellt. Im nächsten Schritt konfigurieren wir den Task, d. h. wir sagen, welche Datei genau umgewandelt werden soll und wie diese nach der Umwandlung heißen soll.

Wir installieren zunächst das SCSS-Plugin für Grunt (siehe auch grunt-contrib-sass):

npm install grunt-contrib-sass --save-dev

Das "--save-dev" sorgt dafür, dass in "package.json" verzeichnet wird, dass wir das SCSS-Plugin benötigen (und in welcher Version). Wenn Sie sich package.json ansehen, sehen Sie einen neuen Eintrag unter "devDependencies":

{
  "name": "myproject",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "grunt": "^1.0.3",
    "grunt-contrib-sass": "^2.0.0"
  }
}

Wie bereits erwähnt werden die Dateien des Plugins in einem Unterverzeichnis namens node_modules ablegt.

Jetzt erweitern wir unser Gruntfile und sehen im wesentlichen drei Teile:

Teil 1 enthält die Konfiguration der Tasks. In unserem Fall wird hier gesagt, welches SCSS-File in welches CSS-File gewandelt werden soll.

Teil 2 besteht aus Befehlen zum Laden notwendiger Plugins. Diese Plugins müssen zuvor mit npm installiert worden sein. In Teil 2 werden diese Plugins lediglich in Grunt geladen.

Teil 3 baut dann verschiedene Tasks zu einem neuen Task 'default' zusammen. In Grunt heißt ein solcher Task Alias Task. Hier besteht der Task nur aus einem Task (SCSS), man kann aber eine Liste von mehreren Tasks in den eckigen Klammern angeben, die dann hintereinander aufgerufen werden.

Im Gruntfile sieht das so aus:

module.exports = function(grunt) {

  // (1) Konfiguration der Tasks
  grunt.initConfig({

    // Verwende Abhängigkeiten aus Projektkonfiguration
    pkg: grunt.file.readJSON('package.json'),

    // SCSS-Dateien in CSS umwandeln
    sass: {
      build: {
        files: {
          'dist/css/style.css': 'src/scss/style.scss'
        }
      }
    }

  });

  // (2) Laden der Plugins
  grunt.loadNpmTasks('grunt-contrib-sass');

  // (3) Alias-Task definieren
  grunt.registerTask('default', ['sass']);

};

Bevor Sie das ganze testen, müssen Sie noch ein Verzeichnis src/scss anlegen und dort eine SCSS-Datei style.scss hinterlegen, z. B.

$hintergrund: #000;
$schrift: yellow;
$spezial: red;

body {
  background-color: $hintergrund;
  color: $schrift;
}

h1 {
  color: $spezial;
}

Wenn Sie jetzt Grunt starten:

grunt

sehen Sie diese Ausgabe

Running "sass:build" (sass) task

Done.

Außerdem sollten Sie die CSS-Datei "style.css" vorfinden:

body {
  background-color: #000;
  color: yellow;
}
h1 {
  color: red;
}

Zweiter Task: Minifizieren

Als nächstes möchten wir das CSS-File "minifizieren", d. h. es werden Leerzeichen und Zeilenumbrüche rausgeworfen, um Speicher zu sparen.

Zunächst installieren Sie das Plugin "cssmin" für Grunt (siehe auch grunt-contrib-cssmin):

npm install grunt-contrib-cssmin --save-dev

In "package.json" sehen Sie eine neue Abhängigkeit:

"devDependencies": {
  "grunt": "^1.0.3",
  "grunt-contrib-cssmin": "^3.0.0",
  "grunt-contrib-sass": "^2.0.0"
}

Jetzt müssen wir unseren Task im "Gruntfile.js" an drei Stellen einbauen:

  1. Im ersten Teil legen wir die Konfiguration für den Task "cssmin" an und spezifizieren Ausgangsdatei/en (src) und Zieldatei (dest).
  2. Im zweiten Teil laden wir das Plugin "cssmin"
  3. Im dritten Teil fügen wir den Task "cssmin" zum Alias-Task hinzu; hier spielt die Reihenfolge eine Rolle, denn genau so werden die Tasks hintereinander ausgeführt
module.exports = function(grunt) {

  // (1) Konfiguration
  grunt.initConfig({

    // Verwende Abhängigkeiten aus Projektkonfiguration
    pkg: grunt.file.readJSON('package.json'),

    // SCSS-Dateien in CSS umwandeln
    sass: {
      build: {
        files: {
          'dist/css/style.css': 'src/scss/style.scss'
        }
      }
    },

    // CSS-Datei minifizieren
    cssmin : {
        target : {
            src : ["dist/css/style.css"],
            dest : "dist/css/style.min.css"
        }
    }

  });

  // (2) Plugins
  grunt.loadNpmTasks('grunt-contrib-sass');
  grunt.loadNpmTasks('grunt-contrib-cssmin');

  // (3) Taskdefinition
  grunt.registerTask('default', ['sass', 'cssmin']);
};

Starten Sie Grunt

grunt

Auf der Konsole sehen Sie, wie viele Dateien umgewandelt wurden, und wie viel Speicherplatz gespart wurde.

Running "sass:build" (sass) task

Running "cssmin:target" (cssmin) task
>> 1 file created. 73 B → 51 B

Done.

Wenn Sie sich "style.min.css" ansehen (z. B. mit less), sehen Sie das minifizierte CSS auf einer einzigen Zeile:

body{background-color:#000;color:#ff0}h1{color:red}

Dritter Task: Kopieren

Jetzt möchten wir noch unsere HTML-Dateien, die wir im Verzeichnis src verwalten, ins Verzeichnis dist einfach rüberkopieren. Dazu benötigen wir das Plugin copy (siehe auch grunt-contrib-copy):

npm install grunt-contrib-copy --save-dev

Wir passen anschließend unser Gruntfile an. Die Konfiguration sieht so aus:

copy: {
  main: {
    expand: true,
    cwd: 'src',
    src: '*html',
    dest: 'dist/',
  },
}

Das Laden so:

grunt.loadNpmTasks('grunt-contrib-copy');

Und unser neuer Default-Taks beinhaltet das Kopieren:

grunt.registerTask('default', ['sass', 'cssmin', 'copy']);

Vierter Task: Löschen

Zu guter Letzt setzen wir einen Task vor alle anderen: wir löschen das Verzeichnis dist zu Beginn unseres Prozesses, um sicherzugehen, dass sich keine Altlasten in diesem Verzeichnis ansammeln.

Dazu installieren wir das Plugin clean (siehe auch grunt-contrib-clean):

npm install grunt-contrib-clean --save-dev

Und passen unser Gruntfile an. Hier nochmal in voller Schönheit:

module.exports = function(grunt) {

  // (1)
  grunt.initConfig({

    // Verwende Abhängigkeiten aus Projektkonfiguration
    pkg: grunt.file.readJSON('package.json'),

    // Verzeichnis "dist" komplett löschen
    clean: ["dist/"],

    // SCSS-Dateien in CSS umwandeln
    sass: {
      build: {
        files: {
          'dist/css/style.css': 'src/scss/style.scss'
        }
      }
    },

    // CSS-Datei minifizieren
    cssmin : {
        target : {
            src : ["dist/css/style.css"],
            dest : "dist/css/style.min.css"
        }
    },

    // HTML-Dateien kopieren
    copy: {
      main: {
        expand: true,
        cwd: 'src',
        src: '*html',
        dest: 'dist/',
      },
    }

  });

  // (2)
  grunt.loadNpmTasks('grunt-contrib-sass');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-clean');

  // (3)
  grunt.registerTask('default', ['clean', 'sass', 'cssmin', 'copy']);
};

Beim Aufruf von Grunt sehen wir noch Informationen zu den Kopier- und Löschvorgängen:

Running "clean:0" (clean) task
>> 1 path cleaned.

Running "sass:build" (sass) task

Running "cssmin:target" (cssmin) task
>> 1 file created. 73 B → 51 B

Running "copy:main" (copy) task
Copied 1 file

Done.

Tasks separat aufrufen

Sie können Grunt auch mit einem spezifischen Task aufrufen:

grunt clean

Dies ruft z. B. nur das "clean" auf:

Running "clean:0" (clean) task
>> 1 path cleaned.

Done.

8.4.4 Mehrseitige Website zusammenbauen

Ein typisches Problem bei mehrseitigen Websites ist, dass der grobe Rahmen (Kopf, Fuß, Navigation) bei allen Seiten (fast) gleich ist und nur die Inhalte (z. B. alles innerhalb von <body>) verschieden.

Mit Assemble können Sie elegant zwischen dem groben Rahmen, genannt "Layout", und den Inhalten, genannte "Pages", trennen. Mit einem Mechanismus, der Handlebars genannt wird, können Sie auch den Rahmen für einzelne Seiten anpassen (typisches Beispiel: der Seitentitle unter <title> sollte bei jeder Seite anders lauten).

Wir zeigen hier anhand eines einfachen Beispiels, wie ein solches Projekt aussehen könnte.

Wir erzeugen zunächst ein Node-Projekt in einem eigenen Verzeichnis:

npm init

Anschließend installieren wird Grunt:

npm install grunt --save-dev

Zusätzlich benötigen wir das Plugin "grunt-assemble":

npm install grunt-assemble --save-dev

Jetzt erstellen wir Layout und Beispielseiten. Richten Sie zunächst folgende Verzeichnisstruktur ein:

src
src/layouts
src/pages

Layout

Das Layout definieren wir in der Datei "src/layouts/default.hbs".

<!DOCTYPE html>
<html lang="de" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>{{ title }}</title>
  </head>
  <body>
    {{> body}}
  </body>
</html>

Dabei verwenden Sie an zwei Stellen Platzhalter: {{ title }} für den Seitentitel und {{> body}} für den Body. Diese Platzhalter sind in Handlebars-Syntax geschrieben (handlebars, weil die geschweiften Klammern wie Fahrradlenker aussehen) und funktionieren so ähnlich wie Variablen. Dort werden später Inhalte eingefügt.

Seiten

Die Seiten enthalten die wesentlichen Inhalte der späteren HTML-Seiten. Wir erstellen hier zwei Beispielseiten.

Erstellen Sie zunächst eine Datei "src/pages/index.hbs". Im oberen Bereich können Variablen gesetzt werden. Hier wird lediglich die Variable "title" gesetzt. Im Hauptbereich befindet sich dann der Text, der oben in {{> body}} eingesetzt wird:

---
title: Start
---
<h1>Das ist die Startseite</h1>

<p>
  Lorem ipsum dolor sit amet, ...
</p>

Grunt wird diese zwei Komponenten später in das Layout einfügen, so dass die folgende HTML-Datei "index.html" entsteht:

<!DOCTYPE html>
<html lang="de" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>Start</title>
  </head>
  <body>
  <h1>Das ist die Startseite</h1>

  <p>
    Lorem ipsum dolor sit amet, ...
  </p>
  </body>
</html>

Wir erstellen noch eine zweite Datei "src/pages/kontakt.hbs". Hier definieren wird einen anderen Titel und Seiteninhalt.

---
title: Kontakt
---
<h1>Kontakt</h1>

<p>
  Sie finden mich ...
</p>

Auch hier setzt Grunt später die Inhalte in die Layoutdatei, so dass eine HTML-Seite "kontakt.html" entsteht.

Grunt konfigurieren

Grunt soll jetzt die Layout-Datei nehmen

src/layout/default.hbs

und für jede Seiten-Datei aus

src/pages/index.hbs
src/pages/kontakt.hbs

eine HTML-Datei erzeugen, die dann in den Ordner dist geschrieben wird:

dist/index.html
dist/kontakt.html

Wir konfigurieren das wie folgt im Gruntfile:

module.exports = function(grunt) {

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        assemble: {
            options: {
                layout: "src/layouts/default.hbs",
                flatten: true
            },
            pages: {
                files: {
                    'dist/': ['src/pages/*.hbs']
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-assemble');

    grunt.registerTask('default', ['assemble']);
};

Wenn Sie Grunt aufrufen, sehen Sie:

Running "assemble:pages" (assemble) task
Assembling dist/index.html OK
Assembling dist/kontakt.html OK
>> 2 pages assembled.

Done.

Im Verzeichnis dist können Sie jetzt die resultierenden HTML-Seiten sehen.

Markdown

Wenn Sie schnell Ihre Seiten schreiben möchten, können Sie Markdown verwenden. Markdown bietet eine vereinfachte Syntax für häufige Formatierungen wie Überschriften und Hervorhebungen.

Die Startseite können Sie dann so schreiben. Der Bereich, in dem Markdown verwendet wird, wird mit zwei Handlebars-Helpern markiert:

---
title: Start
---
{{#markdown}}
# Das ist die Startseite

Lorem ipsum dolor sit amet, ...
{{/markdown}}

Eine Übersicht über die Markdown-Syntax finden Sie auf dem Markdown Cheatsheet der Assemble-Webseite.