Erweiterte Kompilierung

Übersicht

Die Verwendung des Closure Compilers mit einem compilation_level von ADVANCED_OPTIMIZATIONS bietet bessere Komprimierungsraten als eine Kompilierung mit SIMPLE_OPTIMIZATIONS oder WHITESPACE_ONLY. Die Kompilierung mit ADVANCED_OPTIMIZATIONS sorgt für eine zusätzliche Kompression, da sie Code transformiert und Symbole umbenennt. Dieser aggressivere Ansatz bedeutet jedoch, dass Sie bei der Verwendung von ADVANCED_OPTIMIZATIONS sorgfältiger vorgehen müssen, damit der Ausgabecode genauso funktioniert wie der Eingabecode.

In dieser Anleitung wird erläutert, was der Kompilierungsgrad ADVANCED_OPTIMIZATIONS ist und wie Sie dafür sorgen können, dass Ihr Code nach der Kompilierung mit ADVANCED_OPTIMIZATIONS funktioniert. Außerdem wird das Konzept von extern eingeführt, d. h. ein Symbol, das im Code außerhalb des vom Compilers verarbeiteten Codes definiert wird.

Bevor Sie diese Anleitung lesen, sollten Sie mit dem Kompilieren von JavaScript mit einem der Closure-Compiler-Tools (UI des Compiler-Dienstes, API des Compiler-Dienstes oder Compiler-Anwendung) vertraut sein.

Hinweis zur Terminologie: Das --compilation_level-Befehlszeilen-Flag unterstützt die gängige Abkürzungen ADVANCED und SIMPLE sowie die präziseren ADVANCED_OPTIMIZATIONS und SIMPLE_OPTIMIZATIONS. In diesem Dokument wird das längere Format verwendet, die Namen können jedoch in der Befehlszeile austauschbar verwendet werden.

  1. Noch bessere Komprimierung
  2. So aktivieren Sie ADVANCED_OPTIMIZATIONS
  3. Wichtige Hinweise zur Verwendung von ADVANCED_OPTIMIZATIONS
    1. Code, den Sie behalten möchten, entfernen
    2. Inkonsistente Property-Namen
    3. Zwei Codeabschnitte getrennt kompilieren
    4. Fehlerhafte Verweise zwischen kompiliertem und unkompiliertem Code

Noch bessere Komprimierung

Mit der Standardkompilierungsebene von SIMPLE_OPTIMIZATIONS verringert der Closure-Compiler JavaScript durch Umbenennen lokaler Variablen. Es gibt jedoch andere Symbole als lokale Variablen, die gekürzt werden können. Außerdem gibt es noch andere Möglichkeiten, Code zu kürzen, statt Symbole umzubenennen. Bei der Kompilierung mit ADVANCED_OPTIMIZATIONS wird das gesamte Spektrum an Codeverkleinerungsmöglichkeiten genutzt.

Vergleichen Sie die Ausgaben für SIMPLE_OPTIMIZATIONS und ADVANCED_OPTIMIZATIONS für den folgenden Code:

function unusedFunction(note) {
  alert(note['text']);
}

function displayNoteTitle(note) {
  alert(note['title']);
}

var flowerNote = {};
flowerNote['title'] = "Flowers";
displayNoteTitle(flowerNote);

Durch die Kompilierung mit SIMPLE_OPTIMIZATIONS wird der Code auf folgende Weise gekürzt:

function unusedFunction(a){alert(a.text)}function displayNoteTitle(a){alert(a.title)}var flowerNote={};flowerNote.title="Flowers";displayNoteTitle(flowerNote);

Durch die Kompilierung mit ADVANCED_OPTIMIZATIONS wird der Code vollständig gekürzt:

alert("Flowers");

Beide Skripts generieren eine Benachrichtigung mit dem Wert "Flowers", aber das zweite Skript ist viel kleiner.

Die Ebene ADVANCED_OPTIMIZATIONS geht über die einfache Kürzung von Variablennamen hinaus. Dazu gehören:

  • aggressivere Umbenennung:

    Bei der Kompilierung mit SIMPLE_OPTIMIZATIONS werden nur die Parameter note der Funktionen displayNoteTitle() und unusedFunction() umbenannt, da dies die einzigen Variablen im Skript sind, die für eine Funktion lokal sind. ADVANCED_OPTIMIZATIONS benennt auch die globale Variable flowerNote um.

  • Dead-Code-Entfernung:

    Beim Kompilieren mit ADVANCED_OPTIMIZATIONS wird die Funktion unusedFunction() vollständig entfernt, da sie nie im Code aufgerufen wird.

  • Funktionsaufbau:

    Bei der Kompilierung mit ADVANCED_OPTIMIZATIONS wird der Aufruf von displayNoteTitle() durch die einzelne alert() ersetzt, die den Text der Funktion erstellt. Diese Ersetzung eines Funktionsaufrufs durch den Text der Funktion wird als „Inlining“ bezeichnet. Wenn die Funktion länger oder komplizierter wäre, könnte sie inline das Verhalten des Codes ändern. Der Closure-Compiler stellt jedoch fest, dass die Inline-Inline in diesem Fall sicher ist und Platz spart. Bei der Kompilierung mit ADVANCED_OPTIMIZATIONS werden auch Konstanten und einige Variablen inline eingefügt, wenn dies sicher möglich ist.

Diese Liste ist nur ein Beispiel für die Größenreduzierungstransformationen, die die ADVANCED_OPTIMIZATIONS-Kompilierung durchführen kann.

ADVANCED_OPTIMIZATIONS aktivieren

Die Benutzeroberfläche des Closure-Compiler-Dienstes, die Service API und die Anwendung haben unterschiedliche Methoden zum Festlegen von compilation_level auf ADVANCED_OPTIMIZATIONS.

ADVANCED_OPTIMIZATIONS in der Benutzeroberfläche des Closure-Compilers aktivieren

Klicken Sie auf das Optionsfeld „Erweitert“, um ADVANCED_OPTIMIZATIONS für die Benutzeroberfläche des Closure-Compilers zu aktivieren.

ADVANCED_OPTIMIZATIONS in der Closure Compiler Service API aktivieren

Fügen Sie wie im folgenden Python-Programm einen Anfrageparameter namens compilation_level mit dem Wert ADVANCED_OPTIMIZATIONS ein, um ADVANCED_OPTIMIZATIONS für die Closure Compiler Service API zu aktivieren:

#!/usr/bin/python2.4

import httplib, urllib, sys

params = urllib.urlencode([
    ('code_url', sys.argv[1]),
    ('compilation_level', 'ADVANCED_OPTIMIZATIONS'),
    ('output_format', 'text'),
    ('output_info', 'compiled_code'),
  ])

headers = { "Content-type": "application/x-www-form-urlencoded" }
conn = httplib.HTTPSConnection('closure-compiler.appspot.com')
conn.request('POST', '/compile', params, headers)
response = conn.getresponse()
data = response.read()
print data
conn.close()

ADVANCED_OPTIMIZATIONS in der Closure-Compiler-Anwendung aktivieren

Zum Aktivieren von ADVANCED_OPTIMIZATIONS für die Closure-Compiler-Anwendung fügen Sie das Befehlszeilen-Flag --compilation_level ADVANCED_OPTIMIZATIONS wie im folgenden Befehl ein:

java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js hello.js

Hinweise zur Verwendung von ADVANCED_OPTIMIZATIONS

Im Folgenden finden Sie einige häufige unbeabsichtigte Auswirkungen von ADVANCED_OPTIMIZATIONS und Schritte, mit denen Sie diese verhindern können.

Code entfernen, den Sie behalten möchten

Wenn Sie nur die folgende Funktion mit ADVANCED_OPTIMIZATIONS kompilieren, erstellt Closure Compiler eine leere Ausgabe:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}

Da die Funktion nie in dem JavaScript-Code aufgerufen wird, den Sie an den Compiler übergeben, geht Closure Compiler davon aus, dass dieser Code nicht erforderlich ist.

In vielen Fällen ist dieses Verhalten genau das, was Sie möchten. Wenn Sie beispielsweise Ihren Code mit einer großen Bibliothek kompilieren, kann Closure Compiler bestimmen, welche Funktionen aus dieser Bibliothek Sie tatsächlich verwenden, und die nicht verwendeten verwerfen.

Wenn Sie jedoch feststellen, dass Closure Compiler Funktionen entfernt, die Sie beibehalten möchten, gibt es zwei Möglichkeiten, dies zu verhindern:

  • Verschieben Sie die Funktionsaufrufe in den von Closure Compiler verarbeiteten Code.
  • Fügen Sie externe Elemente für die Funktionen hinzu, die Sie verfügbar machen möchten.

In den nächsten Abschnitten werden die einzelnen Optionen ausführlicher behandelt.

Lösung: Funktionsaufrufe in den vom Closure-Compiler verarbeiteten Code verschieben

Wenn Sie nur einen Teil Ihres Codes mit Closure Compiler kompilieren, kann dies zur unerwünschten Codeentfernung führen. Beispiel: Sie haben eine Bibliotheksdatei, die nur Funktionsdefinitionen enthält, und eine HTML-Datei, die die Bibliothek und den Code zum Aufrufen dieser Funktionen enthält. Wenn Sie in diesem Fall die Bibliotheksdatei mit ADVANCED_OPTIMIZATIONS kompilieren, entfernt Closure Compiler alle Bibliotheksfunktionen.

Die einfachste Lösung für dieses Problem besteht darin, die Funktionen zusammen mit dem Programmteil zu kompilieren, der diese Funktionen aufruft. Closure Compiler entfernt beispielsweise displayNoteTitle() beim Kompilieren des folgenden Programms nicht:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
displayNoteTitle({'myTitle': 'Flowers'});

Die Funktion displayNoteTitle() wird in diesem Fall nicht entfernt, da Closure Compiler erkennt, dass sie aufgerufen wird.

Mit anderen Worten: Sie können das Entfernen unerwünschter Codes verhindern, indem Sie den Einstiegspunkt Ihres Programms in den Code aufnehmen, den Sie an den Composer-Compiler übergeben. Der Einstiegspunkt eines Programms ist die Stelle im Code, an der die Ausführung des Programms beginnt. Im Blumen-Hinweisprogramm aus dem vorherigen Abschnitt werden die letzten drei Zeilen ausgeführt, sobald das JavaScript im Browser geladen wurde. Dies ist der Einstiegspunkt für dieses Programm. Um festzustellen, welcher Code beibehalten werden muss, beginnt Closure Compiler an diesem Einstiegspunkt und verfolgt den Kontrollfluss des Programms von dort aus.

Lösung: Extern für die Funktionen einschließen, die Sie verfügbar machen möchten

Weitere Informationen zu dieser Lösung finden Sie unten und auf der Seite zu extern und Exporten.

Inkonsistente Property-Namen

Die Kompilierung des Compilers ändert niemals Stringliterale in Ihrem Code. Dabei spielt es keine Rolle, welche Kompilierungsebene Sie verwenden. Bei der Kompilierung mit ADVANCED_OPTIMIZATIONS werden Attribute also unterschiedlich behandelt, je nachdem, ob der Code mit einem String darauf zugreift. Wenn Sie Stringverweise auf ein Attribut mit dot-Syntax-Referenzen mischen, benennt Closure Compiler einige Verweise auf dieses Attribut um, andere jedoch nicht. Daher wird Ihr Code wahrscheinlich nicht ordnungsgemäß ausgeführt.

Hier ein Beispielcode:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
var flowerNote = {};
flowerNote.myTitle = 'Flowers';

alert(flowerNote.myTitle);
displayNoteTitle(flowerNote);

Die letzten beiden Anweisungen in diesem Quellcode haben dieselbe Funktion. Wenn Sie den Code jedoch mit ADVANCED_OPTIMIZATIONS komprimieren, erhalten Sie Folgendes:

var a={};a.a="Flowers";alert(a.a);alert(a.myTitle);

Die letzte Anweisung im komprimierten Code erzeugt einen Fehler. Der direkte Verweis auf das Attribut myTitle wurde in a umbenannt, aber der in Anführungszeichen gesetzte Verweis auf myTitle in der Funktion displayNoteTitle wurde nicht umbenannt. Daher bezieht sich die letzte Anweisung auf eine Property des Typs myTitle, die nicht mehr vorhanden ist.

Lösung: Konsistente Attributnamen

Diese Lösung ist ziemlich einfach. Verwenden Sie für jeden Typ oder jedes Objekt ausschließlich Punktsyntax oder Strings in Anführungszeichen. Verwechseln Sie die Syntaxen nicht, insbesondere nicht in Bezug auf dieselbe Property.

Verwenden Sie außerdem nach Möglichkeit die Punktsyntax, da diese bessere Prüfungen und Optimierungen unterstützt. Verwenden Sie den Zugriff auf String-Attribute in Anführungszeichen nur, wenn Closure Compiler keine Umbenennung vornehmen soll, z. B. wenn der Name aus einer externen Quelle stammt, z. B. aus decodiertem JSON.

Zwei Codeabschnitte getrennt kompilieren

Wenn Sie Ihre Anwendung in verschiedene Codeblöcke aufteilen, sollten Sie die Blöcke separat kompilieren. Wenn jedoch zwei Codeblöcke überhaupt interagieren, kann dies zu Schwierigkeiten führen. Auch wenn Sie erfolgreich waren, ist die Ausgabe der beiden Closure-Compiler-Ausführungen nicht kompatibel.

Nehmen wir beispielsweise an, dass eine Anwendung in zwei Teile unterteilt ist: einen Teil, der Daten abruft, und einen Teil, der Daten anzeigt.

Hier ist der Code zum Abrufen der Daten:

function getData() {
  // In an actual project, this data would be retrieved from the server.
  return {title: 'Flower Care', text: 'Flowers need water.'};
}

Code zum Anzeigen der Daten:

var displayElement = document.getElementById('display');
function displayData(parent, data) {
  var textElement = document.createTextNode(data.text);
  parent.appendChild(textElement);
}
displayData(displayElement, getData());

Wenn Sie diese beiden Codeblöcke separat kompilieren, treten verschiedene Probleme auf. Zuerst entfernt der Closure Compiler die Funktion getData() aus den in Entfernen von Code, den Sie behalten möchten beschriebenen Gründen. Zweitens erzeugt der Closure Compiler bei der Verarbeitung des Codes, der die Daten anzeigt, einen schwerwiegenden Fehler.

input:6: ERROR - variable getData is undefined
displayData(displayElement, getData());

Da der Compiler bei der Kompilierung des Codes, der die Daten anzeigt, keinen Zugriff auf die Funktion getData() hat, wird getData als undefiniert behandelt.

Lösung: Gesamten Code für eine Seite kompilieren

Für eine ordnungsgemäße Kompilierung müssen Sie den gesamten Code für eine Seite in einem einzigen Kompilierungslauf kompilieren. Der Closure-Compiler kann mehrere JavaScript-Dateien und JavaScript-Strings als Eingabe akzeptieren, sodass Sie Bibliothekscode und anderen Code in einer einzigen Kompilierungsanfrage übergeben können.

Hinweis:Dieser Ansatz funktioniert nicht, wenn Sie kompilierten und unkompilierten Code kombinieren müssen. Tipps zum Umgang mit solchen Situationen finden Sie unter Fehlerhafte Verweise zwischen kompiliertem und unkompiliertem Code.

Fehlerhafte Referenzen zwischen kompiliertem und unkompiliertem Code

Wenn Sie das Symbol in ADVANCED_OPTIMIZATIONS umbenennen, funktioniert die Kommunikation zwischen dem vom Closure Compiler verarbeiteten Code und jedem anderen Code nicht mehr. Beim Kompilieren werden die im Quellcode definierten Funktionen umbenannt. Jeder externe Code, der Ihre Funktionen aufruft, funktioniert nach der Kompilierung nicht mehr, da er noch auf den alten Funktionsnamen verweist. Ebenso können Verweise in kompiliertem Code auf extern definierte Symbole von Closure Compiler geändert werden.

Beachten Sie, dass „unkompilierter Code“ jeden Code enthält, der als String an die Funktion eval() übergeben wird. Closure Compiler ändert Stringliterale im Code nie. Daher ändert er keine Strings, die an eval()-Anweisungen übergeben werden.

Beachten Sie, dass es sich um ähnliche, aber unterschiedliche Probleme handelt: die Kommunikation zwischen kompilierter und externer Kommunikation und die extern kompilierte Kommunikation. Diese separaten Probleme haben eine gemeinsame Lösung, aber auf jeder Seite gibt es Nuancen. Zur optimalen Nutzung von Closure Compiler ist es wichtig zu verstehen, welchen Fall Sie haben.

Bevor Sie fortfahren, sollten Sie sich mit Erweiterungen und Exporten vertraut machen.

Lösung für den Aufruf von externem Code von kompiliertem Code: mit Extern kompilieren

Wenn Sie Code verwenden, der von einem anderen Skript in Ihre Seite eingefügt wurde, müssen Sie sicherstellen, dass Closure Compiler Ihre Verweise nicht in die in dieser externen Bibliothek definierten Symbole umbenennt. Fügen Sie dazu eine Datei mit den externen Elementen der externen Bibliothek in die Kompilierung ein. Dadurch wird Closure Compiler mitgeteilt, welche Namen Sie nicht verwalten und daher nicht geändert werden können. Ihr Code muss dieselben Namen haben wie die externe Datei.

Häufige Beispiele sind APIs wie die OpenSocial API und die Google Maps API. Wenn Ihr Code beispielsweise die OpenSocial-Funktion opensocial.newDataRequest() ohne die entsprechenden externen Elemente aufruft, wandelt Closure Compiler diesen Aufruf in a.b() um.

Lösung zum Aufrufen von komprimiertem Code von externem Code: Extern implementieren

Wenn Sie JavaScript-Code verwenden, den Sie als Bibliothek wiederverwenden, können Sie mit dem Closure Compiler nur die Bibliothek verkleinern, während Sie dennoch unkompilierten Code zum Aufrufen von Funktionen in der Bibliothek zulassen.

Die Lösung in dieser Situation besteht darin, eine Reihe von externen Komponenten zu implementieren, die die öffentliche API Ihrer Bibliothek definieren. Ihr Code stellt Definitionen für die in diesen extern deklarierten Symbolen bereit. Das sind alle Klassen oder Funktionen, die in den externen Ressourcen erwähnt werden. Es kann auch bedeuten, dass Ihre Klassen Schnittstellen implementieren, die in den extern deklariert sind.

Diese Informationen sind nicht nur für dich selbst, sondern auch für andere nützlich. Nutzer der Bibliothek müssen sie einschließen, wenn sie ihren Code kompilieren, da die Bibliothek ein externes Skript aus ihrer Perspektive darstellt. Stellen Sie sich die externen Ressourcen als Verträge zwischen Ihnen und Ihren Kunden vor.

Zu diesem Zweck müssen Sie beim Kompilieren des Codes auch die externen Elemente in die Kompilierung einbeziehen. Das mag ungewöhnlich erscheinen, da wir extern häufig als "von einem anderen Ort aus" betrachten, es ist jedoch erforderlich, dem Closure Compiler mitzuteilen, welche Symbole verfügbar gemacht werden, damit sie nicht umbenannt werden.

Beachten Sie, dass für den Code, mit dem die externen Symbole definiert werden, möglicherweise Diagnosen mit doppelter Definition angezeigt werden. Bei der Schließung des Compilers wird davon ausgegangen, dass jedes Symbol in den externen Elementen von einer externen Bibliothek bereitgestellt wird. Daher kann der Compiler derzeit nicht verstehen, dass Sie absichtlich eine Definition angeben. Diese Diagnosen können unterdrückt werden. Die Unterdrückung stellt eine Bestätigung dar, dass Sie die API wirklich ausführen.

Außerdem kann für den Compiler Closure prüfen, ob Ihre Definitionen mit den Typen der externen Deklarationen übereinstimmen. Dadurch erhalten Sie eine zusätzliche Bestätigung, dass Ihre Definitionen korrekt sind.