Skip to content

Commit

Permalink
Add ability to pre-compile scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
Taritsyn committed Aug 29, 2018
1 parent 26ff2dd commit 4ff0fad
Show file tree
Hide file tree
Showing 24 changed files with 606 additions and 98 deletions.
12 changes: 12 additions & 0 deletions src/React.Core/IReactSiteConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@ public interface IReactSiteConfiguration
/// </summary>
IReactSiteConfiguration SetMaxUsagesPerEngine(int? maxUsagesPerEngine);

/// <summary>
/// Gets or sets whether to allow the JavaScript pre-compilation (accelerates the
/// initialization of JavaScript engines).
/// </summary>
bool AllowJavaScriptPrecompilation { get; set; }
/// <summary>
/// Sets whether to allow the JavaScript pre-compilation (accelerates the initialization of
/// JavaScript engines).
/// </summary>
/// <returns></returns>
IReactSiteConfiguration SetAllowJavaScriptPrecompilation(bool allowJavaScriptPrecompilation);

/// <summary>
/// Gets or sets whether the built-in version of React is loaded. If <c>false</c>, you must
/// provide your own version of React.
Expand Down
43 changes: 38 additions & 5 deletions src/React.Core/JavaScriptEngineFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public class JavaScriptEngineFactory : IDisposable, IJavaScriptEngineFactory
/// </summary>
protected readonly IReactSiteConfiguration _config;
/// <summary>
/// Cache used for storing the pre-compiled scripts
/// </summary>
protected readonly ICache _cache;
/// <summary>
/// File system wrapper
/// </summary>
protected readonly IFileSystem _fileSystem;
Expand Down Expand Up @@ -57,11 +61,13 @@ public class JavaScriptEngineFactory : IDisposable, IJavaScriptEngineFactory
public JavaScriptEngineFactory(
IJsEngineSwitcher jsEngineSwitcher,
IReactSiteConfiguration config,
ICache cache,
IFileSystem fileSystem
)
{
_jsEngineSwitcher = jsEngineSwitcher;
_config = config;
_cache = cache;
_fileSystem = fileSystem;
#pragma warning disable 618
_factory = GetFactory(_jsEngineSwitcher);
Expand Down Expand Up @@ -118,10 +124,11 @@ protected virtual void InitialiseEngine(IJsEngine engine)
#else
var thisAssembly = typeof(ReactEnvironment).GetTypeInfo().Assembly;
#endif
engine.ExecuteResource("React.Core.Resources.shims.js", thisAssembly);
LoadResource(engine, "React.Core.Resources.shims.js", thisAssembly);
if (_config.LoadReact)
{
engine.ExecuteResource(
LoadResource(
engine,
_config.UseDebugReact
? "React.Core.Resources.react.generated.js"
: "React.Core.Resources.react.generated.min.js",
Expand All @@ -138,6 +145,25 @@ protected virtual void InitialiseEngine(IJsEngine engine)
}
}

/// <summary>
/// Loads a code from embedded JavaScript resource into the engine.
/// </summary>
/// <param name="engine">Engine to load a code from embedded JavaScript resource</param>
/// <param name="resourceName">The case-sensitive resource name</param>
/// <param name="assembly">The assembly, which contains the embedded resource</param>
private void LoadResource(IJsEngine engine, string resourceName, Assembly assembly)
{
if (_config.AllowJavaScriptPrecompilation
&& engine.TryExecuteResourceWithPrecompilation(_cache, resourceName, assembly))
{
// Do nothing.
}
else
{
engine.ExecuteResource(resourceName, assembly);
}
}

/// <summary>
/// Loads any user-provided scripts. Only scripts that don't need JSX transformation can
/// run immediately here. JSX files are loaded in ReactEnvironment.
Expand All @@ -149,10 +175,17 @@ private void LoadUserScripts(IJsEngine engine)
{
try
{
var contents = _fileSystem.ReadAsString(file);
engine.Execute(contents);
if (_config.AllowJavaScriptPrecompilation
&& engine.TryExecuteFileWithPrecompilation(_cache, _fileSystem, file))
{
// Do nothing.
}
else
{
engine.ExecuteFile(_fileSystem, file);
}
}
catch (JsRuntimeException ex)
catch (JsScriptException ex)
{
// We can't simply rethrow the exception here, as it's possible this is running
// on a background thread (ie. as a response to a file changing). If we did
Expand Down
161 changes: 161 additions & 0 deletions src/React.Core/JavaScriptEngineUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
*/

using System;
using System.Diagnostics;
using System.Reflection;
using JavaScriptEngineSwitcher.Core;
using JavaScriptEngineSwitcher.Core.Helpers;
using Newtonsoft.Json;
Expand All @@ -20,6 +22,20 @@ namespace React
/// </summary>
public static class JavaScriptEngineUtils
{
/// <summary>
/// Cache key for the script resource precompilation
/// </summary>
private const string PRECOMPILED_JS_RESOURCE_CACHE_KEY = "PRECOMPILED_JS_RESOURCE_{0}";
/// <summary>
/// Cache key for the script file precompilation
/// </summary>
private const string PRECOMPILED_JS_FILE_CACHE_KEY = "PRECOMPILED_JS_FILE_{0}";
/// <summary>
/// Value that indicates whether a cache entry, that contains a precompiled script, should be
/// evicted if it has not been accessed in a given span of time
/// </summary>
private readonly static TimeSpan PRECOMPILED_JS_CACHE_ENTRY_SLIDING_EXPIRATION = TimeSpan.FromMinutes(30);

/// <summary>
/// Determines if the current environment supports the ClearScript V8 engine
/// </summary>
Expand Down Expand Up @@ -61,6 +77,151 @@ public static bool EnvironmentSupportsClearScript()
}
}

/// <summary>
/// Executes a code from JavaScript file.
/// </summary>
/// <param name="engine">Engine to execute code from JavaScript file</param>
/// <param name="fileSystem">File system wrapper</param>
/// <param name="path">Path to the JavaScript file</param>
public static void ExecuteFile(this IJsEngine engine, IFileSystem fileSystem, string path)
{
var contents = fileSystem.ReadAsString(path);
engine.Execute(contents, path);
}

/// <summary>
/// Tries to execute a code with pre-compilation.
/// </summary>
/// <param name="engine">Engine to execute code with pre-compilation</param>
/// <param name="cache">Cache used for storing the pre-compiled scripts</param>
/// <param name="fileSystem">File system wrapper</param>
/// <param name="code">JavaScript code</param>
/// <param name="path">Path to the file on which the executable code depends (required
/// for cache management).</param>
/// <returns>true if can perform a script pre-compilation; otherwise, false.</returns>
public static bool TryExecuteWithPrecompilation(this IJsEngine engine, ICache cache, IFileSystem fileSystem,
string code, string path)
{
if (!CheckPrecompilationAvailability(engine, cache))
{
return false;
}

var cacheKey = string.Format(PRECOMPILED_JS_FILE_CACHE_KEY, path);
var precompiledScript = cache.Get<IPrecompiledScript>(cacheKey);

if (precompiledScript == null)
{
precompiledScript = engine.Precompile(code, path);
var fullPath = fileSystem.MapPath(path);
cache.Set(
cacheKey,
precompiledScript,
slidingExpiration: PRECOMPILED_JS_CACHE_ENTRY_SLIDING_EXPIRATION,
cacheDependencyFiles: new[] { fullPath }
);
}

engine.Execute(precompiledScript);

return true;
}

/// <summary>
/// Tries to execute a code from JavaScript file with pre-compilation.
/// </summary>
/// <param name="engine">Engine to execute code from JavaScript file with pre-compilation</param>
/// <param name="cache">Cache used for storing the pre-compiled scripts</param>
/// <param name="fileSystem">File system wrapper</param>
/// <param name="path">Path to the JavaScript file</param>
/// <returns>true if can perform a script pre-compilation; otherwise, false.</returns>
public static bool TryExecuteFileWithPrecompilation(this IJsEngine engine, ICache cache,
IFileSystem fileSystem, string path)
{
if (!CheckPrecompilationAvailability(engine, cache))
{
return false;
}

var cacheKey = string.Format(PRECOMPILED_JS_FILE_CACHE_KEY, path);
var precompiledScript = cache.Get<IPrecompiledScript>(cacheKey);

if (precompiledScript == null)
{
var contents = fileSystem.ReadAsString(path);
precompiledScript = engine.Precompile(contents, path);
var fullPath = fileSystem.MapPath(path);
cache.Set(
cacheKey,
precompiledScript,
slidingExpiration: PRECOMPILED_JS_CACHE_ENTRY_SLIDING_EXPIRATION,
cacheDependencyFiles: new[] { fullPath }
);
}

engine.Execute(precompiledScript);

return true;
}

/// <summary>
/// Tries to execute a code from embedded JavaScript resource with pre-compilation.
/// </summary>
/// <param name="engine">Engine to execute a code from embedded JavaScript resource with pre-compilation</param>
/// <param name="cache">Cache used for storing the pre-compiled scripts</param>
/// <param name="resourceName">The case-sensitive resource name</param>
/// <param name="assembly">The assembly, which contains the embedded resource</param>
/// <returns>true if can perform a script pre-compilation; otherwise, false.</returns>
public static bool TryExecuteResourceWithPrecompilation(this IJsEngine engine, ICache cache,
string resourceName, Assembly assembly)
{
if (!CheckPrecompilationAvailability(engine, cache))
{
return false;
}

var cacheKey = string.Format(PRECOMPILED_JS_RESOURCE_CACHE_KEY, resourceName);
var precompiledScript = cache.Get<IPrecompiledScript>(cacheKey);

if (precompiledScript == null)
{
precompiledScript = engine.PrecompileResource(resourceName, assembly);
cache.Set(
cacheKey,
precompiledScript,
slidingExpiration: PRECOMPILED_JS_CACHE_ENTRY_SLIDING_EXPIRATION
);
}

engine.Execute(precompiledScript);

return true;
}

/// <summary>
/// Checks a availability of the script pre-compilation
/// </summary>
/// <param name="engine">Instance of the JavaScript engine</param>
/// <param name="cache">Cache used for storing the pre-compiled scripts</param>
/// <returns>true if the script pre-compilation is available; otherwise, false.</returns>
private static bool CheckPrecompilationAvailability(IJsEngine engine, ICache cache)
{
if (!engine.SupportsScriptPrecompilation)
{
Trace.WriteLine(string.Format("The {0} version {1} does not support the script pre-compilation.",
engine.Name, engine.Version));
return false;
}

if (cache is NullCache)
{
Trace.WriteLine("Usage of script pre-compilation without caching does not make sense.");
return false;
}

return true;
}

/// <summary>
/// Calls a JavaScript function using the specified engine. If <typeparamref name="T"/> is
/// not a scalar type, the function is assumed to return a string of JSON that can be
Expand Down
2 changes: 1 addition & 1 deletion src/React.Core/React.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="JavaScriptEngineSwitcher.Core" Version="3.0.0-beta4" />
<PackageReference Include="JavaScriptEngineSwitcher.Core" Version="3.0.0-beta9" />
<PackageReference Include="JSPool" Version="4.0.0-beta1" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
</ItemGroup>
Expand Down
24 changes: 21 additions & 3 deletions src/React.Core/ReactEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,17 @@ protected virtual void EnsureUserScriptsLoaded()
var contents = Babel.TransformFile(file);
try
{
Execute(contents);
if (_config.AllowJavaScriptPrecompilation
&& Engine.TryExecuteWithPrecompilation(_cache, _fileSystem, contents, file))
{
// Do nothing.
}
else
{
Engine.Execute(contents, file);
}
}
catch (JsRuntimeException ex)
catch (JsScriptException ex)
{
throw new ReactScriptLoadException(string.Format(
"Error while loading \"{0}\": {1}",
Expand Down Expand Up @@ -474,7 +482,17 @@ private void EnsureBabelLoaded(IJsEngine engine)
#else
var assembly = typeof(ReactEnvironment).GetTypeInfo().Assembly;
#endif
engine.ExecuteResource("React.Core.Resources.babel.generated.min.js", assembly);
const string resourceName = "React.Core.Resources.babel.generated.min.js";

if (_config.AllowJavaScriptPrecompilation
&& engine.TryExecuteResourceWithPrecompilation(_cache, resourceName, assembly))
{
// Do nothing.
}
else
{
engine.ExecuteResource(resourceName, assembly);
}
}
}
}
Expand Down
14 changes: 8 additions & 6 deletions src/React.Core/ReactSiteConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public ReactSiteConfiguration()
{
BabelConfig = new BabelConfig();
ReuseJavaScriptEngines = true;
AllowMsieEngine = true;
AllowJavaScriptPrecompilation = false;
LoadBabel = true;
LoadReact = true;
JsonSerializerSettings = new JsonSerializerSettings
Expand Down Expand Up @@ -216,17 +216,19 @@ public IReactSiteConfiguration SetMaxUsagesPerEngine(int? maxUsagesPerEngine)
}

/// <summary>
/// Gets or sets whether the MSIE engine should be used if V8 is unavailable.
/// Gets or sets whether to allow the JavaScript pre-compilation (accelerates the
/// initialization of JavaScript engines).
/// </summary>
public bool AllowMsieEngine { get; set; }
public bool AllowJavaScriptPrecompilation { get; set; }

/// <summary>
/// Sets whether the MSIE engine should be used if V8 is unavailable.
/// Sets whether to allow the JavaScript pre-compilation (accelerates the initialization of
/// JavaScript engines).
/// </summary>
/// <returns></returns>
public IReactSiteConfiguration SetAllowMsieEngine(bool allowMsieEngine)
public IReactSiteConfiguration SetAllowJavaScriptPrecompilation(bool allowJavaScriptPrecompilation)
{
AllowMsieEngine = allowMsieEngine;
AllowJavaScriptPrecompilation = allowJavaScriptPrecompilation;
return this;
}

Expand Down
4 changes: 2 additions & 2 deletions src/React.Sample.Cassette/React.Sample.Cassette.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,14 @@
<Private>True</Private>
</Reference>
<Reference Include="JavaScriptEngineSwitcher.Core, Version=3.0.0.0, Culture=neutral, PublicKeyToken=c608b2a8cc9e4472, processorArchitecture=MSIL">
<HintPath>..\packages\JavaScriptEngineSwitcher.Core.3.0.0-beta4\lib\net40-client\JavaScriptEngineSwitcher.Core.dll</HintPath>
<HintPath>..\packages\JavaScriptEngineSwitcher.Core.3.0.0-beta9\lib\net40-client\JavaScriptEngineSwitcher.Core.dll</HintPath>
</Reference>
<Reference Include="JavaScriptEngineSwitcher.Msie, Version=3.0.0.0, Culture=neutral, PublicKeyToken=c608b2a8cc9e4472, processorArchitecture=MSIL">
<HintPath>..\packages\JavaScriptEngineSwitcher.Msie.3.0.0-beta5\lib\net40-client\JavaScriptEngineSwitcher.Msie.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="MsieJavaScriptEngine, Version=3.0.0.0, Culture=neutral, PublicKeyToken=a3a2846a37ac0d3e, processorArchitecture=MSIL">
<HintPath>..\packages\MsieJavaScriptEngine.3.0.0-beta4\lib\net40-client\MsieJavaScriptEngine.dll</HintPath>
<HintPath>..\packages\MsieJavaScriptEngine.3.0.0-beta5\lib\net40-client\MsieJavaScriptEngine.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
Expand Down
Loading

0 comments on commit 4ff0fad

Please sign in to comment.