Übersicht
Wenn Sie den Closure Compiler mit einem compilation_level
von ADVANCED_OPTIMIZATIONS
verwenden, erzielen Sie bessere Komprimierungsraten als bei der Kompilierung mit SIMPLE_OPTIMIZATIONS
oder WHITESPACE_ONLY
. Bei der Kompilierung mit ADVANCED_OPTIMIZATIONS
wird eine zusätzliche Komprimierung erreicht, da der Code aggressiver transformiert und Symbole umbenannt werden. Dieser aggressivere Ansatz bedeutet jedoch, dass Sie bei der Verwendung von ADVANCED_OPTIMIZATIONS
sorgfältiger vorgehen müssen, um sicherzustellen, dass der Ausgabecode genauso funktioniert wie der Eingabecode.
In diesem Tutorial wird veranschaulicht, was die Kompilierungsebene ADVANCED_OPTIMIZATIONS
bewirkt und was Sie tun können, damit Ihr Code nach der Kompilierung mit ADVANCED_OPTIMIZATIONS
funktioniert. Außerdem wird das Konzept von extern eingeführt: ein Symbol, das in Code definiert ist, der nicht vom Compiler verarbeitet wird.
Bevor Sie diese Anleitung lesen, sollten Sie mit dem Kompilieren von JavaScript mit einem der Closure Compiler-Tools wie der Java-basierten Compiler-Anwendung vertraut sein.
Hinweis zur Terminologie: Das Befehlszeilenflag --compilation_level
unterstützt die häufiger verwendeten Abkürzungen ADVANCED
und SIMPLE
sowie die präziseren ADVANCED_OPTIMIZATIONS
und SIMPLE_OPTIMIZATIONS
.
In diesem Dokument wird die Langform verwendet, aber die Namen können in der Befehlszeile austauschbar verwendet werden.
- Noch bessere Komprimierung
- ADVANCED_OPTIMIZATIONS aktivieren
- Worauf Sie bei der Verwendung von ADVANCED_OPTIMIZATIONS achten sollten
Noch bessere Komprimierung
Mit der Standardkompilierungsebene SIMPLE_OPTIMIZATIONS
verkleinert der Closure Compiler JavaScript-Code, indem er lokale Variablen umbenennt. Es gibt jedoch auch andere Symbole als lokale Variablen, die verkürzt werden können, und es gibt andere Möglichkeiten, Code zu verkleinern, als Symbole umzubenennen. Bei der Kompilierung mit ADVANCED_OPTIMIZATIONS
werden alle Möglichkeiten zur Codeverkleinerung 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);
Bei der Kompilierung mit SIMPLE_OPTIMIZATIONS
wird der Code auf Folgendes verkürzt:
function unusedFunction(a){alert(a.text)}function displayNoteTitle(a){alert(a.title)}var flowerNote={};flowerNote.title="Flowers";displayNoteTitle(flowerNote);
Bei der Kompilierung mit ADVANCED_OPTIMIZATIONS
wird der Code vollständig auf Folgendes verkürzt:
alert("Flowers");
Beide Skripts geben die Meldung "Flowers"
aus, aber das zweite Skript ist viel kürzer.
Die ADVANCED_OPTIMIZATIONS
-Ebene geht in vielerlei Hinsicht über das einfache Kürzen von Variablennamen hinaus:
- Aggressivere Umbenennung:
Bei der Kompilierung mit
SIMPLE_OPTIMIZATIONS
werden nur dienote
-Parameter der FunktionendisplayNoteTitle()
undunusedFunction()
umbenannt, da dies die einzigen Variablen im Skript sind, die lokal für eine Funktion sind. MitADVANCED_OPTIMIZATIONS
wird auch die globale VariableflowerNote
umbenannt. - Entfernen von nicht verwendetem Code:
Bei der Kompilierung mit
ADVANCED_OPTIMIZATIONS
wird die FunktionunusedFunction()
vollständig entfernt, da sie im Code nie aufgerufen wird. - Inline-Funktion:
Bei der Kompilierung mit
ADVANCED_OPTIMIZATIONS
wird der Aufruf vondisplayNoteTitle()
durch die einzelnealert()
ersetzt, aus der der Funktionsrumpf besteht. Das Ersetzen eines Funktionsaufrufs durch den Funktionskörper wird als „Inlining“ bezeichnet. Wenn die Funktion länger oder komplizierter wäre, könnte sich durch das Inlining das Verhalten des Codes ändern. Der Closure Compiler stellt jedoch fest, dass das Inlining in diesem Fall sicher ist und Speicherplatz spart. Bei der Kompilierung mitADVANCED_OPTIMIZATIONS
werden auch Konstanten und einige Variablen inline eingefügt, wenn dies sicher möglich ist.
Diese Liste enthält nur einige Beispiele für die Transformationsmöglichkeiten, die bei der ADVANCED_OPTIMIZATIONS
-Kompilierung zur Reduzierung der Größe genutzt werden können.
ADVANCED_OPTIMIZATIONS aktivieren
Wenn Sie ADVANCED_OPTIMIZATIONS
für die Closure Compiler-Anwendung aktivieren möchten, fügen Sie das Befehlszeilenflag --compilation_level ADVANCED_OPTIMIZATIONS
ein, wie im folgenden Befehl:
java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js hello.js
Worauf Sie bei der Verwendung von ADVANCED_OPTIMIZATIONS achten sollten
Im Folgenden sind einige häufige unbeabsichtigte Auswirkungen von ADVANCED_OPTIMIZATIONS aufgeführt sowie Maßnahmen, die Sie ergreifen können, um sie zu vermeiden.
Entfernen von Code, den Sie behalten möchten
Wenn Sie nur die Funktion unten mit ADVANCED_OPTIMIZATIONS
kompilieren, gibt Closure Compiler eine leere Ausgabe zurück:
function displayNoteTitle(note) { alert(note['myTitle']); }
Da die Funktion im JavaScript, das Sie an den Compiler übergeben, nie aufgerufen wird, geht Closure Compiler davon aus, dass dieser Code nicht benötigt wird.
In vielen Fällen ist dieses Verhalten genau das, was Sie möchten. Wenn Sie Ihren Code beispielsweise zusammen mit einer großen Bibliothek kompilieren, kann der Closure Compiler ermitteln, welche Funktionen aus dieser Bibliothek Sie tatsächlich verwenden, und die nicht verwendeten Funktionen verwerfen.
Wenn Sie jedoch feststellen, dass Closure Compiler Funktionen entfernt, die Sie behalten möchten, gibt es zwei Möglichkeiten, dies zu verhindern:
- Verschieben Sie Ihre Funktionsaufrufe in den Code, der von Closure Compiler verarbeitet wird.
- Fügen Sie Externs für die Funktionen ein, die Sie verfügbar machen möchten.
In den nächsten Abschnitten werden die einzelnen Optionen genauer beschrieben.
Lösung: Funktionsaufrufe in den Code verschieben, der vom Closure Compiler verarbeitet wird
Wenn Sie nur einen Teil Ihres Codes mit Closure Compiler kompilieren, kann es vorkommen, dass unerwünschter Code entfernt wird. Sie haben beispielsweise eine Bibliotheksdatei, die nur Funktionsdefinitionen enthält, und eine HTML-Datei, die die Bibliothek enthält und den Code, mit dem diese Funktionen aufgerufen werden. Wenn Sie die Bibliotheksdatei in diesem Fall mit ADVANCED_OPTIMIZATIONS
kompilieren, entfernt Closure Compiler alle Bibliotheksfunktionen.
Die einfachste Lösung für dieses Problem besteht darin, Ihre Funktionen zusammen mit dem Teil Ihres Programms zu kompilieren, der diese Funktionen aufruft.
Closure Compiler entfernt beispielsweise displayNoteTitle()
nicht, wenn das folgende Programm kompiliert wird:
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 verhindern, dass unerwünschter Code entfernt wird, indem Sie den Einstiegspunkt Ihres Programms in den Code aufnehmen, den Sie an Closure Compiler übergeben. Der Einstiegspunkt eines Programms ist die Stelle im Code, an der die Ausführung des Programms beginnt. Im Programm für Notizen zu Blumen aus dem vorherigen Abschnitt werden die letzten drei Zeilen beispielsweise ausgeführt, sobald das JavaScript im Browser geladen wird. Dies ist der Einstiegspunkt für dieses Programm. Um zu ermitteln, welcher Code beibehalten werden muss, beginnt Closure Compiler an diesem Einstiegspunkt und verfolgt den Kontrollfluss des Programms von dort aus vorwärts.
Lösung: Externs für die Funktionen einfügen, die Sie verfügbar machen möchten
Weitere Informationen zu dieser Lösung finden Sie unten und auf der Seite zu externs und exports.
Inkonsistente Property-Namen
Bei der Closure Compiler-Kompilierung werden String-Literale in Ihrem Code nie geändert, unabhängig von der verwendeten Kompilierungsebene. Das bedeutet, dass die Kompilierung mit ADVANCED_OPTIMIZATIONS
Eigenschaften unterschiedlich behandelt, je nachdem, ob Ihr Code mit einem String darauf zugreift. Wenn Sie String-Referenzen auf ein Attribut mit Referenzen in Punktsyntax mischen, benennt Closure Compiler einige der Referenzen auf dieses Attribut um, andere jedoch nicht. Daher wird Ihr Code wahrscheinlich nicht richtig ausgeführt.
Beispiel:
function displayNoteTitle(note) { alert(note['myTitle']); } var flowerNote = {}; flowerNote.myTitle = 'Flowers'; alert(flowerNote.myTitle); displayNoteTitle(flowerNote);
Die letzten beiden Anweisungen in diesem Quellcode bewirken genau dasselbe. 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 führt zu einem Fehler. Die direkte Referenz auf die Property myTitle
wurde in a
umbenannt, die zitierte Referenz auf myTitle
in der Funktion displayNoteTitle
jedoch nicht. Daher bezieht sich die letzte Anweisung auf eine myTitle
-Property, die nicht mehr vorhanden ist.
Lösung: Konsistente Property-Namen verwenden
Diese Lösung ist recht einfach. Verwenden Sie für einen bestimmten Typ oder ein bestimmtes Objekt entweder die Punktsyntax oder in Anführungszeichen gesetzte Strings. Mischen Sie die Syntaxen nicht, insbesondere nicht für dieselbe Property.
Verwenden Sie nach Möglichkeit die Punktsyntax, da sie bessere Prüfungen und Optimierungen unterstützt. Der Zugriff auf Eigenschaften mit in Anführungszeichen gesetzten Strings sollte nur verwendet werden, wenn Closure Compiler keine Umbenennung vornehmen soll, z. B. wenn der Name aus einer externen Quelle stammt, etwa aus decodiertem JSON.
Zwei Codeabschnitte separat kompilieren
Wenn Sie Ihre Anwendung in verschiedene Codeblöcke aufteilen, möchten Sie die Blöcke möglicherweise separat kompilieren. Wenn zwei Codeblöcke jedoch interagieren, kann dies zu Problemen führen. Selbst wenn Sie es schaffen, ist die Ausgabe der beiden Closure Compiler-Ausführungen nicht kompatibel.
Angenommen, eine Anwendung ist in zwei Teile unterteilt: 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.'}; }
Hier ist der 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 versuchen, diese beiden Codeblöcke separat zu kompilieren, treten mehrere 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 gibt der Closure Compiler einen schwerwiegenden Fehler aus, wenn er den Code verarbeitet, der die Daten anzeigt.
input:6: ERROR - variable getData is undefined displayData(displayElement, getData());
Da der Compiler beim Kompilieren des Codes, mit dem die Daten angezeigt werden, keinen Zugriff auf die Funktion getData()
hat, wird getData
als nicht definiert behandelt.
Lösung: Gesamten Code für eine Seite zusammenstellen
Damit die Kompilierung richtig erfolgt, müssen Sie den gesamten Code für eine Seite in einem einzigen Kompilierungsvorgang kompilieren. Der Closure Compiler kann mehrere JavaScript-Dateien und JavaScript-Strings als Eingabe akzeptieren. Sie können also Bibliothekscode und anderen Code zusammen in einer einzigen Kompilierungsanfrage übergeben.
Hinweis:Diese Methode funktioniert nicht, wenn Sie kompilierten und nicht kompilierten Code mischen müssen. Weitere Informationen zum Umgang mit dieser Situation
Fehlerhafte Referenzen zwischen kompiliertem und nicht kompiliertem Code
Durch das Umbenennen von Symbolen in ADVANCED_OPTIMIZATIONS
wird die Kommunikation zwischen Code, der vom Closure Compiler verarbeitet wird, und anderem Code unterbrochen. Bei der Kompilierung werden die in Ihrem Quellcode definierten Funktionen umbenannt. Externer Code, der Ihre Funktionen aufruft, funktioniert nach der Kompilierung nicht mehr, da er sich weiterhin auf den alten Funktionsnamen bezieht. Auch Verweise in kompiliertem Code auf extern definierte Symbole können von Closure Compiler geändert werden.
„Nicht kompilierter Code“ umfasst jeden Code, der als String an die Funktion eval()
übergeben wird. Der Closure Compiler ändert Stringliterale im Code nie. Daher werden auch Strings, die an eval()
-Anweisungen übergeben werden, nicht geändert.
Beachten Sie, dass es sich hierbei um verwandte, aber unterschiedliche Probleme handelt: die Aufrechterhaltung der Kommunikation von kompiliert zu extern und die Aufrechterhaltung der Kommunikation von extern zu kompiliert. Diese separaten Probleme haben eine gemeinsame Lösung, aber es gibt Nuancen auf beiden Seiten. Damit Sie Closure Compiler optimal nutzen können, müssen Sie wissen, welcher Fall bei Ihnen vorliegt.
Bevor Sie fortfahren, sollten Sie sich mit Externs und Exports vertraut machen.
Lösung für Aufrufe von externem Code aus kompiliertem Code: Kompilieren mit Externs
Wenn Sie Code verwenden, der von einem anderen Skript auf Ihre Seite eingefügt wird, müssen Sie darauf achten, dass der Closure Compiler Ihre Verweise auf die in dieser externen Bibliothek definierten Symbole nicht umbenennt. Fügen Sie dazu eine Datei mit den Externs für die externe Bibliothek in die Kompilierung ein. So wird Closure Compiler mitgeteilt, welche Namen Sie nicht steuern und daher nicht geändert werden können. In Ihrem Code müssen dieselben Namen verwendet werden wie in der externen Datei.
Häufige Beispiele hierfür sind APIs wie die OpenSocial API und die Google Maps API. Wenn in Ihrem Code beispielsweise die OpenSocial-Funktion opensocial.newDataRequest()
aufgerufen wird, wandelt Closure Compiler diesen Aufruf ohne die entsprechenden Externs in a.b()
um.
Lösung für den Aufruf von kompiliertem Code aus externem Code: Externs implementieren
Wenn Sie JavaScript-Code haben, den Sie als Bibliothek wiederverwenden, möchten Sie möglicherweise nur die Bibliothek mit dem Closure Compiler verkleinern, während nicht kompilierter Code weiterhin Funktionen in der Bibliothek aufrufen kann.
Die Lösung in dieser Situation besteht darin, eine Reihe von Externs zu implementieren, die die öffentliche API Ihrer Bibliothek definieren. In Ihrem Code werden Definitionen für die in diesen Externs deklarierten Symbole bereitgestellt. Das bedeutet, dass alle Klassen oder Funktionen, die in Ihren Externs erwähnt werden, Das kann auch bedeuten, dass Ihre Klassen in den Externs deklarierte Schnittstellen implementieren.
Diese externen Links sind nicht nur für Sie, sondern auch für andere Nutzer nützlich. Nutzer Ihrer Bibliothek müssen sie einbinden, wenn sie ihren Code kompilieren, da Ihre Bibliothek aus ihrer Sicht ein externes Skript darstellt. Betrachten Sie die externen Dateien als Vertrag zwischen Ihnen und Ihren Kunden. Beide benötigen eine Kopie.
Achten Sie daher darauf, dass Sie beim Kompilieren Ihres Codes auch die Externs einbeziehen. Das mag ungewöhnlich erscheinen, da wir Externs oft als „von woanders kommend“ betrachten. Es ist jedoch erforderlich, dem Closure Compiler mitzuteilen, welche Symbole Sie verfügbar machen, damit sie nicht umbenannt werden.
Eine wichtige Einschränkung ist, dass Sie möglicherweise Diagnosen vom Typ „duplicate definition“ (doppelte Definition) für den Code erhalten, der die externen Symbole definiert. Der Closure Compiler geht davon aus, dass jedes Symbol in den Externs von einer externen Bibliothek bereitgestellt wird. Er kann derzeit nicht erkennen, dass Sie absichtlich eine Definition bereitstellen. Diese Diagnosen können unterdrückt werden. Die Unterdrückung kann als Bestätigung dafür angesehen werden, dass Sie Ihre API wirklich erfüllen.
Außerdem kann der Closure Compiler prüfen, ob Ihre Definitionen mit den Typen der externen Deklarationen übereinstimmen. So erhalten Sie eine zusätzliche Bestätigung, dass Ihre Definitionen korrekt sind.