Ich habe in den letzten drei Monaten Typoskript verwendet, um sehr komplexe CRUD-Anwendungen zu erstellen. Die Sicherheit bei der Kompilierungszeit, die Typescript bietet, hat zu erheblichen Verbesserungen in meiner Arbeit geführt - das Erfassen von Fehlern während der Kompilierungszeit ist ein Gottesgeschenk, verglichen mit der Tatsache, dass sie zur Laufzeit als Ausnahmen und Fehlverhalten angezeigt werden.
Aber es gibt einen Haken.
Ich muss mich mit Hunderten von Tabellen beschäftigen, daher verwende ich einen benutzerdefinierten Code-Generator, der mit dem DB-Schema beginnt und automatisch viele Typoskript-Dateien generiert. Solange das Schema klein ist, funktioniert das perfekt - aber für ein sehr großes Schema, das hunderte von Tabellen enthält, wird die Kompilierungszeit von TSC zu einem Problem. - Ich sehe Kompilierungszeiten von 15 Minuten für einen Satz von 400 Dateien ... (sowie der gefürchtete Kompilierungsfehler von "CALL_AND_RETRY_2 Allocation failed" - das heißt, Speicherprobleme ...)
Bis jetzt habe ich TSC in einem Makefile verwendet, indem ich es mit der Syntax "TSC - OUT ..." aufgerufen habe, die aus allen meinen .TS-Dateien eine einzige .js-Datei generiert. Ich dachte daher, dass ich dieses Problem lösen könnte, indem ich den Build inkrementell ausführe: alle .ts einzeln kompilieren (das heißt, zu einem Zeitpunkt nur eine .ts-Datei an tsc übergeben) und am Ende alle generierten .js in einem einzigen. Dies schien tatsächlich zu funktionieren - nur die geänderten Dateien müssen während der normalen Entwicklung neu kompiliert werden (und nur die erste Kompilierung durchläuft sie alle und benötigt daher viel Zeit).
Aber es stellte sich heraus, dass auch dies ein Problem hat: Um jedes .ts "Stand-alone-kompilierbar" zu machen, musste ich alle relevanten Abhängigkeiten oben hinzufügen - also Zeilen wie
%Vor%... über jeder .ts-Datei.
Und es stellt sich heraus, dass die generierten .js-Dateien aufgrund dieser Verweise identische Abschnitte enthalten, die in vielen von ihnen wiederholt werden ... Wenn ich also die .js-Dateien verkette, bekomme ich mehrere Definitionen für die gleichen Funktionen, und schlimmer noch, globale Gültigkeitsbereichsanweisungen (var global = new ...) wiederholt!
Ich brauche daher eine Möglichkeit, die generierten .js-Dateien irgendwie intelligent zusammenzuführen, um zu vermeiden, replizierte Funktionsdefinitionen zu sehen ...
Gibt es eine Möglichkeit, diese Verschmelzung klug zu machen und Wiederholungen zu vermeiden? Oder vielleicht eine andere Möglichkeit, die Kompilation zu beschleunigen?
Irgendwelche Vorschläge sehr willkommen ... Die TSC-Kompilierungsgeschwindigkeit ist 30-100x langsamer als normale Compiler - es ist jetzt wirklich ein Blocking-Punkt.
UPDATE, 2 Tage später
Basarat (siehe seine Antwort unten) hat mir geholfen, seine Lösung in meinem Projekt anzuwenden. Es stellt sich heraus, dass, obwohl seine Lösung perfekt mit kleinen und mittleren Projekten funktioniert, mit mir die gefürchtete "FATAL ERROR: CALL_AND_RETRY_2 Zuordnung fehlgeschlagen - Prozess nicht genügend Speicher" Fehler - das ist der gleiche Fehler, den ich bekomme, wenn ich "TSC --out ... ".
Am Ende ist meine Makefile-basierte Lösung das einzige, was funktioniert hat - und zwar so:
%Vor%... was zwei Dinge bewirkt: Es verwendet MD5-Prüfsummen, um herauszufinden, wann eine Kompilierung tatsächlich durchgeführt werden soll, und es führt die Kompilierung "alleinstehend" aus (d. h. ohne die "--out" -Option von TSC).
In der eigentlichen Zielregel habe ich die generierten .js-Dateien zusammengeführt ... aber das hat mich ohne funktionierende .map-Dateien (zum Debuggen) verlassen - also habe ich jetzt direkte Includes in der index.html erzeugt:
%Vor%Ich werde die Frage für zukünftige Beiträge offen lassen ...
Ich habe 300+ * .ts Dateien in meinem Projekt erreicht, und ich bin fest mit 0.9.1.1 Compiler (neuere Versionen verwenden inkompatible Version von TypeScript und ich hatte eine riesige Umgestaltung durchführen, um den Compiler glücklich zu machen) mit Kompilierzeit ~ 25 Sek.
Ich benutze tsc app.ts --out app.js
.
Ich bekomme ein ähnliches Timing für tsc app.ts
, das heißt, wenn Sie nicht --out app.js
verwenden, sondern viele kleine js-Dateien erzeugen.
Wenn ich jedoch eine einzelne Datei kompiliere, die nicht zu viele Abhängigkeiten hat, bekomme ich mal ~ 5 sec. (Ich habe für jede * .ts-Datei TSC separat ausgeführt, um dies zu messen, es gab mehrere Ausreißer, die mehr als 10 benötigten Sekunden, aber die meisten Dateien kompilieren schnell, da sie sich in der Hierarchie der Abhängigkeiten befinden. Also meine erste Idee wäre, ein System zu erstellen, das:
tsc foo.ts
für die geänderte Datei Sie könnten den ersten Schritt implementieren, indem Sie das Ergebnis von ls -ltc --full-time $(find ts -type f -name '*.ts')
jede Sekunde oder durch etwas fortgeschritteneres wie inotify
vergleichen.
Der dritte Schritt ist nicht so schwierig, da tsc die Referenzvermerke /// in js-Dateien beibehält, so dass Sie nach ihnen suchen und eine einfache topologische Sortierung nach O (n) durchführen können.
Ich denke jedoch, wir könnten den zweiten Schritt verbessern, indem wir die Option tsc -d
verwenden, um Deklarationsdateien zu erstellen. Im zweiten Schritt wird der TSC nicht nur foo.js erstellen, sondern auch alle Abhängigkeiten von foo.ts untersuchen und kompilieren, was Zeitverschwendung ist.
OTOH Wenn foo.ts nur auf * .d.ts-Dateien referenziert hat (was wiederum keine Abhängigkeiten oder zumindest eine sehr begrenzte Anzahl von ihnen haben würde), könnte der Prozess der Neukompilierung von foo.ts schneller sein.
Damit dies funktioniert, müssen Sie alle Referenzen /// finden und ersetzen, so dass sie auf bar.d.ts und nicht auf bar.ts zeigen.
%Vor%sollte die notwendigen Änderungen vornehmen.
Sie müssen auch alle * .d.ts-Dateien zum ersten Mal generieren. Es ist ein bisschen wie ein Huhn und Ei Problem, wie Sie diese Dateien benötigen, um eine Kompilation durchzuführen. Die gute Nachricht ist, dass dies funktioniert, wenn Sie Dateien in der topologischen Reihenfolge der Referenzen kompilieren.
Also müssen wir eine topologisch sortierte Liste von Dateien erstellen. Es gibt ein tsort
-Programm, das diese Aufgabe ausführt, vorausgesetzt, Sie haben eine Liste von Kanten.
Ich kann alle Abhängigkeiten mit dem folgenden grep finden
Das einzige Problem ist, dass die Ausgabe wörtlich die Referenzen enthält, zum Beispiel:
%Vor%was bedeutet, dass wir relative Pfade zu einer kanonischen Form auflösen müssen. Ein weiteres Detail ist, dass wir zum Sortieren tatsächlich auf * .ts und nicht auf * .d.ts angewiesen sind. Dieses einfache Skript parse.sh kümmert sich darum:
%Vor%Wenn Sie alles mit tsort zusammenstellen, wird eine Liste von Dateien in der richtigen Reihenfolge erstellt:
%Vor% Nun wird das wahrscheinlich scheitern, einfach weil, wenn das Projekt nie so kompiliert wurde, es die Abhängigkeiten wahrscheinlich nicht genau genug definiert hat, um Probleme zu vermeiden. Auch wenn Sie einige globale Variablen (wie var myApp;
) in Ihrer App ( myApp.doSomething()
) verwenden, müssen Sie sie möglicherweise in einer * .d.ts-Datei deklarieren und darauf verweisen. Nun könnte man denken, dass dies eine zirkuläre Abhängigkeit erzeugen würde (App benötigt Modul x, während Modul x App benötigt), aber erinnern Sie sich, dass wir jetzt nur von * .d.ts-Dateien abhängen. Es gibt also keine Zirkularität (das ist etwas ähnlich wie in C oder C ++, wo man nur von Header-Dateien abhängt).
Sobald Sie alle fehlenden Referenzen behoben und alle * .d.ts und * .ts Dateien kompiliert haben. Sie können nach Änderungen suchen und nur die geänderten Dateien neu kompilieren.
Aber Vorsicht, wenn Sie etwas in der Datei foo.ts
ändern, können Sie auch Dateien rekompilieren, die foo.ts benötigen. Nicht, weil das nötig ist, um sie zu aktualisieren - eigentlich sollten sie sich während der Neukompilierung überhaupt nicht ändern. Vielmehr wird dies für die Validierung benötigt - alle Benutzer von foo.d.ts sollten überprüfen, ob die neue Schnittstelle von foo mit der Art und Weise kompatibel ist, wie sie von ihnen verwendet wird!
Daher möchten Sie möglicherweise alle Benutzer von foo.d.ts
erneut kompilieren, wenn foo.d.ts
sich ändert. Dies könnte viel kniffliger und zeitaufwendiger sein, sollte aber selten passieren (nur wenn Sie die Form von foo ändern).
Eine andere Option wäre, einfach alles (in der topologischen Reihenfolge) in solchen Fällen wieder aufzubauen.
Ich bin dabei, diesen Ansatz zu implementieren, also werde ich meine Antwort aktualisieren, sobald ich fertig bin (oder scheitere).
UPDATE
Also habe ich es geschafft, all das zu implementieren, indem ich tsort und gnu make einsetze, um mein Leben mit der Auflösung von Abhängigkeiten zu erleichtern.
Das Problem ist, dass es tatsächlich langsamer war als das ursprüngliche tsc --out app.js app.ts
.
Der Grund dafür ist, dass der 0.9.1.1-Compiler einen großen Aufwand hat, um eine einzelne Kompilierung durchzuführen - selbst für eine so einfache Datei wie
time tsc test.ts
liefert über 3 Sekunden.
Nun, wenn Sie eine einzelne Datei neu kompilieren müssen, ist das in Ordnung.Aber sobald Sie feststellen, dass Sie alle Dateien neu kompilieren müssen, die davon abhängen (hauptsächlich, um die Typüberprüfung durchzuführen) und dann Dateien, die von ihnen abhängen usw., müssen Sie ungefähr 5 bis 10 solcher Kompilationen durchführen.
Auch wenn jeder Kompilierungsschritt sehr schnell ist (3 Sekunden & lt; & lt; 25 Sekunden), ist der Gesamtwert
Erfahrung ist schlechter (~ 50 sec!).
Der Hauptvorteil dieser Übung für mich war, dass ich viele Bugs und fehlende Abhängigkeiten beheben musste, damit es funktionierte:)
Ich habe auch mit einem Makefile mit tsc --out
begonnen, aber ich benutze jetzt requirejs mit tsc --watch --module amd
.
Es gibt wahrscheinlich eine bessere Alternative zu tsc --watch
(es ist nicht sehr schnell), aber requirejs hat die Vorteile, dass Sie import
anstelle von // <reference.../>
(außer für .d.ts-Dateien) und requirejs verwenden können Bündeln und optimieren Sie später Ihr Projekt.
Tags und Links typescript