Was ist MagicString und sind diese Linien so magisch?

MagicString ist eine wenig bekannte Bibliothek. Trotzdem löst es eines der dringendsten Probleme - das Ändern des Quellcodes anhand seiner Struktur (AST - Abstract Syntax Tree).

In diesem Artikel erfahren wir, was MagicString ist und ob diese Zeilen wirklich „magisch“ sind. Dies wird uns helfen, den nächsten Artikel zu verstehen, in dem ich erklären werde, wie wir es geschafft haben, Angular-Dokumentation so schnell zu übersetzen, und wie es bei der Erstellung eines universellen Übersetzers sowohl für Markdown als auch für Dateien eines anderen Formats helfen wird.





Vor 2 Wochen habe ich die russischsprachige Dokumentation von Angular ( angle24.ru ) veröffentlicht. Während dieser Zeit wurden 35 Probleme mit Korrekturen im Text und 2 Pull-Anfragen hinzugefügt. Ich bezweifelte aufrichtig, dass das System, in dem Sie den Text auswählen, eine Übersetzung anbieten und automatisch auf GitHub ausgeben, funktioniert. Aber Crowdsourcing funktioniert! :) Mehr dazu erfahren Sie in diesem Artikel .

Nach der Veröffentlichung war eine der am häufigsten gestellten Fragen: „Warum?“. Die Frage ist absolut richtig, aber um sie zu beantworten, müssen Sie zuerst verstehen, was MagicString ist, wie es funktioniert und wie es nützlich ist.

Angenommen, wir haben einen einfachen Quellcode:

const a = 1;

Wir wollen const durch var ersetzen . Die einfachste Lösung besteht darin, const durch var durch den üblichen String.prototype.replace zu ersetzen . Und für diese Aufgabe ist dies höchstwahrscheinlich die richtigste Lösung. Aber was ist, wenn wir const nur im globalen Bereich durch var ersetzen müssen? Aber nicht innerhalb von Funktionen ersetzen? Sie können sich natürlich eine komplexere Regelmäßigkeit einfallen lassen oder kniffligen Code schreiben, aber es gibt eine skalierbarere und flexiblere Möglichkeit.

Wir können die Parser verwenden, um AST - Abstract Syntax Tree zu erhalten. Wenn Sie daran interessiert sind, was AST ist, gehen Sie zu astexplorer.net . Im Wesentlichen handelt es sich um einen Baum, der die Struktur Ihres Codes genau anzeigt.

Ferner hat jeder der Knoten in diesem AST einen Startund enden Indizes , die die Positionen dieser Elemente in dem Quellcode. Wenn wir diese Koordinaten kennen und die Struktur des Dokuments zur Hand haben, können wir komplexe Ersetzungen und Permutationen vornehmen, indem wir die Struktur des Dokuments beibehalten.



In der Regel erfolgt das Ersetzen mithilfe des Besuchermusterdesigns und mehrerer Helfer , die sich normalerweise in einer einzigen Bibliothek befinden, die als „Transformator-API“ bezeichnet werden kann. Jeder Parser hat eine eigene "Transformator-API".

Solche Bibliotheken sind sehr einfach zu bedienen, haben jedoch mehrere Probleme. Eine davon ist die Leistung.

Da jeder (nun ja, fast jeder) Knoten im AST-Baum Koordinaten enthält, müssen wir beim Ändern eines Knotens häufig die Koordinaten für den Rest des Baums aktualisieren. Hier können Sie argumentieren, dass Sie mit ein wenig Blut auskommen können - aktualisieren Sie die Koordinaten nicht überall, sondern rendern Sie den AST einfach zurück zum Text basierend auf der Struktur. Es gibt jedoch ein Problem: Sie verlieren sofort die Formatierung des Originaltextes, was unserer Aufgabe widerspricht - const in der vorhandenen Zeile durch var zu ersetzen. Tatsächlich erhalten wir eine neue Zeile mit einer neuen Formatierung. Und wenn dies für eine kleine Zeile kein Problem ist, stellen Sie sich eine Datei mit 1000 Zeilen vor, in der sich die Formatierung vollständig geändert hat, weil const durch var ersetzt wurde . Das klingt nicht sehr gut.



Und hier kommt die Magie von MagicString. Ich erfuhr zuerst von ihrer Existenz aus dem Rich Harris-Projekt, das Butternut genannt wurde . Butternut ist ein JavaScript-Minifier. Butternutt soll 3-mal schneller als UglifyJS und 10-15-mal schneller als Babili sein . Ich werde weitermachen und sagen, dass das Projekt vor mindestens 3 Jahren mit einem Kupferbecken bedeckt war. Aber selbst dann war ich fasziniert von dem Geheimnis seiner Leistung. Es war ein MagicString.

Werfen wir einen Blick auf die Arbeit mit MagicString:

var MagicString = require( 'magic-string' );
var s = new MagicString( 'const a = 1; const b = 2;' );

s.overwrite( 0, 5, 'var' );
s.toString(); // 'var a = 1; const b = 2;'

//  

Der Algorithmus von MagicString ist sehr einfach: Wir wickeln die ursprüngliche Zeichenfolge in ein Objekt ein, in dem wir die Änderungen nicht direkt auf die Zeichenfolge anwenden, sondern die Koordinaten und die erforderlichen Schritte in ein Array für die Zukunft einfügen. Und nur wenn jemand die resultierende Zeile erhalten möchte, beginnen wir 1 zu 1, um die akkumulierten Operationen auszuführen. Z.B:

  1. Wir haben const durch var ersetzt, beginnend bei Index 0 und endend bei Index 5
  2. Wir wissen, dass alle nachfolgenden Ersetzungen einen Index von weniger als 2 haben müssen (var kleiner als const um 2 Zeichen, eine Zeile kürzer)
  3. Wir aktualisieren die Koordinaten aller Operationen
  4. Wir wenden die folgende Operation usw. an.




Alles sieht ziemlich einfach aus. Aber warum ist MagicString schneller? Die Antwort ist ganz einfach: Die Anzahl der Operationen, die wir an unserem Baum ausführen, ist viel geringer als die Anzahl der AST-Knoten. Ganz zu schweigen von der für den AST benötigten Speichermenge und der Tatsache, dass Tree Traversal (Reisen durch einen Baum) keine freie Operation ist, sondern O (n + m)



Und wenn ich bereit bin, eine zusätzliche halbe Stunde zu warten? Und hier kommt das zweite Plus von MagicString. Jeder Parser erfindet eine eigene API zur Transformation. Und das ist immer noch sehr gut, wenn es eine solche API gibt (nicht jeder Parser stellt sie bereit), sehr oft bleibt uns die Möglichkeit, den Quelltext normalerweise mit AST zu ersetzen. MagicString ist jedoch eine einzige universelle API zum Ändern der Quellzeichenfolge. Es spielt keine Rolle, welchen Parser oder welche Kombination von Parsern Sie verwendet haben. Mit MagicString können Sie mit jedem AST gleich gut arbeiten.



Ich hoffe, Sie interessieren sich für MagicString. Im nächsten Artikel werde ich über den doppelten MagicString sprechen und wie man einen universellen Übersetzer für Markdown-Dokumente erstellt.

Abonnieren Sie meinen Telegrammkanal @obenjiro_notes und Twitter obenjiroum die folgenden Artikel zum Thema und viele andere interessante Dinge nicht zu verpassen.

All Articles