diff --git a/src/modules/Elsa.JavaScript/Features/JavaScriptFeature.cs b/src/modules/Elsa.JavaScript/Features/JavaScriptFeature.cs index 7700d3708..3bc8b2945 100644 --- a/src/modules/Elsa.JavaScript/Features/JavaScriptFeature.cs +++ b/src/modules/Elsa.JavaScript/Features/JavaScriptFeature.cs @@ -1,3 +1,4 @@ +using Elsa.Caching.Features; using Elsa.Common.Features; using Elsa.Expressions.Features; using Elsa.Extensions; @@ -24,6 +25,7 @@ namespace Elsa.JavaScript.Features; /// [DependsOn(typeof(MediatorFeature))] [DependsOn(typeof(ExpressionsFeature))] +[DependsOn(typeof(MemoryCacheFeature))] public class JavaScriptFeature : FeatureBase { /// diff --git a/src/modules/Elsa.JavaScript/Options/JintOptions.cs b/src/modules/Elsa.JavaScript/Options/JintOptions.cs index c2aa19c5f..f543c60a6 100644 --- a/src/modules/Elsa.JavaScript/Options/JintOptions.cs +++ b/src/modules/Elsa.JavaScript/Options/JintOptions.cs @@ -22,6 +22,15 @@ public class JintOptions /// public bool AllowConfigurationAccess { get; set; } + /// + /// The timeout for script caching. + /// + /// + /// The ScriptCacheTimeout property specifies the duration for which the scripts are cached in the Jint JavaScript engine. When a script is executed, it is compiled and cached for future use. This caching improves performance by avoiding repetitive compilation of the same script. + /// If the value of ScriptCacheTimeout is null, the scripts are cached indefinitely. If a time value is specified, the scripts will be recompiled after the specified duration has elapsed. + /// + public TimeSpan? ScriptCacheTimeout { get; set; } = TimeSpan.FromDays(1); + /// /// A list of callbacks that are invoked when the Jint engine is created. Use this to configure the engine. /// diff --git a/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs b/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs index 16163f7b3..a6dff6543 100644 --- a/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs +++ b/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs @@ -12,14 +12,16 @@ using Elsa.JavaScript.Helpers; using Elsa.JavaScript.Notifications; using Elsa.JavaScript.Options; using Elsa.Mediator.Contracts; +using Esprima; +using Esprima.Ast; using Humanizer; using Jint; using Jint.Runtime.Interop; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; // ReSharper disable ConvertClosureToMethodGroup - namespace Elsa.JavaScript.Services; /// @@ -28,15 +30,17 @@ namespace Elsa.JavaScript.Services; public class JintJavaScriptEvaluator : IJavaScriptEvaluator { private readonly INotificationSender _mediator; + private readonly IMemoryCache _memoryCache; private readonly JintOptions _jintOptions; private readonly IConfiguration _configuration; /// /// Constructor. /// - public JintJavaScriptEvaluator(IConfiguration configuration, INotificationSender mediator, IOptions scriptOptions) + public JintJavaScriptEvaluator(IConfiguration configuration, INotificationSender mediator, IOptions scriptOptions, IMemoryCache memoryCache) { _mediator = mediator; + _memoryCache = memoryCache; _jintOptions = scriptOptions.Value; _configuration = configuration; } @@ -176,9 +180,21 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator engine.SetValue($"get{outputName}From{activityOutput.ActivityName}", (Func)(() => context.GetOutput(activityOutput.ActivityId, outputName))); } - private static object? ExecuteExpressionAndGetResult(Engine engine, string expression) + private object? ExecuteExpressionAndGetResult(Engine engine, string expression) { - var result = engine.Evaluate(expression); + var cacheKey = "jint:script:" + expression.GetHashCode(StringComparison.Ordinal); + + var parsedScript = _memoryCache.GetOrCreate(cacheKey, entry => + { + if (_jintOptions.ScriptCacheTimeout.HasValue) + entry.SetAbsoluteExpiration(_jintOptions.ScriptCacheTimeout.Value); + + var parser = new JavaScriptParser(new ParserOptions { AllowReturnOutsideFunction = true }); + var script = parser.ParseScript(expression); + return script; + })!; + + var result = engine.Evaluate(parsedScript); return result.ToObject(); }