Cache JavaScript Expressions (#5273)

* Add caching for scripts in Jint JavaScript engine

A new feature has been implemented to cache the scripts in the Jint JavaScript engine for performance improvement. A new property, "ScriptCacheTimeout", has been introduced to determine the script caching duration. The scripts will be recompiled after the specified duration has elapsed. If the value is null, the scripts will be cached indefinitely. Also, dependencies and changes have been made in the relevant classes to support this feature.

* Set default value for ScriptCacheTimeout in JintOptions

The change sets a default value for the ScriptCacheTimeout property in the JintOptions.cs file. Previously, ScriptCacheTimeout did not have a default value. Now, ScriptCacheTimeout defaults to one day if no other value is specified.

* Refactor JsonSerializerOptions creation in JintJavaScriptEvaluator

This change breaks down the creation of JsonSerializerOptions into several lines to improve readability. It also keeps JavaScriptEncoder as the encoder but presents the creation of the object in a more explicit manner.
This commit is contained in:
Sipke Schoorstra 2024-04-25 10:26:35 +02:00 committed by GitHub
parent 85ba3fc646
commit f663ad3813
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 31 additions and 4 deletions

View file

@ -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;
/// </summary>
[DependsOn(typeof(MediatorFeature))]
[DependsOn(typeof(ExpressionsFeature))]
[DependsOn(typeof(MemoryCacheFeature))]
public class JavaScriptFeature : FeatureBase
{
/// <inheritdoc />

View file

@ -22,6 +22,15 @@ public class JintOptions
/// </summary>
public bool AllowConfigurationAccess { get; set; }
/// <summary>
/// The timeout for script caching.
/// </summary>
/// <remarks>
/// The <c>ScriptCacheTimeout</c> 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 <c>ScriptCacheTimeout</c> is <c>null</c>, the scripts are cached indefinitely. If a time value is specified, the scripts will be recompiled after the specified duration has elapsed.
/// </remarks>
public TimeSpan? ScriptCacheTimeout { get; set; } = TimeSpan.FromDays(1);
/// <summary>
/// A list of callbacks that are invoked when the Jint engine is created. Use this to configure the engine.
/// </summary>

View file

@ -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;
/// <summary>
@ -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;
/// <summary>
/// Constructor.
/// </summary>
public JintJavaScriptEvaluator(IConfiguration configuration, INotificationSender mediator, IOptions<JintOptions> scriptOptions)
public JintJavaScriptEvaluator(IConfiguration configuration, INotificationSender mediator, IOptions<JintOptions> 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<object?>)(() => 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();
}