Преглед изворни кода

Add ability to prepare and reuse module code (#1300)

Co-authored-by: free.li <[email protected]>
Baowei Li(Free) пре 2 година
родитељ
комит
aaf2ce38b2
3 измењених фајлова са 51 додато и 0 уклоњено
  1. 16 0
      Jint.Tests/Runtime/ModuleTests.cs
  2. 14 0
      Jint/Engine.Ast.cs
  3. 21 0
      Jint/ModuleBuilder.cs

+ 16 - 0
Jint.Tests/Runtime/ModuleTests.cs

@@ -1,3 +1,4 @@
+using Esprima;
 using Jint.Native;
 using Jint.Runtime;
 
@@ -336,6 +337,21 @@ export const count = globals.counter;
         Assert.Equal("John Doe", result);
     }
 
+    [Fact]
+    public void CanReuseModule()
+    {
+        const string Code = "export function formatName(firstName, lastName) {\r\n    return `${firstName} ${lastName}`;\r\n}";
+        var module = Engine.PrepareModule(Code);
+        for (var i = 0; i < 5; i++)
+        {
+            var engine = new Engine();
+            engine.AddModule("__main__", x => x.AddModule(module));
+            var ns = engine.ImportModule("__main__");
+            var result = engine.Invoke(ns.Get("formatName"), "John" + i, "Doe").AsString();
+            Assert.Equal($"John{i} Doe", result);
+        }
+    }
+
     internal static string GetBasePath()
     {
         var assemblyDirectory = new DirectoryInfo(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory);

+ 14 - 0
Jint/Engine.Ast.cs

@@ -23,6 +23,20 @@ public partial class Engine
         return new JavaScriptParser(options).ParseScript(script, source);
     }
 
+    /// <summary>
+    /// Prepares a module for the engine that includes static analysis data to speed up execution during run-time.
+    /// </summary>
+    /// <remarks>
+    /// Returned instance is reusable and thread-safe. You should prepare modules only once and then reuse them.
+    /// </remarks>
+    public static Module PrepareModule(string script, string? source = null)
+    {
+        var astAnalyzer = new AstAnalyzer();
+        var options = ParserOptions.Default with { OnNodeCreated = astAnalyzer.NodeVisitor };
+
+        return new JavaScriptParser(options).ParseModule(script, source);
+    }
+
     private sealed class AstAnalyzer
     {
         private readonly Dictionary<string, EnvironmentRecord.BindingName> _bindingNames = new();

+ 21 - 0
Jint/ModuleBuilder.cs

@@ -11,6 +11,7 @@ public sealed class ModuleBuilder
 {
     private readonly Engine _engine;
     private readonly string _specifier;
+    private Module? _module;
     private readonly List<string> _sourceRaw = new();
     private readonly Dictionary<string, JsValue> _exports = new();
     private readonly ParserOptions _options;
@@ -24,10 +25,29 @@ public sealed class ModuleBuilder
 
     public ModuleBuilder AddSource(string code)
     {
+        if (_module != null)
+        {
+            throw new InvalidOperationException("Cannot have both source text and pre-compiled.");
+        }
         _sourceRaw.Add(code);
         return this;
     }
 
+    public ModuleBuilder AddModule(Module module)
+    {
+        if (_sourceRaw.Count > 0)
+        {
+            throw new InvalidOperationException("Cannot have both source text and pre-compiled.");
+        }
+
+        if (_module != null)
+        {
+            throw new InvalidOperationException("pre-compiled module already exists.");
+        }
+        _module = module;
+        return this;
+    }
+
     public ModuleBuilder ExportValue(string name, JsValue value)
     {
         _exports.Add(name, value);
@@ -104,6 +124,7 @@ public sealed class ModuleBuilder
 
     internal Module Parse()
     {
+        if (_module != null) return _module;
         if (_sourceRaw.Count <= 0)
         {
             return new Module(NodeList.Create(Array.Empty<Statement>()));