Going Mobile mit Apache Wicket Teil 2 – Webressourcen und Performance

Im letzten Artikel haben wir uns einen ersten einfachen Prototypen mit Wicket und Bootstrap erstellt.

Das war für den Anfang schon gar nicht schlecht. Ein kritischer Aspekt bei der Erstellung von mobilen Webanwendungen ist Performance.

Wie aber lässt sich die Performance von Webanwendungen sehr einfach messen, ohne viel Geld für Tools auszugeben. Für einen ersten Überblick ist das Tool der Wahl YSlow. YSlow bringt eine sehr schöne Integration in alle gängigen Browser (Es gibt leider wohl derzeit ein Kompatibilitätsproblem mit Firefox 21).
YSlow nimmt sich die Yahoo Web Performance Best Practices zu Herzen, und analysiert auf Basis von 23 der 34 definierten Regeln, wie gut oder auch schlecht eine Webseite abschneidet.Eine erste Analyse

Ein erster Testlauf für unsere Anwendung bringt folgendes hervor:
YSlow Performance Analyse - Wicket

Eine Auswertung der Einzelnen Kriterien ist in folgendem Screen zu sehen.
yslow_kriterien

Auf den ersten Blick scheint unsere Anwendung schon ganz gut abzuschneiden. Das lässt sich ändern, denn die guten Noten basieren derzeit nur darauf, dass die Anwendung bisher nichts macht.
Werfen wir einen Blick auf die Sourcen unserer Anwendung.

Im Header findet sich folgendes:

<script type="text/javascript" src="./wicket/resource/org.apache.wicket.resource.JQueryResourceReference/jquery/jquery-ver-1367063998000.js"></scrip>
<script type="text/javascript" src="./wicket/resource/org.apache.wicket.ajax.AbstractDefaultAjaxBehavior/res/js/wicket-event-jquery-ver-1367063998000.js"></script>
<link rel="stylesheet" type="text/css" href="./wicket/resource/org.apache.wicket.bootstrap.Bootstrap/css/bootstrap-ver-1368356946000.css" />
<link rel="stylesheet" type="text/css" href="./wicket/resource/org.apache.wicket.bootstrap.Bootstrap/css/bootstrap-responsive-ver-1368356946000.css" />
<script type="text/javascript" src="./wicket/resource/org.apache.wicket.bootstrap.Bootstrap/js/bootstrap-ver-1368356946000.js"></script>

Wir laden bisher 3 JavaScript- und 2 CSS-Dateien von Wicket und Bootstrap. Nicht besonders viel und kaum eine Anwendung wird mit diesem Minimalset auskommen (wenn doch, Respekt!). Wir erschweren das Ganze, indem wir einige zusätzliche Ressourcen laden.

Wir definieren im Package de.effectivetrainings 6 zusätzliche JavaScript-Dateien, die nur ausgeben, dass sie geladen wurden (Hierdurch kann auch schön überprüft werden, in welcher Reihenfolge die Dateien abgearbeitet werden, was später noch eine Rolle spielen wird).

console.log("file xx loaded")

Zusätzlich definieren wir in der renderHead-Methode der Klasse Homepage folgende Ressourcen, damit Wicket die Javascript-Dateien in die Homepage einbindet.

        container.getHeaderResponse().render(JavaScriptHeaderItem.forReference(new PackageResourceReference(HomePage.class,"file1.js")));
        container.getHeaderResponse().render(JavaScriptHeaderItem.forReference(new PackageResourceReference(HomePage.class,"file2.js")));
        container.getHeaderResponse().render(JavaScriptHeaderItem.forReference(new PackageResourceReference(HomePage.class,"file3.js")));
        container.getHeaderResponse().render(JavaScriptHeaderItem.forReference(new PackageResourceReference(HomePage.class,"file4.js")));
        container.getHeaderResponse().render(JavaScriptHeaderItem.forReference(new PackageResourceReference(HomePage.class,"file5.js")));
        container.getHeaderResponse().render(JavaScriptHeaderItem.forReference(new PackageResourceReference(HomePage.class,"file6.js")));

Direkt anschließend starten wir einen neuen YSlow-Test und betrachten die Früchte unserer Arbeit.

YSlow - viele Requests

Wir wurden auf ein B-Grade herabgestuft, da wir eines der wichtigsten Kriterien für performante Webanwendungen verletzen.

Make fewer HTTP-Requests

Welche Möglichkeiten haben wir jetzt, dieses Problem zu lösen?

Wicket ResourceBundles – Einfache Reduktion von HTTP-Requests

Mit ResourceBundles (neu in Wicket-6) lassen sich massiv HTTP-Requests einsparen, in dem Wicket serverseitig Ressourcen zusammenfasst und mit nur einem Request ausliefert. Dies sollte immer der erste Schritt sein, um Performance von Webanwendungen zu optimieren. Gerade im Mobile-Bereich tut jeder zusätzliche Request weh.

Natürlich wäre dies auch händisch möglich, meist hat es aber gute Gründe, dass JavaScript-Dateien separiert sind (Komponentenorientierung, Wartbarkeit, Third-Partly Libraries). Es ist immer gut, die Arbeit von Anderen machen zu lassen (so denn hierbei niemand darunter leidet). Und Wicket nimmt uns sehr gerne Arbeit ab.

Aktuell laden wir 9 JavaScript- und 2 CSS-Dateien. Wieso nicht alle JavaScript- und alle CSS-Ressourcen zusammenfassen, und alles über 2 statt 11 Requests laden?

Als Ergebnis wäre ein Request für eine app.js und ein Request für eine app.css doch völlig ausreichend.

ResourceBundles werden in der Application definiert und zwar global für die ganze Anwendung.

Versuchen wir uns zunächst an den CSS-Dateien, da hier nur zwei Ressourcen zusammengefasst werden müssen.

In der init-Methode der Klasse WicketApplication definieren wir folgendes ResourceBundle.

getResourceBundles().addCssBundle(WicketApplication.class,"app.css", Bootstrap.BOOTSTRAP_CSS, Bootstrap.BOOTSTRAP_RESPONSIVE_CSS);

Wir teilen Wicket mit, es gibt ein neues ResourceBundle, was unter der virtuellen Datei “app.css” für den Browser verfügbar sein soll. Außerdem definieren wir, welche Ressourcen in diese Datei kombiniert werden sollen (In diesem Fall die beiden Bootstrap-CSS).

Direkt nach dieser Änderung mache wir den nächsten YSlow-Test.

YSlow - mit Wicket CSS Resource Bundles

Wir haben aus den beiden Bootstrap-CSS eine app.css-Datei gemacht. Aber moment, wir laden in der Klasse HomePage immer noch beide Ressourcen, wieso wird nur das neue app.css-ResourceBundle geladen?

Wicket ist sehr smart. Wann immer eine JavaScript- oder CSS-Ressource angefragt wird prüft Wicket zunächst, ob diese Ressource teil eines definierten ResourceBundles ist. Ist dies der Fall, wir das ResourceBundle gerendert und nicht die angefragte Ressource selbst. Wicket stellt außerdem sicher, dass Ressourcen nicht mehrfach gerendert werden. Wir fragen also sowohl die Bootstrap.css als auch die Bootstrap-Responsive.css im Code an, Wicket rendert aber nur einmalig das app.css-ResourceBundle.

Damit haben wir einen Request eingespart, übrig sind 9 JavaScript-Requests, die es zu optimieren gilt (wir haben nach wie vor ein B-Grade in YSlow!).

Wir definieren ein neues JavaScript-ResourceBundle in der init-Methode der WicketApplication.

getResourceBundles().addJavaScriptBundle(WicketApplication.class,"app.js",
            WicketEventJQueryResourceReference.get(),
            Bootstrap.responsive(),
            new JavaScriptResourceReference(HomePage.class,"file1.js"),
            new JavaScriptResourceReference(HomePage.class,"file2.js"),
            new JavaScriptResourceReference(HomePage.class,"file3.js"),
            new JavaScriptResourceReference(HomePage.class,"file4.js"),
            new JavaScriptResourceReference(HomePage.class,"file5.js"),
            new JavaScriptResourceReference(HomePage.class,"file6.js")
        );

Wir kombinieren sowohl die Wicket-JavaScript-, die Bootstrap-Ressourcen
als auch die zuvor definierten fileX.js-Ressourcen in ein app.js-ResourceBundle.

Ein kleiner Hinweis: So haben wir doppelte Datenhaltung, wir referenzieren den Dateinamen file1.js an zwei Stellen.
Einmal in der WicketApplication bei der Definition des ResourceBundles, einmal in der Homepage
beim eigentlichen Anfordern der Ressource. Idealerweise definiert man für jede Ressourcen eine eigene ResourceReferenz-Klasse, die
von überall referenziert werden kann.

Zeit für den nächsten YSlow-Testrun.

yslow-komprimierte-js-resourcen

Die Seite fragt derzeit noch zwei Ressourcen an. Wieso aber zwei? Ich persönlich würde davon ausgehen, dass die Dependencies, die in RessourceReferenzen definiert sind auch für das Auflösen in ResourceBundles verwendet werden. Da lässt sich aber vortrefflich diskutieren.

Wir referenzieren die WicketEventJQueryResourceReference. Dies ist die Kompatibilitäts-Bibliothek für die Integration von JQuery in Wicket. Diese ResourceReference definiert eine Abhängigkeit auf JQuery selbst. Wird also die WicketEventJQueryResourceReference angefragt, sorgt Wicket dafür, dass alle Abhängigkeiten vorher geladen sind.

Da wir auch den letzten Request einsparen möchten, definieren wir zusätzlich die JQueryResourceReference als Abhängigkeit im RessourceBundle.

getResourceBundles().addJavaScriptBundle(WicketApplication.class, "app.js",
            WicketEventJQueryResourceReference.get(),
            JQueryResourceReference.get(),
            Bootstrap.responsive(),
            new JavaScriptResourceReference(HomePage.class, "file1.js"),
            new JavaScriptResourceReference(HomePage.class, "file2.js"),
            new JavaScriptResourceReference(HomePage.class, "file3.js"),
            new JavaScriptResourceReference(HomePage.class, "file4.js"),
            new JavaScriptResourceReference(HomePage.class, "file5.js"),
            new JavaScriptResourceReference(HomePage.class, "file6.js")
        );

Ich habe die Abhängigkeite absichtlich in der falschen Reihenfolge definiert, denn starten wir die Anwendung jetzt erscheint folgendes:

Uncaught ReferenceError: jQuery is not defined

Achtung: Bei der Verwendung von RessourceBundles werden die Abhängigkeiten nicht richtig gehandhabt und wir als Entwickler sind in der Pflicht, die Ressourcen in der richtigen Reihenfolge einzubinden.
Im obigen Beispiel definieren wir die WicketEventJQueryResourceReference vor der JQueryResourceReference. Wicket gibt uns keinen Hinweis, dass hier ein Konflikt vorliegt.

Die korrekte Einbindung sieht so aus:

getResourceBundles().addJavaScriptBundle(WicketApplication.class, "app.js",
            JQueryResourceReference.get(),
            WicketEventJQueryResourceReference.get(),
            Bootstrap.responsive(),
            new JavaScriptResourceReference(HomePage.class, "file1.js"),
            new JavaScriptResourceReference(HomePage.class, "file2.js"),
            new JavaScriptResourceReference(HomePage.class, "file3.js"),
            new JavaScriptResourceReference(HomePage.class, "file4.js"),
            new JavaScriptResourceReference(HomePage.class, "file5.js"),
            new JavaScriptResourceReference(HomePage.class, "file6.js")
        );

Höchste Zeit für einen letzten YSlow Test.

yslow grade A

Und am allerwichtigsten

success

Fazit

Geht es um Performance-Optimierung sind ResourceBundles die erste Verteidigungslinie.

Man muss allerdings ein wenig aufpassen, was man tut, denn Wicket “kopiert” alle für ein ResourceBundle notwendigen Dateien stur in das virtuelle File. Nehmen wir testweise die Semikolons aus unseren fileXX.js-Dateien heraus, geht gar nichts mehr mit dem Fehler:

Uncaught SyntaxError: Unexpected identifier

Warum? Weil beim Erstellen des ResourceBundles dann folgendes ensteht:

console.log("file2")console.log("file3")

Eigentlich sollte Wicket für jede inkludierte Datei zumindest einen Zeilenumbruch am Ende einfügen. Ich werde hierzu gleich mal einen Bug inkl. Patch einstellen. Achtet also zumindest für die aktuelle und vielleicht auch die kommende Version noch darauf, das JavaScript mit Semikolons und am Ende einem Zeilenumbruch abzuschliessen (aber das tut Ihr ja sowieso schon, oder?)

Was interessiert Euch für den nächsten Teil? Ich habe noch im Angebot

  • Page Performance Imrovement über die alternative Platzierung von JavaScript-Resourcen
  • Einbindung von CSS- und JavaScript Kompressoren
  • Mögliche Integration mit Wro4J
  • Andere Vorschläge?

Links

YSlow Addon Firefox
YSlow Addon Chrome
Yahoo WebPerformance Best Practices
Source-Code auf Github
Der eingestellte Wicket BUG

Und zu guter Letzt, noch viel genauer können wir über das Thema im Effective Wicket Workshop sprechen. Rufen Sie mich doch einfach an.

Effective Trainings & Consulting - Martin Dilger



Hat Ihnen dieser Blog-Eintrag gefallen? Ich stelle in diesem Blog Informationen über Tools, Frameworks und Werkzeuge zur Verfügung, die mich produktiver machen. Vielleicht kann ich auch Ihnen helfen, produktiver zu werden.


Ich unterstütze Sie als freier Mitarbeiter bei der Entwicklung von Software-Projekten, Agiler Arbeit sowie Schulungen / Fortbildungen.


Jeden Tag ein bisschen produktiver - ab heute