
Der JavaScript Engine Switcher wurde ursprünglich als Hilfsbibliothek erstellt und seine Entwicklung wurde weitgehend von den Anforderungen der Bibliotheken bestimmt, die ihn verwendeten. Tatsächlich löste jede ihrer Hauptversionen eine oder mehrere Hauptaufgaben, die für die weitere Entwicklung anderer Bibliotheken erforderlich sind:
- In der ersten Version bestand diese Aufgabe darin, so viele Adaptermodule wie möglich für gängige JS-Engines hinzuzufügen, die die .NET-Plattform unterstützen. Dies gab den Benutzern von Bundle Transformer eine gewisse Flexibilität: Auf den Computern des Entwicklers konnten sie das MSIE- Modul verwenden, das das Debuggen von JS-Code mit Visual Studio unterstützt, und auf Servern, auf denen keine moderne Version von Internet Explorer vorhanden war oder die überhaupt nicht installiert waren, konnten sie diese verwenden V8- Modul. Einige haben es sogar geschafft, den Bundle Transformer in Mono unter Linux und Mac mit den Modulen Jurassic und Jint auszuführen.
- Die Hauptaufgabe der zweiten Version war die Implementierung der .NET Core-Unterstützung, die für die neue Version der ReactJS.NET- Bibliothek erforderlich war. Eine weitere wichtige Aufgabe war die Erstellung eines plattformübergreifenden Moduls, mit dem große Mengen an JS-Code schnell verarbeitet werden können (Jurassic- und Jint-Module waren dafür nicht geeignet). Nach einer Reihe von Verbesserungen wurde das ChakraCore- Modul zu einem solchen Modul.
- In der dritten Version lag der Schwerpunkt auf der Verbesserung der Integration in die ReactJS.NET-Bibliothek und der Verbesserung der Produktivität.
In diesem Artikel werden einige Neuerungen der dritten Version betrachtet, die sich für viele selbst nach dem Lesen des Release-Textes und des Dokumentationsabschnitts „So aktualisieren Sie Anwendungen auf Version 3.X“ als nicht JsEngineSwitcher
erwiesen haben : Änderungen in der JsEngineSwitcher
Klasse, Neuorganisation von Ausnahmen, informativere Fehlermeldungen, Unterbrechung und vorläufige Zusammenstellung von Skripten, die Möglichkeit, die maximale Stapelgröße in ChakraCore- und MSIE-Modulen zu ändern, sowie ein neues Modul basierend auf NiL.JS.
Änderungen an der JsEngineSwitcher-Klasse
In der neuen Version implementiert die JsEngineSwitcher
Klasse die IJsEngineSwitcher
Schnittstelle und ist kein Singleton mehr (Sie können sie mit dem new
Operator instanziieren). Verwenden Sie die Current
Eigenschaft, um anstelle der Instance
Eigenschaft eine globale Instanz Current
. Die Current
Eigenschaft hat im Gegensatz zur veralteten Instance
Eigenschaft den Rückgabetyp IJsEngineSwitcher
. Außerdem verfügt die Current
Eigenschaft über einen Setter, mit dem Sie die Standardimplementierung durch Ihre eigene ersetzen können:
JsEngineSwitcher.Current = new MyJsEngineSwitcher();
In ASP.NET Core-Webanwendungen, in denen das Paket JavaScriptEngineSwitcher.Extensions.MsDependencyInjection installiert ist, wird die Implementierung mithilfe der Erweiterungsmethode AddJsEngineSwitcher
:
… using JavaScriptEngineSwitcher.Extensions.MsDependencyInjection; … public class Startup { … public void ConfigureServices(IServiceCollection services) { … services.AddJsEngineSwitcher(new MyJsEngineSwitcher(), options => … ) … ; … } … } …
Diese Änderungen "brechen" fast immer Anwendungen oder Bibliotheken, die die vorherige Version des JavaScript Engine Switcher verwenden. Daher müssen Sie die folgenden Änderungen an Ihrem Code vornehmen:
- Ändern Sie den Typ der Variablen, Parameter oder Eigenschaften von
JsEngineSwitcher
in IJsEngineSwitcher
. - Verwenden Sie anstelle der
Instance
Eigenschaft überall die Current
Eigenschaft.
Es ist auch erwähnenswert, dass diese Änderungen für die meisten Entwickler nicht von besonderem Nutzen sind, da ihr Hauptziel darin bestand, Komponententests zu vereinfachen (beispielsweise wurden zuvor in den Komponententests der ReactJS.NET-Bibliothek Sperren verwendet , jetzt können Sie jedoch auf sie verzichten ).
Neuordnung von Ausnahmen
Vor der dritten Version wurden die meisten JS-Engine-Fehler zu JsRuntimeException
, und nur Fehler, die während des Engine-Initialisierungsprozesses auftraten, wurden zu JsEngineLoadException
. Es gab auch eine Basisklasse JsException
, von der zwei der oben genannten Arten von Ausnahmen geerbt wurden, wodurch es möglich wurde, absolut alle Fehler abzufangen, die während des Betriebs von JS-Engines auftraten. Trotz der offensichtlichen Nachteile passt diese Organisation von Ausnahmen gut zum Konzept einer einheitlichen Schnittstelle für den Zugriff auf die grundlegenden Funktionen von JS-Engines.
Mit der Implementierung der Unterbrechung und Vorkompilierung von Skripten (ich werde sie in den folgenden Abschnitten erörtern) entstand jedoch die Notwendigkeit eines neuen Ansatzes zur Organisation von Ausnahmen. Als erstes musste eine neue Art von Ausnahme JsInterruptedException
- JsInterruptedException
, die erforderlich war, um den Benutzer über die Unterbrechung der Skriptausführung zu benachrichtigen. Dann war es notwendig, alle Fehler, die während der Skriptverarbeitung auftraten, explizit in zwei Gruppen zu unterteilen: Kompilierungsfehler (Parsing) und Laufzeitfehler. Es war auch erforderlich, alle Arten spezifischer Fehler von Chakra und V8 zu trennen, die nicht mit der Verarbeitung von Skripten zusammenhängen. Es war auch erforderlich, das Vorhandensein einer Ausnahme in der Jint-Engine zu berücksichtigen, die auftritt, wenn die Zeitüberschreitung für die Ausführung eines Skripts (Timeout) abgelaufen ist. Als Ergebnis wurde ein neuer Ansatz für die Organisation von Ausnahmen entwickelt, der als folgende hierarchische Struktur dargestellt werden kann:
JsException
JsEngineException
JsFatalException
JsScriptException
JsCompilationException
JsRuntimeException
JsInterruptedException
JsTimeoutException
JsUsageException
JsEngineNotFoundException
*
* - Diese Ausnahme tritt nicht auf der Ebene der JS-Engine auf, sondern auf der Ebene des JavaScript Engine Switcher.
Ich denke, dass die oben dargestellte Hierarchie der Ausnahmen keine Kommentare benötigt, da die Namen der Ausnahmen für sich selbst sprechen. Mit diesem Ansatz erhalten wir nicht nur mehr Informationen über die Fehlerursachen, sondern können auch bestimmte Arten von Ausnahmen flexibler behandeln.
Einheitliches Fehlermeldungsformat
Ein weiteres Problem mit früheren Versionen des JavaScript Engine Switcher war die Schwierigkeit, Fehler zu finden, die bei der Verarbeitung von Skripten aufgetreten sind. Anhand der Message
Ausnahmeeigenschaft war es schwierig, genau zu verstehen, wo der Fehler aufgetreten ist. Daher musste ich andere Ausnahmeeigenschaften analysieren, was nicht immer praktisch war. Darüber hinaus reichte die Menge der vorhandenen Eigenschaften auch nicht aus.
Daher wurden der JsScriptException
Klasse zwei neue Eigenschaften hinzugefügt:
- Typ - Typ des JavaScript-Fehlers (z. B.
SyntaxError
oder TypeError
); - DocumentName - Der Name des Dokuments (normalerweise aus den Werten der folgenden Parameter extrahiert:
documentName
der Execute
und Evaluate
Methoden, path
der ExecuteFile
Methode, resourceName
der ExecuteResource
Methode usw.);
Eine neue Eigenschaft wurde auch zur JsRuntimeException
Klasse hinzugefügt - CallStack , die eine Zeichenfolgendarstellung des Aufrufstapels enthält.
Bisher wurde ein Wert aus einer ähnlichen Eigenschaft der ursprünglichen .NET-Ausnahme oder eine Zeichenfolgendarstellung eines JavaScript-Fehlers einfach in die Message
Eigenschaft kopiert. Häufig unterschieden sich Fehlermeldungen in verschiedenen JS-Engines nicht nur im Format, sondern auch in der Menge der darin enthaltenen nützlichen Informationen. Aufgrund des Mangels an Informationen zu Zeilen- und Spaltennummern in einigen Fehlermeldungen mussten die Entwickler der ReactJS.NET-Bibliothek beispielsweise die vom JavaScript Engine Switcher empfangenen Ausnahmen erneut auslösen.
Aus diesem Grund habe ich beschlossen, meine eigenen Fehlermeldungen auf der Ebene des Adaptermoduls zu generieren, die ein einziges (einheitliches) Format haben würden. Dieses Format verwendet alle verfügbaren Fehlerinformationen: Typ, Beschreibung, Dokumentname, Zeilennummer, Spaltennummer, Codefragment und Aufrufstapel. Als Grundlage für das neue Format habe ich das Fehlerformat aus der Microsoft ClearScript- Bibliothek übernommen.
Nachfolgend finden Sie die Meldungen zu demselben Kompilierungsfehler, die von verschiedenen Adaptermodulen generiert wurden:
ChakraCore ========== SyntaxError: Unexpected identifier after numeric literal at declinationOfSeconds.js:12:23 -> caseIndex = number % 1O < 5 ? number % 10 : 5; Jint ==== SyntaxError: Unexpected token ILLEGAL at declinationOfSeconds.js:12:25 Jurassic ======== SyntaxError: Expected operator but found 'O' at declinationOfSeconds.js:12 MSIE Classic ===================== SyntaxError: Expected ';' at declinationOfSeconds.js:12:25 -> caseIndex = number % 1O < 5 ? number % 10 : 5; MSIE Chakra ActiveScript ================================= SyntaxError: Expected ';' at declinationOfSeconds.js:12:25 -> caseIndex = number % 1O < 5 ? number % 10 : 5; MSIE Chakra IE JsRT ============================ SyntaxError: Expected ';' at 12:25 -> caseIndex = number % 1O < 5 ? number % 10 : 5; MSIE Chakra Edge JsRT ============================== SyntaxError: Unexpected identifier after numeric literal at declinationOfSeconds.js:12:23 -> caseIndex = number % 1O < 5 ? number % 10 : 5; NiL === SyntaxError: Unexpected token 'O' at 12:25 V8 == SyntaxError: Invalid or unexpected token at declinationOfSeconds.js:12:24 -> caseIndex = number % 1O < 5 ? number % 10 : 5; Vroom ===== SyntaxError: Unexpected token ILLEGAL at declinationOfSeconds.js:12:24
Ein ähnliches Beispiel für einen Laufzeitfehler:
ChakraCore ========== TypeError: Unable to get property '' of undefined or null reference at transliterate (russian-translit.js:929:4) -> newCharValue = typeof charMapping[charValue] !== 'undefined' ? at Global code (Script Document:1:1) Jint ==== TypeError: charMapping is undefined at russian-translit.js:929:26 Jurassic ======== TypeError: undefined cannot be converted to an object at transliterate (russian-translit.js:929) at Global code (Script Document:1) MSIE Classic ===================== TypeError: 'undefined' is null or not an object at russian-translit.js:929:4 MSIE Chakra ActiveScript ================================= TypeError: Unable to get property '' of undefined or null reference at russian-translit.js:929:4 MSIE Chakra IE JsRT ============================ TypeError: Unable to get property '' of undefined or null reference at transliterate (russian-translit.js:929:4) at Global code (Script Document:1:1) MSIE Chakra Edge JsRT ============================== TypeError: Unable to get property '' of undefined or null reference at transliterate (russian-translit.js:929:4) at Global code (Script Document:1:1) NiL === TypeError: Can't get property "" of "undefined" V8 == TypeError: Cannot read property '' of undefined at transliterate (russian-translit.js:929:37) -> newCharValue = typeof charMapping[charValue] !== 'undefined' ? at Script Document:1:1 Vroom ===== TypeError: Cannot read property '' of undefined at russian-translit.js:929:37
Wie Sie den Beispielen entnehmen können, geben uns einige JS-Engines völlig andere Fehlerbeschreibungen und Spaltennummern, und wir können nicht immer einen vollständigen Satz von Fehlerdaten erhalten. Trotz dieser Mängel liefert uns das einheitliche Format mehr Informationen über den Fehlerort als ursprüngliche Fehlermeldungen.
Tipps zur Bereitstellung nativer Assemblys
Die Hauptursache für Fehler bei der Arbeit mit der zweiten Version des JavaScript Engine Switcher war, dass viele Entwickler vergessen haben, NuGet-Pakete mit nativen Assemblys für ChakraCore- und V8-Module zu installieren. Zu einer Zeit war auch ein Beitrag im ReactJS.NET-Bugtracker diesem Problem gewidmet ( russische Übersetzung ist ebenfalls verfügbar). Ein solcher Fehler tritt heute hauptsächlich bei Anfängern auf, die aus irgendeinem Grund die Dokumentation nicht gelesen haben.
Die Autoren von ReactJS.NET haben versucht, die Anzahl solcher Fehler mithilfe der Hinweise in den Fehlermeldungen zu minimieren, aber die nicht sehr erfolgreiche Implementierung dieses Ansatzes führte zu noch mehr Verwirrung . Die Idee der Hinweise war gut, erforderte jedoch eine grundlegend andere Implementierung, nämlich die Implementierung auf der Ebene der Adaptermodule von JS-Engines. In der neuen Version des JavaScript Engine Switcher werden solche Hinweise zur Fehlermeldung hinzugefügt, wenn DllNotFoundException
und TypeLoadException
Ausnahmen JsEngineLoadException
(siehe Implementierungsbeispiele für ChakraCore- , V8- und Vroom- Module). Darüber hinaus sind diese Tipps intelligent, weil Ihre Generierung berücksichtigt eine Reihe von Faktoren: Betriebssystemtyp, Prozessorarchitektur und Laufzeit (.NET Framework, .NET Core oder Mono).
Wenn Sie beispielsweise das ChakraCore-Modul ohne native Assembly in einem 64-Bit-Prozess unter dem Windows-Betriebssystem verwenden, sieht die Fehlermeldung folgendermaßen aus:
Instanz der ChakraCoreJsEngine konnte nicht erstellt werden. Höchstwahrscheinlich ist es passiert, weil die Assembly 'ChakraCore.dll' oder eine ihrer Abhängigkeiten nicht gefunden wurde. Versuchen Sie, das Paket JavaScriptEngineSwitcher.ChakraCore.Native.win-x64 über NuGet zu installieren. Darüber hinaus müssen Sie noch Microsoft Visual C ++ Redistributable für Visual Studio 2017 installieren ( https://www.visualstudio.com/downloads/#microsoft-visual-c-redistributable-for-visual-studio-2017 ).
Die Fehlermeldung gibt einen Hinweis darauf, dass Sie das JavaScript NuGet packageEngineSwitcher.ChakraCore.Native.win-x64 installieren müssen, und erwähnt auch, dass ChakraCore für Windows die umverteilbare Microsoft Visual C ++ - Komponente für Visual Studio 2017 benötigt, um zu funktionieren. Wenn dieser Fehler auftritt Wenn es in einem 32-Bit-Prozess auftritt, wird der Benutzer aufgefordert, das Paket JavaScriptEngineSwitcher.ChakraCore.Native.win-x86 zu installieren.
Eine ähnliche Fehlermeldung unter Linux in .NET Core würde folgendermaßen aussehen:
Instanz der ChakraCoreJsEngine konnte nicht erstellt werden. Höchstwahrscheinlich ist es passiert, weil die Assembly 'libChakraCore.so' oder eine ihrer Abhängigkeiten nicht gefunden wurde. Versuchen Sie, das Paket JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64 über NuGet zu installieren.
In diesem Fall wird empfohlen, das Paket JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64 zu installieren.
Beim Start in Mono wird eine weitere Eingabeaufforderung angezeigt:
... JavaScriptEngineSwitcher.ChakraCore.Native.linux- * -Pakete unterstützen die Installation unter Mono nicht. Sie können die native Assembly jedoch manuell installieren ( https://github.com/Taritsyn/JavaScriptEngineSwitcher/wiki/ChakraCore#linux ).
Da das Paket JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64 nur mit .NET Core kompatibel ist, enthält der Tooltip einen Link zu Anweisungen zum manuellen Bereitstellen der nativen Assembly unter Linux.
Es können noch viele weitere Beispiele angeführt werden, aber dies macht keinen Sinn.
Skriptausführung abbrechen
Wenn wir Benutzern die Möglichkeit geben, beliebigen JS-Code auf dem Server auszuführen, stehen wir vor einem sehr schwerwiegenden Problem: Wir wissen nicht, wie lange die Ausführung dieses Codes dauern wird. Dies kann eine große Menge nicht optimalen Codes oder Codes sein, der eine Endlosschleife ausführt. In jedem Fall ist es unkontrollierter Code, der die Ressourcen unseres Servers auf unbestimmte Zeit verbraucht. Um diesen Prozess irgendwie zu steuern, müssen wir die Ausführung von Skripten unterbrechen können. Wenn Sie in reinem .NET geschriebene Engines verwenden (z. B. Jint, Jurassic oder NiL.JS), können Sie JS-Code immer als Task mit der Fähigkeit zum Abbrechen ausführen. Dieser Ansatz funktioniert jedoch nicht für andere Engines. Zum Glück verfügen C ++ - Engines über integrierte Mechanismen zum Unterbrechen von Skripten.
Um den Zugriff auf diese Mechanismen zu ermöglichen, wurden der IJsEngine
Schnittstelle die SupportsScriptInterruption
Eigenschaft und die Interrupt
Methode hinzugefügt. Da diese Funktion nicht von allen Engines unterstützt wird, sollten Sie vor dem Aufrufen der Interrupt
Methode immer den Wert der SupportsScriptInterruption
Eigenschaft überprüfen (wenn Sie in früheren Versionen des JavaScript Engine Switcher den Garbage Collector manuell ausführen mussten, werden Sie sofort verstehen, wovon ich spreche):
if (engine.SupportsScriptInterruption) { engine.Interrupt(); }
Und Sie müssen diese Methode in einem separaten Thread aufrufen, der sich von dem Thread unterscheidet, in dem die Skripte ausgeführt werden. Nach dem Aufrufen der Interrupt
Methode enden alle zuvor ausgeführten Evaluate
, Execute*
und CallFunction
mit einer JsInterruptedException
.
Da diese API auf niedriger Ebene ausgeführt wird, wird empfohlen, für die am Anfang des Abschnitts beschriebenen Aufgaben Erweiterungsmethoden wie die folgenden zu verwenden:
using System; #if !NET40 using System.Runtime.ExceptionServices; #endif using System.Threading; using System.Threading.Tasks; using JavaScriptEngineSwitcher.Core; #if NET40 using JavaScriptEngineSwitcher.Core.Extensions; #endif using JavaScriptEngineSwitcher.Core.Resources; … /// <summary> /// Extension methods for <see cref="IJsEngine"/> /// </summary> public static class JsEngineExtensions { /// <summary> /// Evaluates an expression within a specified time interval /// </summary> /// <typeparam name="T">Type of result</typeparam> /// <param name="engine">JS engine</param> /// <param name="expression">JS expression</param> /// <param name="timeoutInterval">Interval to wait before the /// script execution times out</param> /// <param name="documentName">Document name</param> /// <returns>Result of the expression</returns> /// <exception cref="ObjectDisposedException"/> /// <exception cref="ArgumentNullException"/> /// <exception cref="ArgumentException"/> /// <exception cref="JsCompilationException"/> /// <exception cref="JsTimeoutException"/> /// <exception cref="JsRuntimeException"/> /// <exception cref="JsException"/> public static T Evaluate<T>(this IJsEngine engine, string expression, TimeSpan timeoutInterval, string documentName) { if (engine == null) { throw new ArgumentNullException(nameof(engine)); } if (engine.SupportsScriptInterruption) { using (var timer = new Timer(state => engine.Interrupt(), null, timeoutInterval, #if NET40 new TimeSpan(0, 0, 0, 0, -1))) #else Timeout.InfiniteTimeSpan)) #endif { try { return engine.Evaluate<T>(expression, documentName); } catch (JsInterruptedException e) { throw new JsTimeoutException( Strings.Runtime_ScriptTimeoutExceeded, e.EngineName, e.EngineVersion, e ); } } } else { #if NET40 Task<T> task = Task.Factory.StartNew(() => #else Task<T> task = Task.Run(() => #endif { return engine.Evaluate<T>(expression, documentName); }); bool isCompletedSuccessfully = false; try { isCompletedSuccessfully = task.Wait(timeoutInterval); } catch (AggregateException e) { Exception innerException = e.InnerException; if (innerException != null) { #if NET40 innerException.PreserveStackTrace(); throw innerException; #else ExceptionDispatchInfo.Capture(innerException).Throw(); #endif } else { throw; } } if (isCompletedSuccessfully) { return task.Result; } else { throw new JsTimeoutException( Strings.Runtime_ScriptTimeoutExceeded, engine.Name, engine.Version ); } } } … } …
Diese Methode ist ein Add-On zur Engine-Methode Evaluate<T>
, mit der mithilfe des Parameters timeoutInterval
das Zeitlimit für das Warten auf die Ausführung des Skripts festgelegt werden kann. Das Funktionsprinzip dieser Erweiterungsmethode ist sehr einfach. Zunächst prüfen wir, ob unser Motor den eingebauten Interrupt-Mechanismus unterstützt. In diesem timeoutInterval
erstellen wir eine Instanz der Timer
Klasse, die nach dem im Parameter timeoutInterval
angegebenen timeoutInterval
die Interrupt
Methode startet. Anschließend rufen wir die Engine-Methode Evaluate<T>
Fehlerfall wird die JsInterruptedException
und in eine JsTimeoutException
. Wenn die Engine keine Interrupts unterstützt, erstellen Sie eine Instanz der Task
Klasse, in der die Evaluate<T>
-Methode der Engine ausgeführt wird, und timeoutInterval
das timeoutInterval
die timeoutInterval
Task auf den Wert des Parameters timeoutInterval
Wenn die Task durch JsTimeoutException
abgeschlossen wird, JsTimeoutException
Typ JsTimeoutException
. Das Folgende ist ein Beispiel für die Verwendung dieser Erweiterungsmethode:
using System; … using JavaScriptEngineSwitcher.Core; using JavaScriptEngineSwitcher.Core.Helpers; … class Program { … static void Main(string[] args) { const string expression = @"function getRandomInt(minValue, maxValue) { minValue = Math.ceil(minValue); maxValue = Math.floor(maxValue); return Math.floor(Math.random() * (maxValue - minValue + 1)) + minValue; } function sleep(millisecondsTimeout) { var totalMilliseconds = new Date().getTime() + millisecondsTimeout; while (new Date().getTime() < totalMilliseconds) { } } var randomNumber = getRandomInt(1, 10); sleep(randomNumber * 1000); randomNumber;"; using (IJsEngine engine = JsEngineSwitcher.Current.CreateDefaultEngine()) { try { int result = engine.Evaluate<int>(expression, TimeSpan.FromSeconds(3), "randomNumber.js"); Console.WriteLine(" = {0}", result); } catch (JsTimeoutException) { Console.WriteLine(" JavaScript " + " !"); } catch (JsException e) { Console.WriteLine(" JavaScript- " + "!"); Console.WriteLine(); Console.WriteLine(JsErrorHelpers.GenerateErrorDetails(e)); } } } … } …
Mit der Erweiterungsmethode berechnen wir das Ergebnis des JS-Ausdrucks. Das Ergebnis des Ausdrucks ist eine zufällige Ganzzahl, und diese Zahl ist auch die Anzahl der Sekunden, um die dieser Ausdruck verzögert wird. Außerdem geben wir beim Aufrufen der Erweiterungsmethode ein Warteintervall von 3 Sekunden an. Da die Zeit für die Berechnung des Ausdrucks zwischen 1 und 10 Sekunden variiert, wird in einem Fall das Ergebnis des Ausdrucks auf der Konsole angezeigt und in einem anderen Fall eine Meldung über das Zeitlimit.
Es sollte für Sie nicht schwierig sein, diese Erweiterungsmethode zu wiederholen, um andere Methoden der Engine aufzurufen (z. B. Execute*
, CallFunction
oder andere überladene Versionen der Evaluate
Methode). Bisher habe ich nicht entschieden, ob ich der Bibliothek selbst solche Erweiterungsmethoden hinzufügen werde, da noch nicht klar ist, wie stark sie nachgefragt werden.
Skript-Vorkompilierung
Ich wurde gebeten, Unterstützung für die vorläufige Kompilierung von Skripten im Jahr 2015 hinzuzufügen. Zu diesem Zeitpunkt war nicht klar, wie eine solche Funktion in das Konzept einer einheitlichen Schnittstelle passen könnte. Mit der Zeit wurde jedoch allmählich Unterstützung für Eingabehilfen wie Speicherbereinigung und Skriptunterbrechung im JavaScript Engine Switcher angezeigt. In den letzten Phasen der Arbeit an der dritten Version kam es zur vorläufigen Zusammenstellung.
Mithilfe der Vorkompilierung können Sie ein Skript einmal kompilieren und dann mehrmals zum Initialisieren von JS-Engines verwenden. Aufgrund der Tatsache, dass für das vorkompilierte Skript keine Analyse erforderlich ist, erfolgt die Initialisierung der Engines schneller.
Derzeit wird die Vorkompilierung von 5 Adaptermodulen unterstützt: ChakraCore, Jint, Jurassic, MSIE (nur im JsRT-Modus) und V8. Um den Zugriff auf die entsprechenden Engine-Mechanismen zu ermöglichen, wurden der IJsEngine
Schnittstelle die Eigenschaft SupportsScriptPrecompilation
und drei neue Methoden hinzugefügt: Precompile
, PrecompileFile
und PrecompileResource
. Precompile*
-Methoden geben eine Instanz eines Objekts zurück, das die IPrecompiledScript
Schnittstelle implementiert. Dieses Objekt ist ein vorkompiliertes Skript, das von verschiedenen Instanzen der Engines verwendet werden kann (eine überladene Version der Execute
Methode dient zu diesem Zweck). Betrachten Sie ein einfaches Beispiel für die Verwendung dieser API:
using System; … using JavaScriptEngineSwitcher.Core; using JavaScriptEngineSwitcher.Core.Helpers; … class Program { … static void Main(string[] args) { const string sourceCode = @"function declinationOfSeconds(number) { var result, titles = ['', '', ''], titleIndex, cases = [2, 0, 1, 1, 1, 2], caseIndex ; if (number % 100 > 4 && number % 100 < 20) { titleIndex = 2; } else { caseIndex = number % 10 < 5 ? number % 10 : 5; titleIndex = cases[caseIndex]; } result = number + ' ' + titles[titleIndex]; return result; }"; const string functionName = "declinationOfSeconds"; const int itemCount = 4; int[] inputSeconds = new int[itemCount] { 0, 1, 42, 600 }; string[] outputStrings = new string[itemCount]; IJsEngineSwitcher engineSwitcher = JsEngineSwitcher.Current; IPrecompiledScript precompiledCode = null; using (var engine = engineSwitcher.CreateDefaultEngine()) { if (!engine.SupportsScriptPrecompilation) { Console.WriteLine("{0} {1} " + " !", engine.Name, engine.Version); return; } try { precompiledCode = engine.Precompile(sourceCode, "declinationOfSeconds.js"); engine.Execute(precompiledCode); outputStrings[0] = engine.CallFunction<string>(functionName, inputSeconds[0]); } catch (JsCompilationException e) { Console.WriteLine(" " + " !"); Console.WriteLine(); Console.WriteLine(JsErrorHelpers.GenerateErrorDetails(e)); return; } catch (JsException e) { Console.WriteLine(" JavaScript- " + "!"); Console.WriteLine(); Console.WriteLine(JsErrorHelpers.GenerateErrorDetails(e)); return; } } for (int itemIndex = 1; itemIndex < itemCount; itemIndex++) { using (var engine = engineSwitcher.CreateDefaultEngine()) { try { engine.Execute(precompiledCode); outputStrings[itemIndex] = engine.CallFunction<string>( functionName, inputSeconds[itemIndex]); } catch (JsException e) { Console.WriteLine(" JavaScript- " + " !"); Console.WriteLine(); Console.WriteLine(JsErrorHelpers.GenerateErrorDetails(e)); return; } } } for (int itemIndex = 0; itemIndex < itemCount; itemIndex++) { Console.WriteLine(outputStrings[itemIndex]); } } … } …
, . SupportsScriptPrecompilation
, , , . Precompile
, , JsCompilationException
. C Execute
, .. . CallFunction
declinationOfSeconds
. . , , . 3 . , , . , , .
, , . , , . ReactJS.NET. JSPool , ( System.Runtime.Caching.MemoryCache
.NET Framework 4.X, System.Web.Caching.Cache
ASP.NET 4.X Microsoft.Extensions.Caching.Memory.IMemoryCache
ASP.NET Core). , ( ). , ReactJS.NET.
ReactJS.NET . . AllowJavaScriptPrecompilation
true
.
ASP.NET 4.X App_Start/ReactConfig.cs
:
… public static class ReactConfig { public static void Configure() { ReactSiteConfiguration.Configuration … .SetAllowJavaScriptPrecompilation(true) … ; … } } …
ASP.NET Core Startup.cs
:
… public class Startup { … public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … app.UseReact(config => { config … .SetAllowJavaScriptPrecompilation(true) … ; }); app.UseStaticFiles(); … } } …
, . JsExecutionBenchmark , BenchmarkDotNet . JS- . 14,9 , , . JavaScript Engine Switcher ( 3.0.4).
, .NET Framework 4.7.2:
| . | . | Gen 0 . . | Gen 1 . . | Gen 2 . . | . |
---|
ChakraCore | Nein | 41,72 | - | - | - | 74,46 |
Ja | 35,07 | - | - | - | 91,79 |
Jint | Nein | 27,19 | 2 812,50 | 1 343,75 | - | 16 374,58 |
Ja | 15,54 | 1 296,88 | 640,63 | 31,25 | 7 521,49 |
Jurassic | Nein | 455,70 | 2 000,00 | 1 000,00 | - | 15 575,28 |
Ja | 78,70 | 1 000,00 | - | - | 7 892,94 |
MSIE Chakra IE JsRT | Nein | 30,97 | - | - | - | 77,75 |
Ja | 24,40 | - | - | - | 90,58 |
MSIE Chakra Edge JsRT | Nein | 33,14 | - | - | - | 78,40 |
Ja | 32,86 | - | - | - | 95,48 |
V8 | Nein | 41,10 | - | - | - | 79,33 |
Ja | 39,25 | - | - | - | 96,17 |
, .NET — Jurassic 5,79 , Jint 1,75 a. 2 , 2 . . Jurassic, : Jurassic JS- IL- Reflection.Emit, . , , . Jint .NET-, . Jint , . , Jint , , . Jint Jurassic .
, C++, MSIE Chakra IE JsRT — 26,93%. ChakraCore (18,96%), V8 (4,71%) MSIE Chakra Edge JsRT (0,85%). Internet Explorer Edge. . ( ) . , - 12-17 ( ). . . .
.NET Core 2.0 ( V8 , Microsoft ClearScript, , .NET Core):
| . | . | Gen 0 . . | Gen 1 . . | Gen 2 . . | . |
---|
ChakraCore | Nein | 43,65 | - | - | - | 18,07 |
Ja | 36,37 | - | - | - | 16,59 |
Jint | Nein | 24,87 | 2 750,00 | 1 375,00 | - | 16 300,25 |
Ja | 15,25 | 1 281,25 | 593,75 | 62,50 | 7 447,44 |
Jurassic | Nein | 469,97 | 2 000,00 | 1 000,00 | - | 15 511,70 |
Ja | 80,72 | 1 000,00 | - | - | 7 845,98 |
MSIE Chakra IE JsRT | Nein | 31,50 | - | - | - | 20,28 |
Ja | 24,52 | - | - | - | 18,78 |
MSIE Chakra Edge JsRT | Nein | 35,54 | - | - | - | 20,45 |
Ja | 31,44 | - | - | - | 18,99 |
. , — MSIE Chakra Edge JsRT (7,69%). Chakra .
ChakraCore MSIE
, Microsoft, , . IIS (256 32- 512 64-), ASP.NET JS- (, TypeScript) . JavaScript Engine Switcher . , Node.js (492 32- 984 64-). , , - . ChakraCore MSIE MaxStackSize
, . Node.js. , .
NiL.JS
- NiL , NiL.JS . NiL.JS — JS-, .NET. 2014 , Jurassic Jint. — . , .
.NET Framework 4.7.2 :
| . | Gen 0 . . | Gen 1 . . | Gen 2 . . | . |
---|
Jint | 27,19 | 2 812,50 | 1 343,75 | - | 16 374,58 |
Jurassic | 455,70 | 2 000,00 | 1 000,00 | - | 15 575,28 |
NiL | 17,80 | 1 000,00 | - | - | 4 424,09 |
.NET Core 2.0 :
| . | Gen 0 . . | Gen 1 . . | Gen 2 . . | . |
---|
Jint | 24,87 | 2 750,00 | 1 375,00 | - | 16 300,25 |
Jurassic | 469,97 | 2 000,00 | 1 000,00 | - | 15 511,70 |
NiL | 19,67 | 1 000,00 | - | - | 4 419,95 |
. , ( ). , Jint 2016 , . 3.0.0 Beta 1353 , Jint 2,4 , NiL.JS 2.5.1200, .
NiL.JS . - , - , . Bundle Transformer, JavaScript Engine Switcher, Hogan Handlebars, ReactJS.NET. NiL.JS.
Referenzen
- JavaScript Engine Switcher GitHub
- JavaScript Engine Switcher
- « JavaScript Engine Switcher 2.X»
- JSPool GitHub
- ReactJS.NET
- ReactJS.NET GitHub