Einführung in JavaScript Source Maps

Ryan Seddon

Haben Sie sich schon einmal gewünscht, dass Ihr clientseitiger Code auch nach dem Kombinieren und Minimieren lesbar und vor allem debugbar bleibt, ohne dass sich die Leistung verschlechtert? Mit Quellkarten ist das jetzt möglich.

Quellzuordnungen bieten die Möglichkeit, eine kombinierte/minimierte Datei einem nicht erstellten Zustand zuzuordnen. Wenn Sie einen Build für die Produktion erstellen, werden beim Minimieren und Kombinieren Ihrer JavaScript-Dateien eine Sourcemap mit Informationen zu Ihren ursprünglichen Dateien generiert. Wenn Sie eine bestimmte Zeile und Spaltennummer in Ihrem generierten JavaScript abfragen, können Sie in der Quellkarte nachsehen, wo sich der ursprüngliche Speicherort befindet. Mit Entwicklertools (derzeit WebKit-Nightly-Builds, Google Chrome oder Firefox 23 und höher) kann die Quellkarte automatisch geparst werden, sodass es so aussieht, als würden Sie nicht minimierte und nicht kombinierte Dateien ausführen.

In der Demo können Sie mit der rechten Maustaste auf eine beliebige Stelle im Textfeld mit der generierten Quelle klicken. Wenn Sie „Ursprünglichen Speicherort abrufen“ auswählen, wird die Quellkarte durch Übergeben der generierten Zeilen- und Spaltennummer abgefragt und die Position im ursprünglichen Code zurückgegeben. Die Konsole muss geöffnet sein, damit Sie die Ausgabe sehen können.

Beispiel für die Verwendung der JavaScript-Quellzuordnungsbibliothek von Mozilla

Praxis

Bevor Sie sich die folgende praktische Implementierung von Quellkarten ansehen, müssen Sie die Funktion für Quellkarten entweder in Chrome Canary oder WebKit Nightly aktivieren. Klicken Sie dazu im Bereich „Entwicklertools“ auf das Zahnradsymbol für die Einstellungen und aktivieren Sie die Option „Quellkarten aktivieren“.

So aktivieren Sie Quellzuordnungen in den WebKit-Entwicklertools.

In Firefox 23 und höher sind Quellkarten in den integrierten Entwicklertools standardmäßig aktiviert.

So aktivieren Sie Quellzuordnungen in den Firefox-Entwicklertools.

Warum sollten mir Quellzuordnungen wichtig sein?

Derzeit funktioniert die Quellzuordnung nur zwischen unkomprimiertem/kombiniertem JavaScript und komprimiertem/nicht kombiniertem JavaScript. Die Zukunft sieht jedoch vielversprechend aus, da es bereits Sprachen gibt, die in JavaScript kompiliert werden, z. B. CoffeeScript, und es sogar möglich ist, CSS-Preprocessor wie SASS oder LESS zu unterstützen.

Künftig können wir fast jede Sprache problemlos verwenden, als wäre sie nativ im Browser mit Quellkarten unterstützt:

  • CoffeeScript
  • ECMAScript 6 und höher
  • SASS/LESS und andere
  • So ziemlich jede Sprache, die in JavaScript kompiliert wird

In diesem Screencast wird CoffeeScript in einem experimentellen Build der Firefox-Konsole debuggt:

Das Google Web Toolkit (GWT) unterstützt seit Kurzem Quellkarten. Ray Cromwell vom GWT-Team hat einen tollen Screencast zur Unterstützung von Quellkarten erstellt.

Ein weiteres Beispiel, das ich zusammengestellt habe, verwendet die Traceur-Bibliothek von Google. Damit können Sie ES6 (ECMAScript 6 oder Next) schreiben und in ES3-kompatiblen Code kompilieren. Der Traceur-Compiler generiert auch eine Quellkarte. In dieser Demo werden ES6-Traits und ‑Klassen verwendet, als wären sie dank der Quellkarte nativ im Browser unterstützt.

Im Textfeld der Demo können Sie auch ES6-Code schreiben, der direkt kompiliert wird und eine Quellkarte sowie den entsprechenden ES3-Code generiert.

Traceur ES6-Debugging mit Quellkarten

Demo: ES6 schreiben, debuggen und die Quellzuordnung in Aktion ansehen

Wie funktioniert die Quellkarte?

Der einzige JavaScript-Compiler/-Minifier, der derzeit die Generierung von Quellkarten unterstützt, ist der Closure Compiler. (Die Verwendung wird später erklärt.) Nachdem Sie Ihr JavaScript kombiniert und minimiert haben, wird neben der Datei eine Quellzuordnungsdatei erstellt.

Derzeit fügt der Closure-Compiler am Ende keinen speziellen Kommentar hinzu, der den Entwicklertools eines Browsers signalisiert, dass eine Quellkarte verfügbar ist:

//# sourceMappingURL=/path/to/file.js.map

So können Entwicklertools Aufrufe ihrem Speicherort in den ursprünglichen Quelldateien zuordnen. Bisher war die Kommentarpragma //@. Aufgrund einiger Probleme mit dieser und bedingten Kompilierungskommentaren für IE wurde entschieden, sie in //# zu ändern. Derzeit wird die neue Kommentarpragma von Chrome Canary, WebKit Nightly und Firefox 24 und höher unterstützt. Diese Syntaxänderung wirkt sich auch auf „sourceURL“ aus.

Wenn Ihnen der merkwürdige Kommentar nicht gefällt, können Sie alternativ einen speziellen Header in Ihrer kompilierten JavaScript-Datei festlegen:

X-SourceMap: /path/to/file.js.map

Ähnlich wie der Kommentar gibt dies dem Nutzer der Quellzuordnung an, wo er nach der Quellzuordnung suchen muss, die mit einer JavaScript-Datei verknüpft ist. Außerdem wird damit das Problem umgangen, dass in Sprachen, die keine einzeiligen Kommentare unterstützen, auf Quellkarten verwiesen werden kann.

Beispiel für WebKit-Entwicklertools mit und ohne Quellzuordnungen

Die Quellzuordnungsdatei wird nur heruntergeladen, wenn Sie Quellzuordnungen aktiviert und Ihre Entwicklertools geöffnet haben. Außerdem müssen Sie Ihre Originaldateien hochladen, damit sie bei Bedarf in den Entwicklungstools referenziert und angezeigt werden können.

Wie erstelle ich eine Quellkarte?

Sie müssen den Closure Compiler verwenden, um Ihre JavaScript-Dateien zu minimieren, zu concatenieren und eine Quellkarte zu generieren. Der Befehl lautet:

java -jar compiler.jar \
--js script.js \
--create_source_map ./script-min.js.map \
--source_map_format=V3 \
--js_output_file script-min.js

Die beiden wichtigen Befehlsflags sind --create_source_map und --source_map_format. Das ist erforderlich, da die Standardversion 2 ist und wir nur mit Version 3 arbeiten möchten.

Aufbau einer Quellzuordnung

Um eine Quellmap besser zu verstehen, sehen wir uns ein kleines Beispiel für eine Quellmap-Datei an, die vom Closure-Compiler generiert würde. Dabei gehen wir genauer auf die Funktionsweise des Abschnitts „mappings“ ein. Das folgende Beispiel ist eine geringfügige Abwandlung des Beispiels aus der V3-Spezifikation.

{
    version : 3,
    file: "out.js",
    sourceRoot : "",
    sources: ["foo.js", "bar.js"],
    names: ["src", "maps", "are", "fun"],
    mappings: "AAgBC,SAAQ,CAAEA"
}

Wie Sie oben sehen, ist eine Quellkarte ein Objektliteral mit vielen nützlichen Informationen:

  • Versionsnummer, auf der die Quellzuordnung basiert
  • Der Dateiname des generierten Codes (deine minimierte/kombinierte Produktionsdatei)
  • Mit sourceRoot können Sie den Quellen eine Ordnerstruktur vorangestellt werden lassen. Dies ist auch eine platzsparende Methode.
  • „sources“ enthält alle Dateinamen, die kombiniert wurden.
  • „names“ enthält alle Variablen-/Methodennamen, die in Ihrem Code vorkommen.
  • Die Property „mappings“ ist der Ort, an dem die Magie mit Base64-VLQ-Werten passiert. Hier lässt sich der größte Speicherplatz sparen.

Base64-VLQ und kleine Quellkarte

Ursprünglich enthielt die Quellzuordnungsspezifikation eine sehr ausführliche Ausgabe aller Zuordnungen, was dazu führte, dass die Quellzuordnung etwa zehnmal so groß wie der generierte Code war. Version 2 reduzierte das um etwa 50% und Version 3 noch einmal um 50 %. Bei einer Datei von 133 KB erhalten Sie also eine Quellkarte von etwa 300 KB.

Wie haben sie also die Größe reduziert, ohne die komplexen Zuordnungen zu verlieren?

VLQ (Variable Length Quantity) wird zusammen mit der Codierung des Werts in einen Base64-Wert verwendet. Die Property „Zuordnungen“ ist ein sehr großer String. Dieser String enthält Semikolons (;) für eine Zeilennummer in der generierten Datei. Jede Zeile enthält Kommas (,), die die einzelnen Segmente dieser Zeile darstellen. Jedes dieser Segmente hat in Feldern mit variabler Länge den Wert 1, 4 oder 5. Einige mögen länger erscheinen, enthalten aber Fortsetzungsbits. Jedes Segment baut auf dem vorherigen auf, was dazu beiträgt, die Dateigröße zu reduzieren, da jedes Bit relativ zu den vorherigen Segmenten ist.

Aufschlüsselung eines Abschnitts in der JSON-Datei der Quellkarte.

Wie bereits erwähnt, kann jedes Segment eine variable Länge von 1, 4 oder 5 Bit haben. Dieses Diagramm hat eine variable Länge von vier mit einem Fortsetzungsbit (g). Wir gehen dieses Segment genauer durch und zeigen Ihnen, wie die Quellkarte den ursprünglichen Standort ermittelt.

Die oben genannten Werte sind nur die Base64-decodierten Werte. Für die tatsächlichen Werte ist noch etwas mehr Verarbeitung erforderlich. Für jedes Segment werden in der Regel fünf Dinge ermittelt:

  • Generierte Spalte
  • Originaldatei, in der das Problem aufgetreten ist
  • Ursprüngliche Zeilennummer
  • Originalspalte
  • Und, falls verfügbar, den ursprünglichen Namen

Nicht jedes Segment hat einen Namen, einen Methodennamen oder ein Argument. Daher wechseln die Segmente zwischen vier und fünf Variablenlängen. Der Wert „g“ im Segmentdiagramm oben ist ein sogenanntes Fortsetzungsbit, das eine weitere Optimierung in der Base64-VLQ-Dekodierungsphase ermöglicht. Mit einem Fortsetzungsbit können Sie auf einen Segmentwert aufbauen, sodass Sie große Zahlen speichern können, ohne eine große Zahl speichern zu müssen. Dies ist eine sehr clevere Methode zur Platzeinsparung, die ihren Ursprung im MIDI-Format hat.

Das obige Diagramm AAgBC würde nach der weiteren Verarbeitung 0, 0, 32, 16, 1 zurückgeben. Die 32 ist das Fortsetzungsbit, das zum Erstellen des folgenden Werts von 16 beiträgt. B, das rein in Base64 decodiert wird, ist 1. Die wichtigen Werte sind also 0, 0, 16 und 1. Daraus geht hervor, dass Zeile 1 (Zeilen werden durch Semikolons gezählt) Spalte 0 der generierten Datei der Datei 0 (Array von Dateien 0 ist foo.js) Zeile 16 in Spalte 1 zugeordnet ist.

Um zu zeigen, wie die Segmente decodiert werden, beziehe ich mich auf die JavaScript-Bibliothek für Quellzuordnungen von Mozilla. Sie können sich auch den Quellcode für die Zuordnung in den WebKit-Entwicklertools ansehen, der ebenfalls in JavaScript geschrieben ist.

Um zu verstehen, wie wir den Wert 16 aus B erhalten, müssen wir grundlegende Kenntnisse über bitweise Operatoren und die Funktionsweise der Spezifikation für die Quellzuordnung haben. Die vorangehende Ziffer „g“ wird als Fortsetzungsbit gekennzeichnet, indem die Ziffer (32) mit dem VLQ_CONTINUATION_BIT (binär 100000 oder 32) mithilfe des Bitweisen-AND-Operators (&) verglichen wird.

32 & 32 = 32
// or
100000
|
|
V
100000

Dadurch wird an jeder Bitposition, an der beide vorkommen, eine 1 zurückgegeben. Ein Base64-decodierter Wert von 33 & 32 würde also 32 zurückgeben, da sie sich nur den 32-Bit-Speicherort teilen, wie im Diagramm oben zu sehen ist. Dadurch wird der Bit-Verschiebewert für jedes vorangehende Fortsetzungsbit um 5 erhöht. Im obigen Fall wird es nur einmal um 5 verschoben, also um 5 nach links (B).

1 <<../ 5 // 32

// Shift the bit by 5 spots
______
|    |
V    V
100001 = 100000 = 32

Dieser Wert wird dann von einem signierten VLQ-Wert in einen signierten 32‑Bit-Wert umgewandelt, indem die Zahl (32) um eine Stelle nach rechts verschoben wird.

32 >> 1 // 16
//or
100000
|
 |
 V
010000 = 16

So gehts: Das mag auf den ersten Blick etwas kompliziert erscheinen, aber sobald die Zahlen größer werden, macht es Sinn.

Potenzielle XSSI-Probleme

In der Spezifikation werden Probleme mit der websiteübergreifenden Scripteinfügung erwähnt, die durch die Verwendung einer Quellkarte auftreten können. Um dies zu vermeiden, wird empfohlen, der ersten Zeile Ihrer Quellzuordnung „)]}“ voranzustellen, um JavaScript absichtlich ungültig zu machen, sodass ein Syntaxfehler auftritt. Die WebKit-Entwicklungstools können das bereits.

if (response.slice(0, 3) === ")]}") {
    response = response.substring(response.indexOf('\n'));
}

Wie oben gezeigt, werden die ersten drei Zeichen abgeschnitten, um zu prüfen, ob sie mit dem Syntaxfehler in der Spezifikation übereinstimmen. Falls ja, werden alle Zeichen bis zum ersten Zeilenumbruch-Entitätszeichen (\n) entfernt.

sourceURL und displayName in Aktion: Eval- und anonyme Funktionen

Die folgenden beiden Konventionen sind zwar nicht Teil der Spezifikation für Quellkarten, erleichtern aber die Entwicklung bei der Arbeit mit evals und anonymen Funktionen erheblich.

Der erste Helper ähnelt stark der //# sourceMappingURL-Eigenschaft und wird in der Spezifikation für die V3-Quellkarte erwähnt. Wenn Sie den folgenden speziellen Kommentar in Ihren Code einfügen, der ausgewertet wird, können Sie Evals so benennen, dass sie in Ihren Entwicklungstools als logischere Namen erscheinen. Hier eine einfache Demo mit dem CoffeeScript-Compiler:

Demo: Code von eval() als Script über sourceURL anzeigen

//# sourceURL=sqrt.coffee
So sieht der spezielle Kommentar „sourceURL“ in den Entwicklertools aus

Mit der anderen Hilfsfunktion können Sie anonyme Funktionen benennen, indem Sie die Eigenschaft displayName verwenden, die im aktuellen Kontext der anonymen Funktion verfügbar ist. In der folgenden Demo wird die Property displayName veranschaulicht.

btns[0].addEventListener("click", function(e) {
    var fn = function() {
        console.log("You clicked button number: 1");
    };

    fn.displayName = "Anonymous function of button 1";

    return fn();
}, false);
Die Property „displayName“ in Aktion

Wenn Sie Ihren Code in den Entwicklertools profilieren, wird die Property displayName statt einer Property wie (anonymous) angezeigt. Der displayName ist jedoch praktisch nicht mehr verfügbar und wird nicht in Chrome übernommen. Aber es gibt Hoffnung: Es wurde ein viel besserer Vorschlag gemacht: debugName.

Derzeit ist die Benennung von eval-Funktionen nur in Firefox- und WebKit-Browsern verfügbar. Die Property displayName ist nur in WebKit-Nightlies verfügbar.

Gemeinsam für mehr Vielfalt

Derzeit gibt es eine sehr lange Diskussion darüber, ob CoffeeScript Unterstützung für Quellkarten hinzugefügt werden soll. Sehen Sie sich das Problem an und unterstützen Sie die Forderung, dass die Generierung von Quellkarten in den CoffeeScript-Compiler aufgenommen wird. Das ist ein großer Gewinn für CoffeeScript und seine treuen Anhänger.

UglifyJS hat auch ein Problem mit der Quellkarte, das Sie sich ansehen sollten.

Es gibt viele Tools, mit denen Quellkarten generiert werden, einschließlich des CoffeeScript-Compilers. Ich halte das jetzt für einen müßigen Punkt.

Je mehr Tools zur Verfügung stehen, mit denen Quellkarten generiert werden können, desto besser. Bitten Sie also die Entwickler Ihres bevorzugten Open-Source-Projekts, die Unterstützung für Quellkarten hinzuzufügen.

Es ist nicht perfekt.

Quellzuordnungen unterstützen derzeit keine Zählerausdrücke. Das Problem ist, dass beim Versuch, einen Argument- oder Variablennamen im aktuellen Ausführungskontext zu prüfen, nichts zurückgegeben wird, da er nicht wirklich existiert. Dazu ist eine Art umgekehrte Zuordnung erforderlich, um den tatsächlichen Namen des Arguments/der Variablen, die Sie prüfen möchten, mit dem tatsächlichen Namen des Arguments/der Variablen in Ihrem kompilierten JavaScript abzugleichen.

Dieses Problem lässt sich natürlich lösen. Wenn wir uns mehr auf die Quellkarten konzentrieren, können wir einige tolle Funktionen und eine bessere Stabilität erzielen.

Probleme

In der neuesten Version von jQuery 1.9 wurde die Unterstützung für Quellkarten hinzugefügt, wenn sie über offizielle CDNs bereitgestellt werden. Außerdem wurde ein eigenartiger Fehler festgestellt, wenn Kommentare zur bedingten Kompilierung in IE (//@cc_on) vor dem Laden von jQuery verwendet werden. Es wurde inzwischen ein commit vorgenommen, um dies zu beheben, indem die sourceMappingURL in einen mehrzeiligen Kommentar eingeschlossen wurde. Fazit: Verwenden Sie keine bedingten Kommentare.

Dieses Problem wurde durch die Änderung der Syntax zu //# behoben.

Tools und Ressourcen

Hier sind einige weitere Ressourcen und Tools, die Sie sich ansehen sollten:

Quellkarten sind ein sehr leistungsstarkes Tool im Werkzeugkasten eines Entwicklers. Es ist sehr nützlich, die Webanwendung schlank, aber leicht zu debuggen zu halten. Es ist auch ein sehr leistungsstarkes Lerntool für neue Entwickler, um zu sehen, wie erfahrene Entwickler ihre Apps strukturieren und schreiben, ohne sich durch unlesbaren minimierten Code wühlen zu müssen.

Worauf wartest du noch? Erstellen Sie jetzt Quellkarten für alle Projekte.