Montag, 3. Dezember 2012

Singing the YUI Blues

Im Web gehört es ja zum guten Ton, spätetens seit Google die PageSpeed Tools herausgebracht hat, zusätzlich zur Kompression des HTTP-Inhaltes CSS und JavaScript zu "minifizieren" (ich sage mal lieber minimieren).

Es gehört sogar nicht nur zum guten Ton sondern bringt meßbar bessere Reaktionen in den Browsern und vermindert für Betreiber größerer Websites signifikant die Traffic-Menge. Das alte Argument, daß nachbearbeiten wäre zu CPU-belastend, gilt heute wirklich nicht mehr, wie ich an einer der größten deutschen (europäischen?) Website erfahren und messen konnte. Hier hat man eher Probleme, es nicht zu tun, da irgendwann auch GBit-Bündel von Netzanschlüssen am Kapazitätsende ist - nicht jedoch die CPUs.

Im Tangram sind natürlich alle statischen Dateien im Bereich CSS und JavaScript bereits beim Zusammenbau minifiziert und beim Ausliefern sollte der JSP/Servlet Container komprimieren - oder ein HTTP-Server davor. In der Google App Enginge erledigt das die Infrastruktur von Google auf irgendeiner Ebene für uns ebenfalls automatisch mit. (Und wenn auch die es im großen tun, ist es wohl wirklich kein Lastproblem mehr...) Für Inhalte aus dem Repository ist eine Lösung enthalten, die das CSS oder JavaScript zur Laufzeit minimiert.

Obwohl man das Minimieren dadurch mit dem Komprimieren des gesamten HTTP-Inhaltes verwechseln kann, wird es auch gerne "Kompression" genannt und ist so der Namensgeber des YUICompressors. Dieser ist - s.o. - lange im Tangram integriert, aber nie ohne Probleme, die wir hier endlich einmal nicht elegant aber funktionierend lösen müssen.

Der YUICompressor bietet sehr gute Ergebnisse, ist eigentlich einfach zu benutzen und sehr weit verbreitet. Damit wäre alle in Ordnung und erledigt, da er auch eine einfach zu benutzende Programmierschnittstelle hat, die im Tangram sowohl im Buildsystem als auch in der späteren Ausspielung zur Laufzeit genutzt wird.

Der erste unangenehme Punkt ist, daß aktuelle Versionen des YUICompressors (mindestens für mich) bisher nicht in öffentlichen Maven-Artefakt-Repositories zu finden sind und wir so mit einer nicht ganz neuen Version unterwegs sind.

Der zweite Punkt tut mehr weh: Im JAR-Archiv des YUICompressors befinden sich Klassen, die einige Klasse aus der eingesetzten JavaScript-Bibliothek - einer ebenfalls älteren Fassung von Rhino - überschreiben sollen. Das ist so natürlich eine recht dreckige Lösung, und sie funktioniert auch normalerweise nicht. Das liegt ganz einfach daran, daß im classpath ein JAR mit Namen yui... selten vor einem JAR mit Namen js... zu finden ist. Eine relativ saubere Behandlung der dreckigen Lösung wäre das umschreiben der Archive beim Zusammenbau des Tangram.

Da aber Tangram eher die möglichkeiten bestehender Software nutzen als zu viel Räder neu erfinden soll, Greifen wir lieber in den Werkzeugkasten und sehen, was unser Werkzeugbestand leisten kann.

In diesem Fall ist Gradle - endlich einmal wieder - unser Freund und bietet Möglichkeiten zur Umbenennung beim Zusammenbau. Da alle Zielsysteme den classpath alphabetisch sortiert zusammestellen, reicht es für uns aus, den YUICompressor einfach ein wenig nach vorne zu schieben. Damit sind dieselben Klassennamen immernoch zweimal im classpath vertreten, aber es wird - Erfahrung im Tangram und großen Systemen (s.o.) - nur die funktionierende Version genutzt.

Damit alle Abhängigkeiten im Buildsystem sauber formuliert bleiben, kleben wir unser Pflaster nur in den Anwendungen auf, die mit Tangram gebaut sind - nicht im System selbst.

Im Detail sieht das so aus:

Wir müssen nun den Anwendungen die genaue Version bekannt machen, die bisher nur im System einfach enthalten war:

ext.yui_version="2.4.6"

Das entsprechen JAR lösen wir aus dem automatisch zusammegestellten classpath heraus:

dependencies {
  .
  .
  .
  // To exclude original YUICompressor jar from output war
  providedCompile "com.yahoo.platform.yui:yuicompressor:$yui_version"
  .
  .
  .
}

Jetzt sind wir sowohl den YUICompressor als auch seine Dependencies los und müssen diese nun "von Hand" wieder einfügen. Da tun wir im war.doFirst Zusatz, der in Tangram-Anwendungen "traditionell" schon angepaßt ist:

war.doFirst {
  // Strange way of overwriting things - it must be the first webapp dependency
  if (configurations.webapp.dependencies.size() > 0) {
    String archiveFileName = configurations.webapp.asPath
    int idx = archiveFileName.indexOf(';')
    if (idx >= 0) {
      archiveFileName = archiveFileName.substring(0, idx)
    } // if
    println "unzipping $archiveFileName"
    ant.unzip(src: archiveFileName, dest: "$buildDir/target") 
  } // if
 
  // Missing: YUICompressor - see base system
  // It's not really that important since we don't have any
  // static Stylesheets or JavaScripts are in the examples.
  copy {
    from 'src/main/webapp'
    into "$buildDir/target"
    include '**/**'
  }
 
  // ...and to again include those YUICompressor's dependencies with a different name

  copy {
    from "$buildDir/target"
    into "$buildDir/target"
    include '**/yui*'
    rename 'yui', 'aaa-yui'
  }
  into ('') {
    from "$buildDir/target"
    include 'WEB-INF/lib/aaa-*'
    include 'WEB-INF/lib/js-*'
  }
 
  into ('') {
    from "$buildDir/target"
    exclude 'WEB-INF/lib/**'
  }
}

Hier wird die WAR-Depencency ausgepackt, ggf. statische Ressourcen minifiziert und dann alles außer den Abhängigkeiten sauber verpackt. Und das "alle außer den Abhängigkeiten" müssen wir nun wieder relativieren, da wir ja den YUICompressor aus den Abhängigkeiten entfernt hatte. Also kommt er explizit als "aaa-yuicompressor" wieder rein zusammen mit der Rhino-Bibliothek. Viola - das war's.

Was ist an der Lösung so toll? Nun, in Maven-Szenarien ist mir nichts besseren Eingefallen, als den YUICompressor unter dem albernen Namen in ein lokalen, eigenes Repository einzufügen. Würde ich das hier tun, wäre Tangram nicht mehr mit öffentlichen Maven-Artefakt-Repositories zu bauen. - Nicht gerade ein gute Idee und eigentlich auch keine für nicht öffentliche Projekte.

Keine Kommentare:

Kommentar veröffentlichen