Frontend-Entwicklung mit Grunt - Realworld Workflow (Teil 3)
In Teil 1 der Beitragsreihe zum Thema Frontend-Entwicklung mit Grunt bin ich darauf eingegangen, was Grunt ist und wie Grunt funktioniert. Teil 2 hat beleuchtet, wie Gruntfiles wartbar gehalten werden können, indem beispielsweise Task-Konfigurationen in eigene Dateien ausgelagert werden können. In diesem Teil möchte ich einige Grunt-Plugins vorstellen, die sich für mich als nützlich erwiesen haben. Im Folgenden werden die verschiedenen Plugins kurz angesprochen, wobei ausführliche Dokumentation von den jeweiligen Plugin-Seiten zu entnehmen sind.
Grunt Plugins
Zum Zeitpunkt dieses Beitrags sind über das offizielle Web Search Interface zur Suche von Grunt-Plugins 4.672 Plugins registriert. Und diese Zahl ist ständig steigend.
Wann ist man eigentlich ein Grunt-Plugin? Ein Node-Projekt ist dann ein Grunt-Plugin, wenn es den Tag gruntplugin hat und via dem Node Package Manager NPM publiziert wurde.
Statische Analyse und Linting
Das Plugin grunt-contrib-jshint validiert Javascript-Dateien mit JSHint.
// config/jshint.json
{
"options": {
"curly": true,
"eqeqeq": true,
"immed": true,
"latedef": true,
"newcap": true,
"noarg": true,
"sub": true,
"undef": true,
"unused": true,
"boss": true,
"eqnull": true,
"browser": true,
"globals": {
"require": true,
"define": true,
"requirejs": true,
"describe": true,
"expect": true,
"it": true
}
},
"gruntfile" : {
"src" : "Gruntfile.js"
},
"app_js" : {
"src" : ["<%= project.js %>"]
}
}
Dateien von A nach B verschieben
Mit dem Plugin grunt-contrib-copy können, wie der Name schon vermuten lässt, Dateien ausgehend von einer Quelle an eine andere Stelle kopiert werden. In unserem Beispiel werden alle HTML-Dateien, die sich im Source-Verzeichnis des Projekts befinden, in ein Verzeichnis project.dest kopiert. Auch hier können mit Variablen und mit globbing-Patterns gearbeitet werden.
// config/copy.js
module.exports.tasks = {
copy: {
html_gen: {
files: [
{
expand: true,
flatten: true,
filter: "isFile",
cwd: "<%= project.src %>",
src: ['**/*.html'],
dest: '<%= project.dest %>/'}
]
}
}
}
Dateien konkatinieren
Während beispielsweise zur Entwicklungszeit aus Gründen der Übersichtlichkeit, Lesbarkeit und Wartbarkeit in einzelnen Javascript-Dateien gearbeitet wird, sollte im Produktivsystem nur eine einzelne Javascript-Datei eingebunden werden. Das Plugin grunt-contrib-concat ermöglicht genau dies.
// config/concat.js
module.exports.tasks = {
"concat": {
"options": {
"separator": ';',
},
"dist": {
"src": [
'src/intro.js',
'src/project.js',
'src/outro.js'
],
"dest": '<%= project.dest %>/app.js',
},
}
}
In diesem Beispiel werden drei Javascript-Dateien in die Javascript-Datei app.js zusammengefasst. In der neuen Datei werden die Inhalte untereinander in der Reihenfolge der Auflistung eingefügt, d.h. zuerst intro.js, dann project.js und schließlich outro.js.
Verkleinern von Javascript-Dateien
Konkatinierung von Javascript-Dateien ist nicht ausreichend, da in der Praxis im Produktivsystem die eingebundene Javascript noch komprimiert werden sollte, was als uglifying bezeichnet wird. Mit dem Plugin grunt-contrib-uglify werden Javascript-Dateien menschenunlesbar gemacht, indem alle whitespaces und Formatierungen entfernt werden. Alle Optionen und Kompressionsmöglichkeiten können von der Projektseite entnommen werden.
// config/uglify.js
module.exports.tasks = {
"uglify": {
"options": {
"banner": "<%= banner %>"
},
"dist": {
"src": "<%= project.js_glob %>",
"dest": "<%= project.js_dist %>/<%= pkg.name %>.min.js"
}
}
}
Das Plugin bietet neben Verkleinerung auch das Konkatinieren an, so dass die Verwendung von grunt-contrib-concat häufig nicht nötig ist.
module.exports = {
dest: {
options: {
mangle: false,
beautify: true,
preserveComments: 'all'
},
files: [
{
expanded: true,
"src":
[
"<%= paths.src.js_lib %>/_bower.js",
"<%= paths.src.js_lib %>/bootstrap.min.js",
"<%= paths.src.js_lib %>/aes.js",
"<%= paths.src.js_lib %>/pbkdf2.js",
"<%= paths.src.js %>/browser-check.js",
"<%= paths.src.js %>/restive_setup.js",
"<%= paths.src.js %>/mega-menu.js",
"<%= paths.src.js %>/aes-crypto.js",
"<%= paths.src.js %>/content-footer.js",
"<%= paths.src.js %>/country-picker.js",
"<%= paths.src.js %>/download-center.js",
"<%= paths.src.js %>/contact-center.js"
],
"dest": "<%= paths.dest.js %>/app.js"
}
]
}
};
Transkompilierung mit Sass
Die Verwendung von Transkompiler wie Less oder Sass ist eine gängige Praxis. Folgendes Snippet zeigt, wie eine exemplarische Konfiguration für das Plugin grunt-contrib-sass aussehen kann.
// config/sass.js
module.exports.tasks = {
sass: {
dev: {
options: {
style: 'expanded',
banner: '<%= tag.banner %>',
compass: true
},
files: {
'<%= project.css_gen %>/style.css':
'<%= project.scss_file %>'
}
}
}
}
Hier wird ein Sass-File, welches über eine Variable spezifiziert wird, in eine Datei style.css übersetzt.
Shell Aufurufe
Das Plugin grunt-shell ermöglicht es, Shell-Aufurufe auszuführen. In dem Beispiel habe ich exemplarisch ein PHP-Script aufgerufen. Dabei handelt es sich um die Generierung eines Styleguides durch das Framework Pattern Lab.
// config/patternlab.js
module.exports = {
patternlab: {
options: {
stdout: true
},
command: 'php core/builder.php -g'
}
};
File Watching
Aus meiner Sicht eines der Killer Features und Pflicht in jedem Frontend-Workflow. Dabei geht es darum, dass Aktionen angestoßen werden sollen, wenn spezielle Dateien oder Dateitypen gespeichert werden. In dem einfachen Beispiel wird unter Verwendung des Plugins grunt-contrib-watch der Grunt-Task jshint angestoßen, wenn das Gruntfile oder eine Javascript-Datei im Quellverzeichnis verändert wird. Weiterhin wird auf Veränderungen von Sass-Dateien gehört und der dazugehörige sass Task angestroßen, der die Kompilierung in CSS-Dateien veranlasst.
// config/watch.js
module.exports.tasks = {
watch: {
gruntfile: {
files: '<%= jshint.gruntfile.src %>',
tasks: ['jshint:gruntfile']
},
app_js: {
files: '<%= project.js %>',
tasks: ['jshint:app_js']
},
app_sass: {
files: ['<%= project.src %>/{,*/}*.{scss,sass}'],
tasks: ['sass'],
options: {
livereload: true
},
}
}
}
Eine weitere Optimierung des Frontend-Workflows stellt die Verwendung von Live Reload dar, so dass sich eine Webseite automatisch aktualisiert, nachdem die nötigen Schritte im watch-Task ausgeführt wurden. Dieses Youtube-Video zeigt, was unter Live Reload zu verstehen ist. Im vorigen Beispiel wurde Live Reload für den Sass-Task aktiviert. Nachdem der Sass-Task abgearbeitet wurde, wird ein Server gestartet, der auf standardmäßig auf Port 35729 hört. Nun kann im Browser über ein eine Extension Live Reload aktiviert werden. Alternativ ist ein Script-Tag im HTML nötig. Weitere Informationen sind in der Dokumentation von grunt-contrib-watch zu finden.
Testen mit Karma und Jasmin
Das Plugin grunt-karma ermöglicht es, automatische Javascript-basierte Unit Tests auszuführen. Karma stellt dabei den Test Runner und Jasmin das Test Framework dar.
// config/karma.js
module.exports.tasks = {
karma: {
options: {
configFile: '<%= build_dir %>/karma-unit.js',
frameworks: ['jasmine', 'requirejs']
},
unit: {
port: 9019,
background: true
},
continuous: {
singleRun: true
}
}
}
Grunt-Ausgabe und weitere Statistiken
Mit dem Plugin time-grunt kann die Ausgabe der Task-Ausführungszeiten ausgegeben werden.
Das Plugin jshint-stylish verschönert die Ausgabe von JSHint.