Die Einstellung von node-fibers
Gepostet am 26. März 2021 von Natalie Weizenbaum
Wir haben kürzlich die unerfreuliche, aber nicht gänzlich überraschende Nachricht erhalten, dass das node-fibers-Paket sein Lebensende erreicht hat und nicht mehr mit Node 16 kompatibel sein wird. Dart Sass hat es JavaScript-Benutzern historisch ermöglicht, node-fibers zu verwenden, um die Leistung der asynchronen render()-Methode zu verbessern. Zukünftig wird dies jedoch leider in Node 16 und darüber hinaus nicht mehr möglich sein.
Es gibt eine Reihe von Alternativen, um diese verlorene Leistung zurückzugewinnen. Einige davon sind bereits verfügbar, einige sind in Entwicklung und einige sind theoretischer Natur, könnten aber mit Pull-Requests von Benutzern wie Ihnen realisiert werden. Leider sind keine der derzeit verfügbaren Optionen sofort einsetzbare Lösungen mit dem gleichen Bedienungskomfort wie node-fibers. Wenn Ihnen diese Leistung wichtig ist, empfehlen wir Ihnen, vorerst bei Node 14 zu bleiben.
Was ist passiert?Was ist passiert? Permalink
Um zu verstehen, wie wir hierher gekommen sind, ist es wichtig, zwei historische Fakten zu kennen. Erstens: Warum verwendet Dart Sass überhaupt node-fibers? Und zweitens: Warum stirbt node-fibers?
Dieser Abschnitt ist recht technisch, Sie können also gerne vorspringen, wenn Sie sich nicht für die blutigen Details interessieren.
Fibers in SassFibers in Sass Permalink
Dart Sass hat seine JavaScript-API von der mittlerweile veralteten Node Sass übernommen. Diese API verfügt über zwei Hauptfunktionen zum Kompilieren von Sass-Dateien: renderSync(), die das kompilierte CSS synchron zurückgibt, und render(), die stattdessen einen Callback annimmt, an den sie das kompilierte CSS asynchron übergibt. Nur render() unterstützte asynchrone Plugins, einschließlich weit verbreiteter Importer wie webpacks sass-loader, so dass render() in der Praxis sehr häufig verwendet wurde.
Für Node Sass war der Leistungsunterschied zwischen render() und renderSync() vernachlässigbar, da es auf C++-Code basierte, der nur wenige Einschränkungen bei der Handhabung von Asynchronität hatte. Dart Sass läuft jedoch als reines JavaScript, was es den strengen asynchronen Regeln von JavaScript unterwirft. Asynchronität in JavaScript ist ansteckend, das heißt, wenn eine Funktion (wie ein Importer-Plugin) asynchron ist, muss alles, was sie aufruft, ebenfalls asynchron sein, und so weiter, bis das gesamte Programm asynchron ist.
Und Asynchronität in JavaScript ist nicht kostenlos. Jeder asynchrone Funktionsaufruf muss Callbacks zuordnen, sie irgendwo speichern und einen Weg zurück zur Event-Schleife machen, bevor diese Callbacks aufgerufen werden, und all das kostet Zeit. Tatsächlich kostet es genug Zeit, dass das asynchrone render() in Dart Sass tendenziell 2-3 Mal langsamer ist als renderSync().
Hier kommen Fibers ins Spiel. Fibers sind ein sehr cooles Konzept, das in Sprachen wie Ruby und C++ verfügbar ist und dem Programmierer mehr Kontrolle über asynchrone Funktionen gibt. Sie können sogar ermöglichen, dass ein Block synchronen Codes (wie der Sass-Compiler) asynchrone Callbacks (wie das webpack-Plugin) aufruft. Das node-fibers-Paket führte einige obskure Tricks mit der V8-Virtual Machine durch, um Fibers in JavaScript zu implementieren, was es Dart Sass ermöglichte, den schnellen synchronen Code zu verwenden, um die asynchrone render()-API zu implementieren. Und eine Zeit lang war das großartig.
Der Tod der FibersDer Tod der Fibers Permalink
Leider beinhalteten die obskuren Tricks, die node-fibers verwendete, den Zugriff auf einige Teile von V8, die nicht offiziell Teil seiner öffentlichen API waren. Es gab keine Garantie dafür, dass die verwendeten Schnittstellen von Version zu Version gleich blieben, und tatsächlich änderten sie sich ziemlich regelmäßig. Lange Zeit waren diese Änderungen klein genug, dass es möglich war, eine neue Version von node-fibers zu veröffentlichen, die diese unterstützte, aber mit Node.js 16 ging das Glück zur Neige.
Die neueste Version von V8 beinhaltet einige größere Überarbeitungen seiner internen Strukturen. Diese werden schließlich einige coole Verbesserungen ermöglichen, daher ist es schwer, sie zu beanstanden, aber ein Nebeneffekt ist, dass die APIs, die node-fibers verwendete, vollständig verschwunden sind, ohne offensichtlichen Ersatz. Das ist niemandes Schuld: Da diese Schnittstellen nicht Teil der öffentlichen API von V8 waren, hatten sie keine Verpflichtung, sie stabil zu halten. Manchmal geht es in der Software eben einfach so zu.
Leistung zurückgewinnenLeistung zurückgewinnen Permalink
Es gibt einige Optionen, um die Leistung wiederzugewinnen, die durch die Nichtverwendung von node-fibers in sass.render() verloren geht. In der Reihenfolge vom kürzesten zum längsten Zeitraum:
Vermeiden Sie asynchrone PluginsVermeiden Sie asynchrone Plugins Permalink
Dies ist etwas, das Sie heute tun können. Wenn es überhaupt möglich ist, die an Sass übergebenen Plugins synchron zu gestalten, können Sie die Methode renderSync() verwenden, die keine Fasern benötigt, um schnell zu sein. Dies erfordert möglicherweise das Umschreiben einiger bestehender Plugins, zahlt sich aber sofort aus.
Eingebettetes Dart SassEingebettetes Dart Sass Permalink
Obwohl es noch nicht bereit für die Hauptsendezeit ist, arbeitet das Sass-Team an einem Projekt namens "Embedded Dart Sass". Dies beinhaltet die Ausführung von Dart Sass als Subprozess anstelle einer Bibliothek und die Kommunikation mit ihm über ein spezielles Protokoll. Dies bietet mehrere wichtige Verbesserungen gegenüber den bestehenden Alternativen:
-
Im Gegensatz zur Ausführung von
sassvon der Befehlszeile aus funktioniert dies immer noch mit Plugins wie dem webpack-Importer. Tatsächlich planen wir, die bestehende JavaScript-API so genau wie möglich abzubilden. Dies wird wahrscheinlich asynchrone Plugins *noch schneller* als synchrone ausführen. -
Im Gegensatz zur bestehenden JS-kompilierten Version wird hierfür die Dart VM verwendet. Aufgrund der statischeren Natur der Dart-Sprache läuft die Dart VM Sass erheblich schneller als Node.js, was eine Geschwindigkeitssteigerung von etwa dem 2-fachen für große Stylesheets bedeutet.
Der Node.js-Host für Embedded Sass befindet sich noch in der aktiven Entwicklung, aber es gibt eine Beta-Version (mit minimalen Funktionen), wenn Sie sie ausprobieren möchten.
Worker-ThreadsWorker-Threads Permalink
Wir haben die Möglichkeit untersucht, das reine JS-Dart-Sass in einem Node.js-Worker-Thread auszuführen. Worker-Threads funktionieren ein wenig wie Fibers, da sie es synchronem Code ermöglichen, auf das Ausführen asynchroner Callbacks zu warten. Leider sind sie auch extrem restriktiv hinsichtlich der Art von Informationen, die über die Thread-Grenze hinweg übergeben werden können, was es viel schwieriger macht, sie zum Umschließen einer komplexen API wie Sass's zu verwenden.
Derzeit konzentriert sich das Sass-Team auf Embedded Sass, sodass wir nicht über die freie Bandbreite verfügen, um uns als Alternative mit Worker-Threads zu befassen. Das gesagt, wir würden einem motivierten Benutzer gerne bei der Implementierung helfen. Wenn Sie interessiert sind, folgen Sie bitte diesem GitHub-Issue!
Wiederbelebung von node-fibersWiederbelebung von node-fibers Permalink
Es gibt eine weitere potenzielle Lösung, obwohl es wirklich Hingabe erfordern würde, um sie Wirklichkeit werden zu lassen. Es wäre prinzipiell möglich, eine neue API zu V8 hinzuzufügen, die die Hooks, die node-fibers für seine gute Arbeit benötigt, offiziell unterstützt. Dies würde es dem Paket ermöglichen, glorreich wieder zum Leben zu erwachen und Sass, render() auch in Zukunft schnell zu machen.
Das Sass-Team hat sowohl das V8-Team als auch den Eigentümer von node-fibers kontaktiert, und beide sind diesem Gedanken prinzipiell nicht abgeneigt. Obwohl keiner von beiden die Zeit hat, ihn selbst zu Ende zu führen, haben sie ihre Bereitschaft geäußert, einem Ingenieur zu helfen, der bereit ist, es zu versuchen.
Dies ist jedoch keineContribution für schwache Nerven: Sie erfordert Kenntnisse in C++, die Bereitschaft, zumindest die Grundlagen der node-fibers-Codebasis und der V8-Isolat-APIs zu erlernen, sowie Fähigkeiten in API-Design und menschlicher Interaktion, um eine stabile API auszuhandeln, die die Bedürfnisse von node-fibers erfüllt und die das V8-Team als stabil erachtet und bereit ist zu pflegen. Aber wenn Sie interessiert sind, zögern Sie bitte nicht, sich zu melden!