Stichworte: (eigenes und /contrib) JavaScript, Frontend, JSmin Kompression, Prototype, Ajax, Temporäre Dateien
Hallo zusammen,
ich bin grad dabei einige eigene Plugins zu schreiben und auf die Frage gestoßen, was wohl eine "best practice" ist, um JavaScript automatisch komprimiert im Frontend zu benutzen.
Meinen Lösungsansatz hab ich hier mal beschrieben und wäre dankbar für Kommentare, ob dies einer sinnvollen Herangehensweise entspricht, oder ob ich dasselbe vielleicht mit 2 Zeilen TS lösen könnte. Weiß man ja nie 😉
Szenario:
Ich möchte einen Username-Suchfeld-Plugin haben, der beim eintippen ähnliche Nutzernamen vorschlägt, die man dann auswählen kann um zu deren Profilseiten zu navigieren. (Beispiel in meiner Joomla-Implementation: User Suche).
Herangehensweise:
Da mir der xajax Plugin nicht performant genug ist habe ich mich für die eID-Methode entschieden (in eigenen Tests hat xajax 5 mal so lange gedauert, einen einfachen String zurückzugeben (1s vs 200ms)).
Der eigentliche Ajax Request soll über die in Typo3 ja bereits enthaltene Prototype Library getätigt werden mit relativ wenig zusätzlichen client-seitigem code.
Frage:
Die Prototype Library ist derzeit 124kb groß. Komprimiert man sie mit JSmin, welches ja ebenfalls in Typo3 enthalten ist, sind es nurnoch 92kb. Wie ist nun der richtige Weg, um die Typo3 internen libraries aus dem Frontend heraus zu benutzen?
Eigentlich ist die Javascript Minimierung (minify) seit Typo4.2 default mäßig aktiviert. Jedoch merkt man davon im Frontend nichts. Das Backend hat einfache Methoden, die einem zum Beispiel das Laden der System JS-libraries ermöglichen und ich denke, dass die dann auch automatisch den JSmin minify Algorithmus durchlaufen. Auf Frontend Seite wird man jedoch (fast) allein gelassen.
Lösungsvorschlag für eigenen Code:
Die Methode t3lib_div::minifyJavascript() bietet Zugriff auf die JSmin library und kann sehr gut benutzt werden, um komprimierten JS code in der HTML Seite auszuliefern. Der Plugin Code kann dabei so aussehen:
$script = '
function myRequest() {
var url = "index.php";
var par1 = "jim";
var pars = "eID=usersearch&name="+par1;
var myAjax = new Ajax.Request(url, {method: "get", parameters: pars, onComplete: myResponse});
}
function myResponse(orgRequest) {
//wenn Antwort XML: var xmldoc = orgRequest.responseXML;
var responseText = orgRequest.responseText;
document.getElementById("some_id").innerHTML = responseText;
}';
//minify using JSmin
$script = t3lib_div::minifyJavaScript($script);
//add to page
$GLOBALS['TSFE']->setJS($this->extKey,$script);
Resultat ist, dass die HTML Seite am Ende schön minimalisierten JavaScript Code enthält.
Nachteile:
- Der Code muss jedes Mal durch JSmin geschickt werden wenn die Seite generiert wird.
- Es geht nur für Code der direkt vorliegt und nicht in externen JS-Dateien steckt, zum Beispiel in eigenen aufwändigeren Skripten, bzw. in 3rd-Party Frameworks wie Prototype.
Effizienter Lösungsvorschlag für komplexere Setups:
Es wäre schön, den JavaScript code, nicht jedes Mal komprimieren zu müssen, sondern die komprimierte Version, vorzugsweise natürlich automatisch, abzuspeichern und einzubinden.
Typo3 bietet dazu folgende interessante Möglichkeit: inline2TempFile
Ersetzt man im obigen Beispiel die unteren Zeilen durch:
//minify using JSmin
$script = t3lib_div::minifyJavaScript($script, $error);
//generate tmp file from inline string
$inlineTmpfile = TSpagegen::inline2TempFile($script,'js');
//add to site header
$GLOBALS['TSFE']->additionalHeaderData['sup_games'] = $inlineTmpfile;
Die inline2TempFile Methode funktioniert so, dass ein md5 Hash des Strings gebildet wird, der in einer temporären Datei abgelegt werden soll (funktioniert für Javascript und CSS Dateien) und aus diesem wird ein entsprechender Dateiname generiert. Sollte diese Datei bereits im typo3temp Ordner existieren, wird eine in die entsprechenden script oder link tags gewrappte Referenz auf diese Datei zurückgegeben. Bsp:
<script type="text/javascript" src="typo3temp/javascript_26d6cd95e6.js"></script>
Dieser kann natürlich einfach als additionalHeaderData hinzugefügt werden.
Um nun aber eine externe Datei so abzuspeichern würde man nichts gewinnen, da der md5 Hash ja auf dem bereits minify'ten String gebildet wird um die temporäre Datei zu speichern. Man müsste also beispielsweise jedes Mal, die Prototype library öffnen, lesen und minifizieren um zu sehen, dass man es schonmal gemacht hat. --> Blöd. Insbesondere deswegen, weil JSmin für eine komplexe Datei wie die prototype.js schon ein paar Sekunden benötigen kann.
Besser: Man schreibt eine abgewandelte inline2TempFile und benutzt diese:
function main($content,$conf) {
//...
$minProt = $this->minifyFile(PATH_typo3.'contrib/prototype/prototype.js');
$GLOBALS['TSFE']->additionalHeaderData['sup_games'] = $minProt;
return $this->pi_wrapInBaseClass($content);
}
function minifyFile($file, &$error='') {
//generate md5 hash based on filename not content
$hashedName = 'typo3temp/javascript_'.substr(md5($file),0,10).'.js';
$output = '<script type="text/javascript" src="'.htmlspecialchars($GLOBALS['TSFE']-">absRefPrefix.$hashedName).'"></script>';';
// Write file if not exists:
if (!@is_file(PATH_site.$hashedName)) {
//Einlesen der Originalversion:
$str = file_get_contents($file);
//minify
$str = t3lib_div::minifyJavaScript($str, $error);
t3lib_div::writeFile(PATH_site.$hashedName,$str);
}
return $output;
}
Mit dieser Methode, wird der md5-Hash auf den Dateinamen der Javascript Datei gebildet anstatt auf den Inhalt. Es ist somit möglich, JS-code einfach in JS-Dateien auszulagern, die bei Generierung der Seite automatisch minifiziert werden, dies jedoch nur, wenn es nicht bereits geschehen sein sollte.
Vorteil: Gute Methode, um beliebige JS-Script Dateien automatisch und bei Bedarf zu minifizieren und so auszuliefern.
Nachteil: Es ist abhängig vom Dateinamen. Sollte sich also der Inhalt der Datei ändern (Development-Setup / Typo3 Update), so muss der Inhalt des Temp Ordners natürlich gelöscht werden, damit die Datei neu erstellt wird.
Kommentare? 🙂
Grüße,
EM