Moin moin und hallo,

wie in dem letzten Artikel angekündigt möchte ich mich im Folgenden um einen automatsierten den Prozess, zum Einspielen neuer Webseiten-Inhalte und Änderungen, mit Hilfe von Grunt widmen.


Ziel

Warum möchte ich den Build-Lauf überhaupt automatisieren? Wie viele andere Schritte innerhalb eines Software-Lebenszyklus, ist das Deployment ein sehr mechanis und repetitiver Vorgang, der nur sehr wenig Nachdenken erfordert. In einer Idealen Umgebung muss kann man seinen sogar Kopf getrost ganz ausschalten.

Alles was immer wiederkehrend auftritt und nur wenig Kopf-Leistung erfordert, sollte (muss) automatisiert werden, damit man sich auf die wesentlicheren Dinge konzentrieren kann.

Dafür möchte ich, weil ich bisher durchweg nur psoitive Erfahrung damit gesammelt habe, Grunt einsetzen.

![Grunt logo](grunt-logo.png)

Grunt ist ein sogenannter “Task runner”, d.h. er führt ihm vorgegebene Aufgaben automatisch aus. Ähnlich wie bei Ant, Maven, Gradle, Gulp, oder ählichen anderen Task runner, wird der Ablauf in einer zentralen Steuerungsdatei definiert.

Bei Grunt heißt diese “Gruntfile.js”.

Gruntfile.js

Ja, ganz Recht, Grunt ist ein weiteres Framework das auf Node JS aufbaut.

Diese Steuerungsdatei sollte in an der Wurzel unseres Projektes angelegt werden:

├── ...
├── contents
├── plugins
├── templates
├── config.json
├── Gruntfile.js
├── ...

Die kleinste, vorstellbare Grunt-Konfiguration sieht wie folgt aus:
module.exports = function (grunt) {

}

Diese leistet aber im Grunde nichts, und ist entsprechend noch wenig hilfreich.
Bevor wir uns in die Implementierung unseres Buildlaufs stürzen, müssen wir uns zuerst die einzelnen Schritte noch einmal verdeutlichen:
  1. Baue eine neue Version mit Wintersmith
  2. Kopiere den neuen Build in das Deployment-Verzeichnis
  3. Füge alle neuen Dateien dem Git-Repository hin zu
  4. Spiele alle Änderungen in das lokale Repository ein
  5. Schreibe die neue Version auf den Server

Da wir mit einem Proejkt arbeiten, das bereits mit dem Heroku Server konfiguriert ist, entfällt ein möglicher Server Neustart. Darum kümmert sich nämlich Herkou selbst, d.h. wir müssen nur die Ändeurngen in den “master” Branch hochladen.

Die einzelnen Schritte spiegeln sich in dem Build-Skript wie folgt wieder:


### 0. Installiere Grunt
~$> cd <projet verzeichnis>
~$> npm install -g grunt-cli
~$> npm install grunt --save-dev

### 1. Baue eine neue Version mit Wintersmith

Zunächst müssen wir das Wintersmith Grunt Plugin installieren:

~$> npm install grunt-wintersmith --save-dev

Als nächstes erweitern wir unser Build-Skript um die Wintersmith-Konfiguration als auch um einen “build”-Task

module.exports = function (grunt) {

    var deploymentDir = '<Pfad zum Deployment-Verzeichnis>'

    grunt.initConfig({

        wintersmith: {
            build: {}
        }
    });

    // Load NPM Tasks
    grunt.loadNpmTasks('grunt-wintersmith');


    grunt.task.registerTask('build', ['wintersmith']);
};

Wenn wir jetzt unser Projekt bauen möchten, muss nur noch folgendes ein die Konsole eingegeben werden: “`javascript ~$> grunt build “`

Man könnte auch einen sogenannten “default”-Task einrichten und nur noch per “grunt” den Prozess starten, ich versuche aber meine Tasks immer genauer zu spezifizieren um sie später schneller wieder zu finden und einfacher verwalten zu können.


### 2. Kopiere den neuen Build in das Deployment-Verzeichnis

Nachdem das Projekt nun gebaut wurde, müssen wir unseren “build” in Folge in das eigentliche “Deployment-Verzeichnis”, sprich das Verzeichnis aus dem wir den Server ansteuern, kopieren.

Das Kopieren von Dateien erfolgt bei Grunt ebenfalls mit Hilfe eines Plugins:

~$> npm install grunt-contrib-copy --save-dev

Unser Build-Skript bedarf dann folgender Erweiterungen: “`javascript module.exports = function (grunt) {
var deploymentDir = '<Pfad zum Deployment-Verzeichnis>'

grunt.initConfig({

    wintersmith: {
        build: {}
    },

    copy: {
        main: {
            expand: true,
            src: '**',
            cwd: 'build/',
            dest: deploymentDir,

            mode: true
        }
    }
});

// Load NPM Tasks
grunt.loadNpmTasks('grunt-wintersmith');
grunt.loadNpmTasks('grunt-contrib-copy');


grunt.task.registerTask('build', ['wintersmith', 'copy']);

};


Da wir nie, niemals, nicht, in einem Build direkt Änderungen vornehmen, können wir die bestehenden Dateien im Deployment-Verzeichnis ohne Sorgen überschreiben.

<br/>
### Schritt 3 - 5: Git

Um nun unsere Änderungen zu publizieren brauchen wir lediglich noch das [Grunt Git Plugin](https://github.com/rubenv/grunt-git).
Die einzelnen Tasks sind so ähnlich durch zu führen, sodass ich denke das es sich nicht lohnt sie noch einzelnen zu listen.

Zuerst die Plugininstallation:

```javascript
~$> npm install grunt-git --save-dev

Unser vollständiges Build-Skript sieht nun wie folgt aus:
module.exports = function (grunt) {

    var deploymentDir = '<Pfad zum Deployment-Verzeichnis>'

    grunt.initConfig({

        wintersmith: {
            build: {}
        },

        copy: {
            main: {
                expand: true,
                src: '**',
                cwd: 'build/',
                dest: deploymentDir,

                mode: true
            },
        },

        gitadd: {
            task: {
                options: {
                    all: true,
                    cwd: deploymentDir
                }
            }
        },

        gitcommit: {
            task: {
                options: {

                    message: 'Auto Deployment',
                    noVerify: true,
                    cwd: deploymentDir
                }
            }
        },

        gitpush: {
            heroku: {
                options: {
                    // Target-specific options go here.
                    cwd: deploymentDir,
                    remote: 'heroku',
                    branch: 'master'
                }
            }
        }
    });

    // Load NPM Tasks
    grunt.loadNpmTasks('grunt-wintersmith');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-git');


    grunt.task.registerTask('build', ['wintersmith', 'copy', 'gitadd', 'gitcommit', 'gitpush']);
};

Fazit

Wenn man alles richtig eingerichtet hat, sieht unser Buildlauf wie folgt aus:

~$>grunt build
Running "wintersmith:build" (wintersmith) task
  rendering tree:
    articles/
      grunt-auto-deployment/
        index.md (url: /articles/grunt-auto-deployment/)
      relaunch-2.0/
        build.png (url: /articles/relaunch-2.0/build.png)
        index.md (url: /articles/relaunch-2.0/)
      wintersmith-on-heroku/
        dns.png (url: /articles/wintersmith-on-heroku/dns.png)
        domains.png (url: /articles/wintersmith-on-heroku/domains.png)
        index.md (url: /articles/wintersmith-on-heroku/)
        settings.png (url: /articles/wintersmith-on-heroku/settings.png)
    authors/
      roman-fetsch.json (url: /authors/roman-fetsch.html)
    backups/
      another-test/
        index.md (url: /backups/another-test/)
      bamboo-cutter/
        index.md (url: /backups/bamboo-cutter/)
        taketori_monogatari.jpg (url: /backups/bamboo-cutter/taketori_monogatari.jpg)
      hello-world/
        index.md (url: /backups/hello-world/)
      markdown-syntax/
        index.md (url: /backups/markdown-syntax/)
      red-herring/
        banana.png (url: /backups/red-herring/banana.png)
        index.md (url: /backups/red-herring/)
      baker.json (url: /backups/baker.html)
      the-wintersmith.json (url: /backups/the-wintersmith.html)
    bootstrap-3.3.6-dist/
      css/
        bootstrap-theme.css (url: /bootstrap-3.3.6-dist/css/bootstrap-theme.css)
        bootstrap-theme.css.map (url: /bootstrap-3.3.6-dist/css/bootstrap-theme.css.map)
        bootstrap-theme.min.css (url: /bootstrap-3.3.6-dist/css/bootstrap-theme.min.css)
        bootstrap-theme.min.css.map (url: /bootstrap-3.3.6-dist/css/bootstrap-theme.min.css.map)
        bootstrap.css (url: /bootstrap-3.3.6-dist/css/bootstrap.css)
        bootstrap.css.map (url: /bootstrap-3.3.6-dist/css/bootstrap.css.map)
        bootstrap.min.css (url: /bootstrap-3.3.6-dist/css/bootstrap.min.css)
        bootstrap.min.css.map (url: /bootstrap-3.3.6-dist/css/bootstrap.min.css.map)
      fonts/
        glyphicons-halflings-regular.eot (url: /bootstrap-3.3.6-dist/fonts/glyphicons-halflings-regular.eot)
        glyphicons-halflings-regular.svg (url: /bootstrap-3.3.6-dist/fonts/glyphicons-halflings-regular.svg)
        glyphicons-halflings-regular.ttf (url: /bootstrap-3.3.6-dist/fonts/glyphicons-halflings-regular.ttf)
        glyphicons-halflings-regular.woff (url: /bootstrap-3.3.6-dist/fonts/glyphicons-halflings-regular.woff)
        glyphicons-halflings-regular.woff2 (url: /bootstrap-3.3.6-dist/fonts/glyphicons-halflings-regular.woff2)
      js/
        bootstrap.js (url: /bootstrap-3.3.6-dist/js/bootstrap.js)
        bootstrap.min.js (url: /bootstrap-3.3.6-dist/js/bootstrap.min.js)
        jquery-2.2.4.min.js (url: /bootstrap-3.3.6-dist/js/jquery-2.2.4.min.js)
        npm.js (url: /bootstrap-3.3.6-dist/js/npm.js)
    fonts/
      glyphicons-halflings-regular.eot (url: /fonts/glyphicons-halflings-regular.eot)
      glyphicons-halflings-regular.svg (url: /fonts/glyphicons-halflings-regular.svg)
      glyphicons-halflings-regular.ttf (url: /fonts/glyphicons-halflings-regular.ttf)
      glyphicons-halflings-regular.woff (url: /fonts/glyphicons-halflings-regular.woff)
      glyphicons-halflings-regular.woff2 (url: /fonts/glyphicons-halflings-regular.woff2)
    images/
      head.jpeg (url: /images/head.jpeg)
      me.jpg (url: /images/me.jpg)
    pages/
      1.page (url: /)
      about-me.md (url: /pages/about-me.html)
      contact.md (url: /pages/contact.html)
      impressum.md (url: /pages/impressum.html)
    styles/
      animate.css (url: /styles/animate.css)
      code.less (url: /styles/code.css)
      footer.less (url: /styles/footer.css)
      header.less (url: /styles/header.css)
      page.less (url: /styles/page.css)
      theme-superhero.min.css (url: /styles/theme-superhero.min.css)
    about.md (url: /about.html)
    archive.json (url: /archive.html)
    composer.json (url: /composer.json)
    feed.json (url: /feed.xml)
    index.json (url: /index.php)
    index.page (url: /)
    last.page (url: /)


Running "copy:main" (copy) task
Created 18 directories, copied 54 files

Running "gitadd:task" (gitadd) task

Running "gitcommit:task" (gitcommit) task

Running "gitpush:heroku" (gitpush) task

Done.

Damit sind wir auch schon fertig.

Man kann seinen Buildlauf natürlich soweit abstrahieren / verkomplizieren, wie es das jeweilige Projekt erfodert. Meiner Erfarhung nach gibt es, für fast jeden Fall, bereits irgendwo ein Grunt Plugin. Für denn Fall dass es doch nicht so ist, kann man auch noch andere Konsolen-Kommandos aus Grunt heraus starten und somit in einen einheitlichen Build zusammenführen.

Die Kunst ist es aber sein Build-Skript so sauber wie möglich zu gestalten. Wie bei vielen anderen Dingen gelingt das meistens nur wenn man es schafft einen komplexen Ablauf in viele einzelne, in sich geschlossene Aufgaben auf zu brechen und zu verwalten.


#### In diesem Sinne, bis demnächst!