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.

Ausgabe von grunt

Das Plugin jshint-stylish verschönert die Ausgabe von JSHint.

Ausgabe von grunt

Written on May 13, 2015