/// <summary> /// Runs the JavaScript at the given path. /// </summary> /// <param name="sourcePath">The source path.</param> /// <param name="sourceUrl">The source URL.</param> public void RunScript(string sourcePath, string sourceUrl) { if (sourcePath == null) { throw new ArgumentNullException(nameof(sourcePath)); } if (sourceUrl == null) { throw new ArgumentNullException(nameof(sourceUrl)); } try { var binPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, BytecodeFileName); if (_useSerialization) { var srcFileInfo = new FileInfo(sourcePath); var binFileInfo = new FileInfo(binPath); // The idea is to run the JS bundle and generate bytecode for it on a background thread. // This eliminates the need to delay the first start when the app doesn't have bytecode. // Next time the app starts, it checks if bytecode is still good and runs it directly. if (binFileInfo.Exists && binFileInfo.LastWriteTime > srcFileInfo.LastWriteTime) { Native.ThrowIfError((JavaScriptErrorCode)_executor.RunSerializedScript(sourcePath, binPath, sourceUrl)); } else { Task.Run(() => { try { // In Chakra JS engine, only one runtime can be active on a particular thread at a time, // and a runtime can only be active on one thread at a time. However it's possible to // create two runtimes and let them run on different threads. var rt = new ChakraBridge.NativeJavaScriptExecutor(); Native.ThrowIfError((JavaScriptErrorCode)rt.InitializeHost()); Native.ThrowIfError((JavaScriptErrorCode)rt.SerializeScript(sourcePath, binPath)); Native.ThrowIfError((JavaScriptErrorCode)rt.DisposeHost()); } catch (Exception) { // It's fine if the bytecode couldn't be generated: RN can still use the JS bundle. } }); Native.ThrowIfError((JavaScriptErrorCode)_executor.RunScript(sourcePath, sourceUrl)); } } else { Native.ThrowIfError((JavaScriptErrorCode)_executor.RunScript(sourcePath, sourceUrl)); } } catch (JavaScriptScriptException ex) { var jsonError = JavaScriptValueToJTokenConverter.Convert(ex.Error); var message = jsonError.Value <string>("message"); var stackTrace = jsonError.Value <string>("stack"); throw new Modules.Core.JavaScriptException(message ?? ex.Message, stackTrace, ex); } }
/// <summary> /// Instantiates the <see cref="NativeJavaScriptExecutor"/>. /// </summary> /// <param name="useSerialization">true to use serialization, else false.</param> public NativeJavaScriptExecutor(bool useSerialization) { _executor = new ChakraBridge.NativeJavaScriptExecutor(); Native.ThrowIfError((JavaScriptErrorCode)_executor.InitializeHost()); _useSerialization = useSerialization; }
/// <summary> /// Runs the JavaScript at the given path. /// </summary> /// <param name="sourcePath">The source path.</param> /// <param name="sourceUrl">The source URL.</param> public void RunScript(string sourcePath, string sourceUrl) { if (sourcePath == null) { throw new ArgumentNullException(nameof(sourcePath)); } if (sourceUrl == null) { throw new ArgumentNullException(nameof(sourceUrl)); } try { var binPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, BytecodeFileName); if (_useSerialization) { var srcFileInfo = new FileInfo(sourcePath); var binFileInfo = new FileInfo(binPath); bool ranSuccessfully = false; // The idea is to run the JS bundle and generate bytecode for it on a background thread. // This eliminates the need to delay the first start when the app doesn't have bytecode. // Next time the app starts, it checks if bytecode is still good and runs it directly. if (binFileInfo.Exists && binFileInfo.LastWriteTime > srcFileInfo.LastWriteTime) { try { Native.ThrowIfError((JavaScriptErrorCode)_executor.RunSerializedScript(sourcePath, binPath, sourceUrl)); ranSuccessfully = true; } catch (JavaScriptUsageException exc) { if (exc.ErrorCode == JavaScriptErrorCode.BadSerializedScript) { // Bytecode format is dependent on Chakra engine version, so an OS upgrade may require a recompilation Debug.WriteLine("Serialized bytecode script is corrupted or wrong format, will generate new one"); } else { // Some more severe error. We still have a chance (recompiling), so we keep continuing. Debug.WriteLine($"Failed to run serialized bytecode script ({exc.ToString()}), will generate new one"); } File.Delete(binPath); } } else { Debug.WriteLine("Serialized bytecode script doesn't exist or is obsolete, will generate one"); } if (!ranSuccessfully) { Task.Run(() => { try { // In Chakra JS engine, only one runtime can be active on a particular thread at a time, // and a runtime can only be active on one thread at a time. However it's possible to // create two runtimes and let them run on different threads. var rt = new ChakraBridge.NativeJavaScriptExecutor(); Native.ThrowIfError((JavaScriptErrorCode)rt.InitializeHost()); Native.ThrowIfError((JavaScriptErrorCode)rt.SerializeScript(sourcePath, binPath)); Native.ThrowIfError((JavaScriptErrorCode)rt.DisposeHost()); } catch (Exception ex) { Debug.WriteLine($"Failed to generate serialized bytecode script ({ex.ToString()})."); // It's fine if the bytecode couldn't be generated: RN can still use the JS bundle. } }); Native.ThrowIfError((JavaScriptErrorCode)_executor.RunScript(sourcePath, sourceUrl)); } } else { Native.ThrowIfError((JavaScriptErrorCode)_executor.RunScript(sourcePath, sourceUrl)); } } catch (JavaScriptScriptException ex) { var jsonError = JavaScriptValueToJTokenConverter.Convert(ex.Error); var message = jsonError.Value <string>("message"); var stackTrace = jsonError.Value <string>("stack"); throw new Modules.Core.JavaScriptException(message ?? ex.Message, stackTrace, ex); } }