Lo que necesita saber sobre JavaScript Engine Switcher 3.0

Logotipo para la tercera versión de JavaScript Engine Switcher


El JavaScript Engine Switcher se creó originalmente como una biblioteca auxiliar y su desarrollo estaba determinado en gran medida por las necesidades de las bibliotecas que lo usaban. De hecho, cada una de sus versiones principales resolvió una o varias tareas importantes necesarias para el desarrollo posterior de otras bibliotecas:


  1. En la primera versión, esta tarea consistía en agregar tantos módulos adaptadores para los motores JS populares que admitieran la plataforma .NET como fuera posible. Y esto les dio a los usuarios de Bundle Transformer cierta flexibilidad: en las computadoras del desarrollador, podían usar el módulo MSIE que admite la depuración de código JS usando Visual Studio, y en servidores que no tenían una versión moderna de Internet Explorer o no estaban instalados, podían usar Módulo V8 . Algunos incluso lograron ejecutar el Bundle Transformer en Mono en Linux y Mac usando los módulos Jurassic y Jint .
  2. La tarea principal de la segunda versión fue la implementación del soporte de .NET Core, que se requería para la nueva versión de la biblioteca ReactJS.NET . Otra tarea importante fue la creación de un módulo multiplataforma que puede procesar rápidamente grandes cantidades de código JS (los módulos Jurassic y Jint no eran adecuados para esto), y después de una serie de mejoras, el módulo ChakraCore se convirtió en dicho módulo.
  3. En la tercera versión, el objetivo principal era mejorar la integración con la biblioteca ReactJS.NET y mejorar la productividad.

En este artículo, consideraremos algunas innovaciones de la tercera versión, que para muchos resultaron ser obvias incluso después de leer el texto de la versión y la sección de documentación "Cómo actualizar las aplicaciones a la versión 3.X" : cambios en la clase JsEngineSwitcher , reorganización de excepciones, mensajes de error más informativos, interrupción y compilación preliminar de scripts, la capacidad de cambiar el tamaño máximo de pila en los módulos ChakraCore y MSIE, así como un nuevo módulo basado en NiL.JS.


Cambios en la clase JsEngineSwitcher


En la nueva versión, la clase JsEngineSwitcher implementa la interfaz IJsEngineSwitcher y ya no es un singleton (puede crear una instancia usando el new operador). Para obtener una instancia global, en lugar de la propiedad Instance , use la propiedad Current . La propiedad Current , a diferencia de la propiedad Instance desuso, tiene el tipo de retorno IJsEngineSwitcher . Además, la propiedad Current tiene un setter, con el que puede reemplazar la implementación estándar por la suya propia:


 JsEngineSwitcher.Current = new MyJsEngineSwitcher(); 

En las aplicaciones web ASP.NET Core que tienen instalado el paquete JavaScriptEngineSwitcher.Extensions.MsDependencyInjection , la implementación se reemplaza utilizando el AddJsEngineSwitcher extensión AddJsEngineSwitcher :


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

Estos cambios casi siempre "rompen" las aplicaciones o bibliotecas que usan la versión anterior de JavaScript Engine Switcher. Por lo tanto, debe realizar los siguientes cambios en su código:


  1. Cambie el tipo de variables, parámetros o propiedades de JsEngineSwitcher a IJsEngineSwitcher .
  2. En lugar de la propiedad Instance , use la propiedad Current todas partes.

También vale la pena señalar que para la mayoría de los desarrolladores, estos cambios no serán de beneficio particular, ya que su objetivo principal era simplificar las pruebas unitarias (por ejemplo, anteriormente, en las pruebas unitarias de la biblioteca ReactJS.NET, se usaron bloqueos, pero ahora puede prescindir de ellos ).


Reorganización de excepciones


Antes de la tercera versión, la mayoría de los errores del motor JS se convirtieron en JsRuntimeException tipo JsRuntimeException , y solo los errores que ocurrieron durante el proceso de inicialización del motor se convirtieron en JsEngineLoadException . También hubo una clase base JsException , de la que se heredaron dos de los tipos de excepciones anteriores, que hicieron posible interceptar absolutamente todos los errores que ocurrieron durante el funcionamiento de los motores JS. A pesar de los inconvenientes obvios, esta organización de excepciones encaja bien con el concepto de una interfaz unificada para acceder a las capacidades básicas de los motores JS.


Pero con la implementación de la interrupción y la precompilación de los scripts (los analizaré en las siguientes secciones), surgió la necesidad de un nuevo enfoque para organizar excepciones. Lo primero que debía hacer era agregar un nuevo tipo de excepción: JsInterruptedException , que era necesario para notificar al usuario sobre la interrupción de la ejecución del script. Luego fue necesario dividir explícitamente todos los errores que ocurrieron durante el procesamiento del script en dos grupos: errores de compilación (análisis) y errores de tiempo de ejecución. También se requería separar todo tipo de errores específicos de Chakra y V8, que no estaban relacionados con el procesamiento de scripts. También era necesario tener en cuenta la presencia de una excepción en el motor Jint que ocurre cuando ha transcurrido el tiempo de espera para ejecutar un script (tiempo de espera). Como resultado, se formó un nuevo enfoque para la organización de excepciones, que se puede representar como la siguiente estructura jerárquica:


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

* - esta excepción no ocurre en el nivel del motor JS, sino en el nivel de JavaScript Engine Switcher.


Creo que la jerarquía de excepciones presentada anteriormente no necesita comentarios, porque los nombres de las excepciones hablan por sí mismos. Con este enfoque, obtenemos no solo más información sobre las causas del error, sino que también podemos manejar de manera más flexible ciertos tipos de excepciones.


Formato de mensaje de error unificado


Otro problema con las versiones anteriores de JavaScript Engine Switcher fue la dificultad para localizar errores que ocurrieron al procesar secuencias de comandos. Desde la propiedad de excepción Message , fue difícil entender exactamente dónde ocurrió el error en el código, por lo que tuve que analizar otras propiedades de excepción, lo que no siempre fue conveniente. Además, el conjunto de propiedades existentes tampoco era suficiente.


Por lo tanto, se agregaron 2 nuevas propiedades a la clase JsScriptException :


  1. Tipo : tipo de error de JavaScript (por ejemplo, SyntaxError o TypeError );
  2. DocumentName : el nombre del documento (generalmente extraído de los valores de los siguientes parámetros: documentName de los métodos Execute y Evaluate , path método ExecuteFile , resourceName método ExecuteResource , etc.);

También se ha agregado una nueva propiedad a la clase JsRuntimeException : CallStack , que contiene una representación de cadena de la pila de llamadas.


Anteriormente, un valor de una propiedad similar de la excepción .NET original o una representación de cadena de un error de JavaScript simplemente se copiaba en la propiedad Message . A menudo, los mensajes de error en diferentes motores JS diferían no solo en el formato, sino también en la cantidad de información útil presentada en ellos. Por ejemplo, debido a la falta de información del número de fila y columna en algunos mensajes de error, los desarrolladores de la biblioteca ReactJS.NET se vieron obligados a volver a lanzar las excepciones recibidas del JavaScript Engine Switcher.


Por lo tanto, decidí generar mis propios mensajes de error en el nivel del módulo adaptador, que tendría un formato único (unificado). Este formato utiliza toda la información de error disponible: tipo, descripción, nombre del documento, número de línea, número de columna, fragmento de código y pila de llamadas. Como base para el nuevo formato, tomé el formato de error de la biblioteca de Microsoft ClearScript .


A continuación se muestran los mensajes sobre el mismo error de compilación que fueron generados por diferentes módulos adaptadores:


 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 ejemplo similar para un error de tiempo de ejecución:


 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 

Como puede ver en los ejemplos, algunos motores JS nos dan descripciones de error y números de columna completamente diferentes, y no siempre podemos obtener un conjunto completo de datos de error, pero a pesar de estas deficiencias, el formato unificado nos brinda más información sobre la ubicación del error que mensajes de error originales


Consejos de implementación de ensamblado nativo


La principal causa de errores al trabajar con la segunda versión de JavaScript Engine Switcher fue que muchos desarrolladores olvidaron instalar paquetes NuGet que contienen ensamblajes nativos para módulos ChakraCore y V8. En un momento, una publicación en el rastreador de errores ReactJS.NET también se dedicó a este problema ( la traducción al ruso también está disponible). Ahora, este error se encuentra principalmente solo por principiantes que, por alguna razón, no leyeron la documentación.


Los autores de ReactJS.NET intentaron minimizar el número de tales errores utilizando las sugerencias dentro de los mensajes de error, pero la implementación poco exitosa de este enfoque generó aún más confusión . La idea de las sugerencias era buena, pero requería una implementación fundamentalmente diferente, a saber, implementación a nivel de módulos adaptadores de motores JS. En la nueva versión de JavaScript Engine Switcher, tales sugerencias se agregan al mensaje de error cuando se envuelven las excepciones DllNotFoundException y TypeLoadException JsEngineLoadException (ver ejemplos de implementación para los módulos ChakraCore , V8 y Vroom ). Además, estos consejos son inteligentes, porque su generación tiene en cuenta una serie de factores: tipo de sistema operativo, arquitectura del procesador y tiempo de ejecución (.NET Framework, .NET Core o Mono).


Por ejemplo, cuando se utiliza el módulo ChakraCore sin ensamblaje nativo en un proceso de 64 bits en el sistema operativo Windows, el mensaje de error se verá así:


Error al crear la instancia de ChakraCoreJsEngine. Lo más probable es que haya sucedido porque no se encontró el ensamblado 'ChakraCore.dll' o una de sus dependencias. Intente instalar el paquete JavaScriptEngineSwitcher.ChakraCore.Native.win-x64 a través de NuGet. Además, aún necesita instalar Microsoft Visual C ++ Redistributable para Visual Studio 2017 ( https://www.visualstudio.com/downloads/#microsoft-visual-c-redistributable-for-visual-studio-2017 ).

El mensaje de error da una pista de que necesita instalar el paquete NuGet JavaScriptEngineSwitcher.ChakraCore.Native.win-x64, y también menciona que ChakraCore para Windows requiere el componente redistribuible de Microsoft Visual C ++ para que Visual Studio 2017 funcione. Si ocurre en un proceso de 32 bits, se le pedirá al usuario que instale el paquete JavaScriptEngineSwitcher.ChakraCore.Native.win-x86.


Un mensaje de error similar en Linux en .NET Core se vería así:


Error al crear la instancia de ChakraCoreJsEngine. Lo más probable es que sucedió, porque no se encontró el ensamblado 'libChakraCore.so' o una de sus dependencias. Intente instalar el paquete JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64 a través de NuGet.

En este caso, se propondrá instalar el paquete JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64.


Cuando se inicia en Mono, se mostrará otro mensaje:


... Los paquetes JavaScriptEngineSwitcher.ChakraCore.Native.linux- * no admiten la instalación en Mono, pero puede instalar el ensamblaje nativo manualmente ( https://github.com/Taritsyn/JavaScriptEngineSwitcher/wiki/ChakraCore#linux ).

Dado que el paquete JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64 es compatible solo con .NET Core, la información sobre herramientas proporcionará un enlace a las instrucciones para implementar manualmente el ensamblado nativo en Linux.


Se pueden dar muchos más ejemplos, pero esto no tiene sentido.


Anulación de ejecución de script


Cuando damos a los usuarios la oportunidad de ejecutar código JS arbitrario en el servidor, nos enfrentamos a un problema muy grave: no sabemos cuánto tiempo llevará ejecutar este código. Esto puede ser una gran cantidad de código no óptimo o código que ejecuta un bucle infinito. En cualquier caso, será un código no controlado que consumirá los recursos de nuestro servidor indefinidamente. Para controlar de alguna manera este proceso, necesitamos la capacidad de interrumpir la ejecución de los scripts. Al usar motores escritos en .NET puro (por ejemplo, Jint, Jurassic o NiL.JS), siempre podemos comenzar a ejecutar código JS como una tarea con la capacidad de cancelar, pero este enfoque no funcionará para otros motores. Afortunadamente para nosotros, los motores C ++ tienen mecanismos incorporados para interrumpir los scripts.


Para proporcionar acceso a estos mecanismos, la propiedad SupportsScriptInterruption y el método Interrupt se agregaron a la interfaz IJsEngine . Dado que no todos los motores admiten esta función, siempre debe verificar el valor de la propiedad SupportsScriptInterruption antes de llamar al método Interrupt (si en versiones anteriores de JavaScript Engine Switcher tuvo que ejecutar manualmente el recolector de basura, comprenderá de inmediato de qué estoy hablando):


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

Y necesita llamar a este método en un hilo separado diferente del hilo en el que se ejecutan los scripts. Después de llamar al método Interrupt , todos los CallFunction Evaluate , Execute* y CallFunction se CallFunction terminarán con una JsInterruptedException .


Dado que esta API es de bajo nivel, se recomienda utilizar métodos de extensión como los siguientes para las tareas descritas al principio de la sección:


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

Este método es un complemento del método del motor Evaluate<T> , que permite utilizar el parámetro timeoutInterval establecer el tiempo de espera para que se ejecute el script. El principio de funcionamiento de este método de extensión es muy simple. Primero, verificamos si nuestro motor admite el mecanismo de interrupción incorporado. Si lo timeoutInterval , creamos una instancia de la clase Timer , que, después del intervalo de timeoutInterval especificado en el parámetro de timeoutInterval , inicia el método de Interrupt , luego llamamos al método del motor Evaluate<T> y, en caso de error, captura la JsInterruptedException y la envuelve en una JsTimeoutException . Si el motor no admite interrupciones, cree una instancia de la clase Task que ejecute el método Evaluate<T> del motor, luego establezca el intervalo de espera para que la tarea se ejecute igual al valor del parámetro timeoutInterval , y si la tarea se completa por tiempo de espera, arroje una JsTimeoutException tipo JsTimeoutException . El siguiente es un ejemplo del uso de este método de extensión:


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

Usando el método de extensión, calculamos el resultado de la expresión JS. El resultado de la expresión es un entero aleatorio, y este número también es el número de segundos por los cuales esta expresión se retrasa. Además, al llamar al método de extensión, especificamos un intervalo de espera de 3 segundos. Dado que el tiempo para calcular la expresión varía de 1 a 10 segundos, en un caso, el resultado de la expresión se mostrará en la consola y, en otro, un mensaje sobre el tiempo de espera.


No debería ser difícil para usted rehacer este método de extensión para llamar a otros métodos del motor (por ejemplo, Execute* , CallFunction u otras versiones sobrecargadas del método Evaluate ). Hasta ahora no he decidido si agregaré tales métodos de extensión a la biblioteca en sí, porque todavía no está claro cuánto tendrán demanda.


Precompilación de guiones


Me pidieron que agregara soporte para la compilación preliminar de scripts en 2015 . En ese momento, no estaba claro cómo esa función podría encajar en el concepto de una interfaz unificada. Pero a medida que pasaba el tiempo, el soporte para las funciones de accesibilidad, como la recolección de basura y la interrupción de scripts, comenzó a aparecer gradualmente en JavaScript Engine Switcher. En las últimas etapas de trabajo en la tercera versión, llegó el momento de la compilación preliminar.


Con la compilación previa, puede compilar un script una vez y luego usarlo muchas veces para inicializar los motores JS. Debido al hecho de que el script precompilado no requiere análisis, la inicialización de los motores será más rápida.


En la actualidad, la precompilación es compatible con 5 módulos adaptadores: ChakraCore, Jint, Jurassic, MSIE (solo en modos JsRT) y V8. Para proporcionar acceso a los mecanismos del motor correspondientes, la propiedad SupportsScriptPrecompilation y 3 nuevos métodos se agregaron a la interfaz Precompile : Precompile , PrecompileFile y PrecompileResource . Precompile* métodos de Precompile* devuelven una instancia de un objeto que implementa la interfaz IPrecompiledScript . Este objeto es un script precompilado que puede ser utilizado por diferentes instancias de los motores (una versión sobrecargada del método Execute sirve para este propósito). Considere un ejemplo simple de uso de esta 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 . ..
ChakraCoreNo41,72---74,46
Si35,07---91,79
JintNo27,192 812,501 343,75-16 374,58
Si15,541 296,88640,6331,257 521,49
JurassicNo455,702 000,001 000,00-15 575,28
Si78,701 000,00--7 892,94
MSIE Chakra IE JsRTNo30,97---77,75
Si24,40---90,58
MSIE Chakra Edge JsRTNo33,14---78,40
Si32,86---95,48
V8No41,10---79,33
Si39,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 . ..
ChakraCoreNo43,65---18,07
Si36,37---16,59
JintNo24,872 750,001 375,00-16 300,25
Si15,251 281,25593,7562,507 447,44
JurassicNo469,972 000,001 000,00-15 511,70
Si80,721 000,00--7 845,98
MSIE Chakra IE JsRTNo31,50---20,28
Si24,52---18,78
MSIE Chakra Edge JsRTNo35,54---20,45
Si31,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.


Referencias


  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/439446/


All Articles