AngularJS und Java EE: Build Prozess

Der Schritt von der Entwicklung von Webanwendungen mit JSF hin zu JavaScript Frameworks wie „AngularJS“ ist kein Kleiner. Umfangreiche Anwendungen mit JavaScript zu schreiben ist da nur ein kleiner Baustein denn allzu schnell wird klar, dass die „JavaScript Welt“ ganz eigene Tools, Frameworks, Do’s and Don’ts hatAngularJS + Yeoman_s

Wenn die ersten AngularJS – Tutorials erst einmal absolviert sind und man anfängt sich ernsthaft Gedanken um eine saubere Projektstruktur zu machen wird es Zeit zu automatisieren. Ein häufig eingesetztes Tool für diesen Zweck ist „grunt“. Grunt allein reicht hier allerdings bei weitem nicht denn für jede Aufgabe gibt es eigenes Modul das ganz individuell konfiguriert werden will.

Yeoman erscheint da vielen Entwicklern der Retter in der Not zu sein und bietet die Flucht aus dem Dschungel der Module, Plugins und Konfigurationen, aber müssen wir überhaupt flüchten? Es steht außer Frage: Yeoman nimmt dem Entwickler viel Arbeit ab. Auf Basis von so genannten Generatoren werden (nicht nur für AngularJS) ganze Projekte mit nur wenigen Angaben generiert, inklusive entsprechender Prozesse fürs Testing, Development und Release-Builds. Alleine für AngularJS gibt gleich eine Vielzahl von solchen Generatoren, allen voran der offizielle „angular“ Generator vom Yeoman Team selbst.

Auf den ersten Blick ähnelt es demnach den Maven Archetypes mit dessen Hilfe wir ja auch im Java Umfeld Template basierte Projekte erstellen können. Yeoman geht allerdings noch einen Schritt weiter. Nach dem initialen Projekt-Setup kann Yeoman auch während der Entwicklung zur Generierung von Sourcen verwendet werden. Wir benötigen eine Angular-Route? Nichts leichter als das „yo angular:route myRoute“ generiert einen entsprechenden Eintrag in der Konfiguration, legt View, Controller und passende Test-Cases an.

„Traue keinem Generator den du nicht verstehst“

Yeoman erleichtert also das Einrichten eines Projektes und Unterstützt bei der täglichen Entwicklung. Aber nur weil wir etwas machen lassen heißt das nicht, dass wir davon entbunden sind es zu verstehen, außerdem legt jeder Yeoman-Generator seine eigene Struktur und Abläufe fest die in Gänze oftmals nicht den eigenen Anforderungen entsprechen. Also widmen wir uns heute dem ganz eigenen Setup eines AngularJS Projektes. Genutzt wird in diesem Beispiel NetBeans als Werkzeug, welches die eine oder andere Aufgabe ebenfalls übernehmen kann.

Als Grundlage dient folgende Projektstruktur:

AngularJS + Yeoman_folderfiles

Der eigentliche Build-Prozess gliedert sich in diverse einzelne Schritte die wir mittels „grunt“ automatisieren. Werfen wir also einen Blick auf die Kern-Aufgaben die in aller Regel zur Bereitstellung einer JavaScript Anwendung nötig ist und wie wir dies mittels „grunt“ bewerkstelligen können. ( wer noch nie mit grunt gearbeitet hat, finde hier  eine kleine Einführung )


 

Was: Code Quality prüfen

Womit: grunt-contrib-jshint

Wie:

        jshint: {
            options: {
                jshintrc: '.jshintrc'
            },
            all: {
                src: "public_html/{,modules/**/}*.js"
            }
        },

Ergebnis:

jshint überprüft unseren Quelltext (alle *.js Dateien in unserem Webfolder und unterhalb von ‚modules‘) auf Basis der Regeln die in der Datei: „.jshintrc“ Konfiguriert sind. Eine Verletzung der Regeln führt zu einem Abbruch des Vorgangs. Die Prüfungen die jshint zur Verfügung stehen und die in der jshintrc-Datei aktiviert werden können sind hier alle aufgeführt: http://jshint.com/docs/options/


Was: CSS Source optimieren

Womit: grunt-postcss + autoprefixer

Wie:

        postcss: {
            options: {
                processors: [
                    require('autoprefixer')({
                        browsers: ['last 2 versions']
                    })
                ]
            },
            release: {
                files: [{
                        expand: true,
                        cwd: 'public_html/resources/styles/',
                        src: '*.css',
                        dest: '.tmp/styles/'
                    }]
            }
        },

Ergebnis:

Wir verwenden im Beispiel nur den autoprefixer der dafür sorgt dass unsere CSS Regeln mit den Browser-spezifischen Erweiterungen ergänzt werden. (-ms… , -webkit…). Das Ergebnis ist eine neue CSS-Datei die wir (erst einmal) in einem temporären Ordner ablegen. Der „autoprefixer“ ist nur ein Anwendungsfall, es existieren diverse Plugins für postcss, siehe: https://github.com/postcss/postcss/blob/master/docs/plugins.md


Was: automatisch JavaScript- und CSS-Links in index.html setzen

Womit: grunt-injector

Wie:

injector: {
            options: {
                bowerPrefix: 'bower:',
                template: 'public_html/index.html',
                ignorePath: 'public_html',
                addRootSlash: false
            },
            development: {
                files: {
                    'public_html/index.html': ['bower.json', 'public_html/{,modules/**/}*.js', 'public_html/resources/styles/*.css']
                }
            },
            prepare: {
                options: {
                    min: true,
                    relative: true,
                    template: 'public_html/index.html'
                },
                files: {
                    'public_html/index.tmp': ['.tmp/scripts/**/*.js', '.tmp/styles/*.css', 'bower.json']
                }
            }
        },

Ergebnis:

Sowohl unsere Bower-Abhängigkeiten als auch unsere eigenen Module/CSS Dateien werden automatisch in der index.html Datei eingetragen. Dazu verwendet „injector“ einen bestimmten Bereich innerhalb der HTML Datei den wir selber festlegen müssen. Wir verwenden im Beispiel den Standard Fall:

„<!– injector:js –> bzw. <!– injector:css –> .

Es kommen zwei unterschiedliche targets zum Einsatz:

„development“: fügt die nicht komprimierten Abhängigkeiten direkt in die index.xhtml ein

„prepare“: wird für ein Release verwendet, nutz die .min-Versionen der JavaScript-Dateien und generiert eine eigene temporäre Index-Datei.


Was: Unsere Angular Module für die Komprimierung Vorbereiten

Warum:

AngularJS arbeitet mit Dependency Injection die über Namensgleichheit realisiert ist. Eine Komprimierung der Sourcen wird in aller Regel dazu führen das durch die Änderung der Variablennamen dies nicht mehr möglich ist. Angular bietet dazu eine entsprechende sichere Schreibweise an die wir uns hier generieren lassen (anstatt sie selber zu verwenden)

Womit: grunt-ng-annotate

Wie:

        ngAnnotate: {
            build: {
                files: [{
                        expand: true,
                        src: "public_html/{,modules/**/}*.js",
                        ext: '.annotated.js',
                        dest: '.tmp/scripts'
                    }]
            }
        },

Ergebnis:

In unserem temporären Verzeichnis wird für jedes unserer Angular-Module eine entsprechende .annoted.js-Datei generiert die später komprimiert werden kann. Dazu ersetz das Modul lediglich die kritischen Deklarationen, zum Beispiel:

.controller("home", function ($scope) {…
wird zu:
.controller("home", ["$scope", function ($scope) {….

Was: Komprimieren von CSS und JavaScript-Dateien

Womit: grunt-usemin

Wie:

        useminPrepare: {
            html: 'public_html/index.tmp',
            options: {
                dest: 'dist'
            }
        },
        
        usemin: {
            html: ['dist/{,*/}*.html'],
            css: ['dist/styles/{,*/}*.css'],
            js: ['dist/scripts/{,*/}*.js']
        },

Ergebnis:

Das Modul bietet zwei Tasks: useminPrepare und usemin. ‚useminPrepare‘ führt keine eigenen Änderungen an den Dateien durch stattdessen generiert dieser Task dynamisch zusätzliche Tasks: cssmin,uglify,concat,filerev jeweils mit dem target: ‚generated‘. Genau wie der „grunt-injector“ wird hier mit einem HTML-Kommentar Block in der index.xhtml gearbeitet. Das Ausführen der Tasks sorgt dafür das:

  • alle Referenzen (CSS und JavaScript) die in einem solchen Block definiert sind: <!– build:[type css/js] [Ziel z.B. dist/scripts.js] –>
    • komprimiert werden (uglify für .js und cssmin für .css),
    • zu der entsprechende Datei in unserem ‚dist‘-Order zusammen gefasst werden (concat)

abschließend sorgt ‚usemin‘ dafür das die Referenzen in der index.html auf die minimierten Dateien verweisen.


Was: Komprimieren von HTML Dateien

Womit: grunt-contrib-htmlmin

Wie:

        htmlmin: {
            release: {
                options: {
                    collapseWhitespace: true,
                    conservativeCollapse: true,
                    collapseBooleanAttributes: true,
                    removeCommentsFromCDATA: true
                },
                files: [{
                        expand: true,
                        cwd: 'dist',
                        src: ['{,**/}*.html'],
                        dest: 'dist'
                    }]
            }
        },

Ergebnis:

Alle HTML-Dateien in unserem Release-Verzeichnis (dist) werden nach den konfigurieren Regeln komprimiert um eine geringere Dateigröße zu erreichen. Die zu Verfügung stehenden Optionen sind hier aufgeführt.


 

Zusätzlich haben wir noch einige Tasks um Dateien zu kopieren, die temporären Dateien und Ordner zu löschen und ein Maven-Artefakt (Beitrag) zu erzeugen. Ein entsprechender Prozess könnte dann so aussehen:

    grunt.registerTask("development", [
        // Sourcen für die Entwicklung setzen
        'injector:development',
        // Änderungen an Bower/index.html = neu generieren
        'watch:index'
    ]);

    grunt.registerTask('release', [
        // alte Dateien Löschen
        'clean',
        // Code Quality Check
        'jshint:all',
        // unsere Module für das uglyfy Vorbereiten
        'ngAnnotate:build',
        // CSS Regeln ergänzen
        'postcss:release',
        // Dateien kopieren (html, Bilder, Fonts)
        'copy:release',
        // min Versionen (und unsere ngAnnotatet-Vorbereiteten Sourcen) referenzieren
        'injector:prepare',
        // usemin Konfigurieren
        'useminPrepare',
        // Zusammenfassen
        'concat:generated',
        // CSS minimieren
        'cssmin:generated',
        // JS minimieren
        'uglify:generated',
        // Referenzen ersetzen
        'usemin',
        // HTML minimieren
        'htmlmin:release',
        // Maven Artefakt erzeugen
        'maven:release',
        // Sourcen für die Entwicklung wieder setzen
        'injector:development',
        // temporäre Dateien löschen
        'clean:tmp'
    ]);

Zwei Dinge fallen auf:

  • in vielen Grunt-Prozessen wird man die Registrierung eines lokalen Servers finden (connect + livereload) der wärend der Entwicklung automatisch die Änderungen der Webseite lädt und anzeigt. Netbeans bietet hier einen „embedded Webserver“ der über die Properties des Projektes (>“Run“) aktiviert werden kann. Ein Ausführen des Projektes bietet dank Chrome-Plugin („NetBeans Connector“) eine nahtlose Einbindun in NetBeans:
    • automatischer Refresh der Seite bei Änderungen
    • Netzwerk-Monitor
    • Browser-Logs direkt in NetBeans
    • HTML-Selektor über NetBeans (Anzeige/Änderung Styles)
    • JavaScript Debugging
  • TEST. Bisher führt unserer Prozess noch keinerlei Tests aus. Diesem Thema widmen wir in nächster Zeit einen ganz eigenen Beitrag.

Ergebnis (vorher,nachher)

angular_uglify_vorher_nachher

Es wird deutlich das ein automatisierter Prozess mit Grunt nicht nur für mehr Stabilität und Komfort bei der Entwicklung sorgt sondern vor allem, selbst in diesem extrem kleinen Szenario, in einer massiven Reduzierung der Netzwerklast in der finalen Anwendung.

Das gesamte Beispiel gibt es noch einmal hier.

 

 

Advertisements

Über Dominik Mathmann
Dominik Mathmann arbeitet als Berater, Entwickler und Trainer bei der GEDOPLAN GmbH. Auf Basis seiner langjährigen Erfahrung in der Implementierung von Java-EE-Anwendungen leitet er Seminare, hält Vorträge und unterstützt Kunden bei der Konzeption und Realisierung von Webanwendungen vom Backend bis zum Frontend. Sein derzeitiger Schwerpunkt liegt dabei auf der Entwicklung von JSF-Anwendungen. Er sieht die stärker werdende Position von JavaScript-Frameworks jedoch positiv und beschäftigt sich darüber hinaus mit Webframeworks wie AngularJS.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: