Ce que vous devez savoir sur JavaScript Engine Switcher 3.0

Logo de la troisième version du sélecteur de moteur JavaScript


Le JavaScript Engine Switcher a été initialement créé en tant que bibliothèque auxiliaire et son développement a été largement déterminé par les besoins des bibliothèques qui l'ont utilisé. En fait, chacune de ses versions majeures a résolu une ou plusieurs tâches majeures nécessaires au développement ultérieur d'autres bibliothèques:


  1. Dans la première version, cette tâche consistait à ajouter autant de modules adaptateurs que possible pour les moteurs JS populaires prenant en charge la plate-forme .NET. Et cela a donné aux utilisateurs de Bundle Transformer une certaine flexibilité: sur les ordinateurs du développeur, ils pouvaient utiliser le module MSIE qui prend en charge le débogage du code JS à l'aide de Visual Studio, et sur les serveurs qui n'avaient pas de version moderne d'Internet Explorer ou qui n'étaient pas installés du tout, ils pouvaient utiliser Module V8 . Certains ont même réussi à exécuter Bundle Transformer en Mono sur Linux et Mac en utilisant les modules Jurassic et Jint .
  2. La tâche principale de la deuxième version était l'implémentation de la prise en charge de .NET Core, qui était requise pour la nouvelle version de la bibliothèque ReactJS.NET . Une autre tâche importante a été la création d'un module multiplateforme qui peut rapidement traiter de grandes quantités de code JS (les modules Jurassic et Jint n'étaient pas adaptés à cela), et après un certain nombre d'améliorations, le module ChakraCore est devenu un tel module.
  3. Dans la troisième version, l'accent était mis sur l'amélioration de l'intégration avec la bibliothèque ReactJS.NET et l'amélioration de la productivité.

Dans cet article, nous considérerons certaines innovations de la troisième version, qui pour beaucoup se sont révélées non évidentes même après avoir lu le texte de la version et la section de documentation «Comment mettre à niveau les applications vers la version 3.X» : changements dans la classe JsEngineSwitcher , réorganisation des exceptions, messages d'erreur plus informatifs, interruption et la compilation préliminaire des scripts, la possibilité de modifier la taille maximale de la pile dans les modules ChakraCore et MSIE, ainsi qu'un nouveau module basé sur NiL.JS.


Modifications de la classe JsEngineSwitcher


Dans la nouvelle version, la classe JsEngineSwitcher implémente l'interface IJsEngineSwitcher et n'est plus un singleton (vous pouvez l'instancier en utilisant le new opérateur). Pour obtenir une instance globale, au lieu de la propriété Instance , utilisez la propriété Current . La propriété Current , contrairement à la propriété Instance obsolète, a le type de retour IJsEngineSwitcher . De plus, la propriété Current a un setter, avec lequel vous pouvez remplacer l'implémentation standard par la vôtre:


 JsEngineSwitcher.Current = new MyJsEngineSwitcher(); 

Dans les applications Web ASP.NET Core sur lesquelles le package JavaScriptEngineSwitcher.Extensions.MsDependencyInjection est installé, l'implémentation est remplacée à l'aide de la AddJsEngineSwitcher extension AddJsEngineSwitcher :


 … using JavaScriptEngineSwitcher.Extensions.MsDependencyInjection; … public class Startup { … public void ConfigureServices(IServiceCollection services) { … services.AddJsEngineSwitcher(new MyJsEngineSwitcher(), options => … ) … ; … } … } … 

Ces modifications «cassent» presque toujours les applications ou bibliothèques qui utilisent la version précédente de JavaScript Engine Switcher. Par conséquent, vous devez apporter les modifications suivantes à votre code:


  1. Modifiez le type de variables, paramètres ou propriétés de JsEngineSwitcher à IJsEngineSwitcher .
  2. Au lieu de la propriété Instance , utilisez la propriété Current partout.

Il convient également de noter que pour la plupart des développeurs, ces modifications ne seront pas particulièrement avantageuses, car leur objectif principal était de simplifier les tests unitaires (par exemple, auparavant, dans les tests unitaires de la bibliothèque ReactJS.NET, des verrous étaient utilisés , mais maintenant vous pouvez vous en passer ).


Réorganisation des exceptions


Avant la troisième version, la plupart des erreurs de moteur JS se transformaient en JsRuntimeException type JsRuntimeException et seules les erreurs survenues pendant le processus d'initialisation du moteur se transformaient en JsEngineLoadException . Il y avait également une classe de base JsException , dont deux des types d'exceptions ci-dessus ont été hérités, ce qui a permis d'intercepter absolument toutes les erreurs survenues lors du fonctionnement des moteurs JS. Malgré les inconvénients évidents, cette organisation des exceptions correspond bien au concept d'une interface unifiée pour accéder aux capacités de base des moteurs JS.


Mais avec la mise en œuvre de l'interruption et de la pré-compilation des scripts (je les aborderai dans les sections suivantes), un besoin s'est fait sentir pour une nouvelle approche de l'organisation des exceptions. La première chose à faire a été d'ajouter un nouveau type d'exception - JsInterruptedException , qui était nécessaire pour informer l'utilisateur de l'interruption de l'exécution du script. Il a ensuite fallu diviser explicitement toutes les erreurs survenues lors du traitement du script en deux groupes: les erreurs de compilation (analyse) et les erreurs d'exécution. Il était également nécessaire de séparer toutes sortes d'erreurs spécifiques de Chakra et V8, qui n'étaient pas liées au traitement de script. Il était également nécessaire de prendre en compte la présence d'une exception dans le moteur Jint qui se produit lorsque le délai d'expiration pour l'exécution d'un script (timeout) s'est écoulé. En conséquence, une nouvelle approche de l'organisation des exceptions a été formée, qui peut être représentée comme la structure hiérarchique suivante:


  • JsException
    • JsEngineException
      • JsEngineLoadException
    • JsFatalException
    • JsScriptException
      • JsCompilationException
      • JsRuntimeException
        • JsInterruptedException
        • JsTimeoutException
    • JsUsageException
  • JsEngineNotFoundException *

* - cette exception ne se produit pas au niveau du moteur JS, mais au niveau du sélecteur de moteur JavaScript.


Je pense que la hiérarchie des exceptions présentée ci-dessus n'a pas besoin de commentaires, car les noms des exceptions parlent d'eux-mêmes. Avec cette approche, nous obtenons non seulement plus d'informations sur les causes de l'erreur, mais nous pouvons également gérer de manière plus flexible certains types d'exceptions.


Format de message d'erreur unifié


Un autre problème avec les versions précédentes de JavaScript Engine Switcher était la difficulté de localiser les erreurs survenues lors du traitement des scripts. À partir de la propriété d'exception Message , il était difficile de comprendre exactement où l'erreur s'est produite dans le code, j'ai donc dû analyser d'autres propriétés d'exception, ce qui n'était pas toujours pratique. De plus, l'ensemble des propriétés existantes n'était pas non plus suffisant.


Par conséquent, 2 nouvelles propriétés ont été ajoutées à la classe JsScriptException :


  1. Type - type d'erreur JavaScript (par exemple, SyntaxError ou TypeError );
  2. DocumentName - le nom du document (généralement extrait des valeurs des paramètres suivants: documentName des méthodes Execute et ExecuteFile , path la méthode ExecuteFile , resourceName la méthode ExecuteResource , etc.);

Une nouvelle propriété a également été ajoutée à la classe JsRuntimeException - CallStack , qui contient une représentation sous forme de chaîne de la pile d'appels.


Auparavant, une valeur provenant d'une propriété similaire de l'exception .NET d'origine ou une représentation sous forme de chaîne d'une erreur JavaScript était simplement copiée dans la propriété Message . Souvent, les messages d'erreur dans les différents moteurs JS diffèrent non seulement par le format, mais également par la quantité d'informations utiles qui y sont présentées. Par exemple, en raison du manque d'informations sur les numéros de ligne et de colonne dans certains messages d'erreur, les développeurs de la bibliothèque ReactJS.NET ont été contraints de renvoyer les exceptions reçues du JavaScript Engine Switcher.


Par conséquent, j'ai décidé de générer mes propres messages d'erreur au niveau du module adaptateur, qui auraient un format unique (unifié). Ce format utilise toutes les informations d'erreur disponibles: type, description, nom du document, numéro de ligne, numéro de colonne, fragment de code et pile d'appels. Comme base pour le nouveau format, j'ai pris le format d'erreur de la bibliothèque Microsoft ClearScript .


Voici les messages sur la même erreur de compilation qui ont été générés par différents modules d'adaptateur:


 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 

Un exemple similaire pour une erreur d'exécution:


 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 

Comme vous pouvez le voir dans les exemples, certains moteurs JS nous donnent des descriptions d'erreurs et des numéros de colonnes complètement différents, et nous ne pouvons pas toujours obtenir un ensemble complet de données d'erreur, mais malgré ces lacunes, le format unifié nous donne plus d'informations sur l'emplacement de l'erreur que messages d'erreur d'origine.


Conseils de déploiement de l'assembly natif


La principale cause d'erreurs lors de l'utilisation de la deuxième version de JavaScript Engine Switcher était que de nombreux développeurs ont oublié d'installer des packages NuGet contenant des assemblages natifs pour les modules ChakraCore et V8. À un moment donné, un article dans le bugtracker ReactJS.NET a également été consacré à ce problème (une traduction russe est également disponible). Or, une telle erreur n'est principalement rencontrée que par des débutants qui, pour une raison quelconque, n'ont pas lu la documentation.


Les auteurs de ReactJS.NET ont essayé de minimiser le nombre de ces erreurs en utilisant les conseils à l'intérieur des messages d'erreur, mais la mise en œuvre pas très réussie de cette approche a conduit à encore plus de confusion . L'idée des astuces était bonne, mais elle nécessitait une implémentation fondamentalement différente, à savoir une implémentation au niveau des modules adaptateurs des moteurs JS. Dans la nouvelle version de JavaScript Engine Switcher, ces indications sont ajoutées au message d'erreur lors de l' TypeLoadException exceptions DllNotFoundException et TypeLoadException JsEngineLoadException (voir les exemples d'implémentation des modules ChakraCore , V8 et Vroom ). De plus, ces conseils sont intelligents, car leur génération prend en compte un certain nombre de facteurs: type de système d'exploitation, architecture de processeur et runtime (.NET Framework, .NET Core ou Mono).


Par exemple, lorsque vous utilisez le module ChakraCore sans assemblage natif dans un processus 64 bits sur le système d'exploitation Windows, le message d'erreur ressemblera à ceci:


Impossible de créer une instance de ChakraCoreJsEngine. Cela s'est probablement produit, car l'assembly 'ChakraCore.dll' ou l'une de ses dépendances est introuvable. Essayez d'installer le package JavaScriptEngineSwitcher.ChakraCore.Native.win-x64 via NuGet. En outre, vous devez toujours installer le Microsoft Visual C ++ Redistributable pour Visual Studio 2017 ( https://www.visualstudio.com/downloads/#microsoft-visual-c-redistributable-for-visual-studio-2017 ).

Le message d'erreur indique que vous devez installer le package NuGet JavaScriptEngineSwitcher.ChakraCore.Native.win-x64 et mentionne également que ChakraCore pour Windows nécessite le composant redistribuable Microsoft Visual C ++ pour Visual Studio 2017 pour fonctionner. Si cette erreur se produit dans un processus 32 bits, l'utilisateur sera invité à installer le package JavaScriptEngineSwitcher.ChakraCore.Native.win-x86.


Un message d'erreur similaire sur Linux dans .NET Core ressemblerait Ă  ceci:


Impossible de créer une instance de ChakraCoreJsEngine. Cela s'est probablement produit, car l'assembly «libChakraCore.so» ou l'une de ses dépendances n'a pas été trouvé. Essayez d'installer le package JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64 via NuGet.

Dans ce cas, il sera suggéré d'installer le package JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64.


Une fois lancé en Mono, une autre invite s'affiche:


... Les packages JavaScriptEngineSwitcher.ChakraCore.Native.linux- * ne prennent pas en charge l'installation sous Mono, mais vous pouvez installer l'assembly natif manuellement ( https://github.com/Taritsyn/JavaScriptEngineSwitcher/wiki/ChakraCore#linux ).

Étant donné que le package JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64 est compatible uniquement avec .NET Core, l'info-bulle fournira un lien vers des instructions pour déployer manuellement l'assembly natif sur Linux.


De nombreux autres exemples peuvent être donnés, mais cela n'a aucun sens.


Abandon de l'exécution du script


Lorsque nous donnons aux utilisateurs la possibilité d'exécuter du code JS arbitraire sur le serveur, nous sommes confrontés à un problème très grave - nous ne savons pas combien de temps il faudra pour exécuter ce code. Cela peut être une grande quantité de code non optimal ou de code qui exécute une boucle infinie. Dans tous les cas, ce sera du code non contrôlé qui consommera indéfiniment les ressources de notre serveur. Afin de contrôler en quelque sorte ce processus, nous devons avoir la possibilité d'interrompre l'exécution des scripts. Lorsque vous utilisez des moteurs écrits en .NET pur (par exemple, Jint, Jurassic ou NiL.JS), nous pouvons toujours commencer à exécuter du code JS en tant que tâche avec la possibilité d'annuler, mais cette approche ne fonctionnera pas pour d'autres moteurs. Heureusement pour nous, les moteurs C ++ ont des mécanismes intégrés pour interrompre les scripts.


Pour fournir l'accès à ces mécanismes, la propriété SupportsScriptInterruption et la méthode Interrupt ont été ajoutées à l'interface IJsEngine . Étant donné que tous les moteurs ne prennent pas en charge cette fonctionnalité, vous devez toujours vérifier la valeur de la propriété SupportsScriptInterruption avant d'appeler la méthode Interrupt (si dans les versions précédentes de JavaScript Engine Switcher vous deviez exécuter manuellement le garbage collector, vous comprendrez immédiatement de quoi je parle):


 if (engine.SupportsScriptInterruption) { engine.Interrupt(); } 

Et vous devez appeler cette méthode dans un thread distinct différent du thread dans lequel les scripts sont exécutés. Après avoir appelé la méthode Interrupt , toutes les CallFunction , Execute* et CallFunction se termineront par une JsInterruptedException .


Étant donné que cette API est de bas niveau, il est recommandé d'utiliser des méthodes d'extension comme les suivantes pour les tâches décrites au début de la section:


 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 ); } } } … } … 

Cette méthode est un module complémentaire de la méthode du moteur Evaluate<T> , qui permet d'utiliser le paramètre timeoutInterval définir le délai d'attente pour l'exécution du script. Le principe de fonctionnement de cette méthode d'extension est très simple. Tout d'abord, nous vérifions si notre moteur prend en charge le mécanisme d'interruption intégré. Si c'est le timeoutInterval , nous créons une instance de la classe Timer , qui, après l'intervalle de timeoutInterval spécifié dans le paramètre de timeoutInterval , démarre la méthode d' Interrupt , puis nous appelons la méthode du moteur Evaluate<T> , et en cas d'erreur, interceptons la JsInterruptedException et enveloppons-la dans une JsTimeoutException . Si le moteur ne prend pas en charge les interruptions, créez une instance de la classe Task qui exécute la méthode Evaluate<T> du moteur, puis définissez l'intervalle d'attente pour que la tâche soit exécutée égal à la valeur du paramètre timeoutInterval et, si la tâche se termine par timeout, lancez une JsTimeoutException type JsTimeoutException . Voici un exemple d'utilisation de cette méthode d'extension:


 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)); } } } … } … 

En utilisant la méthode d'extension, nous calculons le résultat de l'expression JS. Le résultat de l'expression est un entier aléatoire, et ce nombre est également le nombre de secondes de retard de cette expression. De plus, lors de l'appel de la méthode d'extension, nous spécifions un intervalle d'attente de 3 secondes. Étant donné que le temps de calcul de l'expression varie de 1 à 10 secondes, dans un cas, le résultat de l'expression sera affiché sur la console et dans un autre, un message sur le délai d'expiration.


Il ne devrait pas être difficile pour vous de refaire cette méthode d'extension pour appeler d'autres méthodes de moteur (par exemple, pour Execute* , les CallFunction ou d'autres versions surchargées de la méthode CallFunction ). Jusqu'à présent, je n'ai pas décidé si j'ajouterais de telles méthodes d'extension à la bibliothèque elle-même, car on ne sait pas encore combien elles seront demandées.


Précompilation de scripts


On m'a demandé d' ajouter un support pour la compilation préliminaire des scripts en 2015 . À ce moment, il n'était pas clair comment une telle fonction pourrait s'intégrer dans le concept d'une interface unifiée. Mais au fil du temps, la prise en charge des fonctionnalités d'accessibilité telles que la récupération de place et l'interruption de script a progressivement commencé à apparaître dans le moteur de commutation JavaScript. Aux dernières étapes du travail sur la troisième version, c'est au tour de la compilation préliminaire.


En utilisant la précompilation, vous pouvez compiler un script une fois, puis l'utiliser plusieurs fois pour initialiser les moteurs JS. Du fait que le script précompilé ne nécessite pas d'analyse, l'initialisation des moteurs sera plus rapide.


À l'heure actuelle, la pré-compilation est prise en charge par 5 modules adaptateurs: ChakraCore, Jint, Jurassic, MSIE (uniquement en modes JsRT) et V8. Pour donner accès aux mécanismes du moteur correspondants, la propriété SupportsScriptPrecompilation et 3 nouvelles méthodes ont été ajoutées à l'interface IJsEngine : Precompile , PrecompileFile et PrecompileResource . Precompile* méthodes Precompile* renvoient une instance d'un objet qui implémente l'interface IPrecompiledScript . Cet objet est un script précompilé qui peut être utilisé par différentes instances des moteurs (une version surchargée de la méthode Execute sert à cet effet). Prenons un exemple simple d'utilisation de cette 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 . ..
ChakraCore41,72---74,46
Oui35,07---91,79
Jint27,192 812,501 343,75-16 374,58
Oui15,541 296,88640,6331,257 521,49
Jurassic455,702 000,001 000,00-15 575,28
Oui78,701 000,00--7 892,94
MSIE Chakra IE JsRT30,97---77,75
Oui24,40---90,58
MSIE Chakra Edge JsRT33,14---78,40
Oui32,86---95,48
V841,10---79,33
Oui39,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 . ..
ChakraCore43,65---18,07
Oui36,37---16,59
Jint24,872 750,001 375,00-16 300,25
Oui15,251 281,25593,7562,507 447,44
Jurassic469,972 000,001 000,00-15 511,70
Oui80,721 000,00--7 845,98
MSIE Chakra IE JsRT31,50---20,28
Oui24,52---18,78
MSIE Chakra Edge JsRT35,54---20,45
Oui31,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 . ..
Jint27,192 812,501 343,75-16 374,58
Jurassic455,702 000,001 000,00-15 575,28
NiL17,801 000,00--4 424,09

.NET Core 2.0 :


.Gen 0 . .Gen 1 . .Gen 2 . ..
Jint24,872 750,001 375,00-16 300,25
Jurassic469,972 000,001 000,00-15 511,70
NiL19,671 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.


Les références


  1. JavaScript Engine Switcher GitHub
  2. JavaScript Engine Switcher
  3. « JavaScript Engine Switcher 2.X»
  4. JSPool GitHub
  5. ReactJS.NET
  6. ReactJS.NET GitHub

Source: https://habr.com/ru/post/fr439446/


All Articles