Sfoglia il codice sorgente

Fix dynamic importing of JSON modules (#1837)

adams85 1 anno fa
parent
commit
d39eeee619

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

@@ -663,4 +663,104 @@ export const count = globals.counter;
             throw new NotImplementedException(); // no need in this test
         }
     }
+
+    [Theory]
+    [InlineData(false)]
+    [InlineData(true)]
+    public void CanStaticallyImportJsonModule(bool importViaLoader)
+    {
+        const string JsonModuleSpecifier = "./test.json";
+        const string JsonModuleContent =
+            """
+            { "message": "hello" }
+            """;
+
+        const string MainModuleSpecifier = "./main.js";
+        const string MainModuleCode =
+            $$"""
+            import json from "{{JsonModuleSpecifier}}" with { type: "json" };
+            export const msg = json.message;
+            """;
+
+        var loaderModules = new Dictionary<string, Func<Engine, ResolvedSpecifier, Module>>();
+        var engine = new Engine(o => o.EnableModules(new TestModuleLoader(loaderModules)));
+
+        loaderModules.Add(JsonModuleSpecifier, (engine, resolved) => ModuleFactory.BuildJsonModule(engine, resolved, JsonModuleContent));
+        if (importViaLoader)
+        {
+            loaderModules.Add(MainModuleSpecifier, (engine, resolved) => ModuleFactory.BuildSourceTextModule(engine, resolved, MainModuleCode));
+        }
+        else
+        {
+            engine.Modules.Add(MainModuleSpecifier, MainModuleCode);
+        }
+
+        var mainModule = engine.Modules.Import(MainModuleSpecifier);
+
+        Assert.Equal("hello", mainModule.Get("msg").AsString());
+    }
+
+    [Theory]
+    [InlineData(false)]
+    [InlineData(true)]
+    public async Task CanDynamicallyImportJsonModule(bool importViaLoader)
+    {
+        const string JsonModuleSpecifier = "./test.json";
+        const string JsonModuleContent =
+            """
+            { "message": "hello" }
+            """;
+
+        const string MainModuleSpecifier = "./main.js";
+        const string MainModuleCode =
+            $$"""
+            const json = await import("{{JsonModuleSpecifier}}", { with: { type: "json" } });
+            callback(json.default.message);
+            """;
+
+        var completionTcs = new TaskCompletionSource<JsValue>(TaskCreationOptions.RunContinuationsAsynchronously);
+
+        var loaderModules = new Dictionary<string, Func<Engine, ResolvedSpecifier, Module>>();
+        var engine = new Engine(o => o.EnableModules(new TestModuleLoader(loaderModules)))
+            .SetValue("callback", new Action<JsValue>(value => completionTcs.SetResult(value)));
+
+        loaderModules.Add(JsonModuleSpecifier, (engine, resolved) => ModuleFactory.BuildJsonModule(engine, resolved, JsonModuleContent));
+        if (importViaLoader)
+        {
+            loaderModules.Add(MainModuleSpecifier, (engine, resolved) => ModuleFactory.BuildSourceTextModule(engine, resolved, MainModuleCode));
+        }
+        else
+        {
+            engine.Modules.Add(MainModuleSpecifier, MainModuleCode);
+        }
+
+        var mainModule = engine.Modules.Import(MainModuleSpecifier);
+
+        Assert.Equal("hello", (await completionTcs.Task).AsString());
+    }
+
+    private sealed class TestModuleLoader : IModuleLoader
+    {
+        private readonly Dictionary<string, Func<Engine, ResolvedSpecifier, Module>> _moduleFactories;
+
+        public TestModuleLoader(Dictionary<string, Func<Engine, ResolvedSpecifier, Module>> moduleFactories)
+        {
+            _moduleFactories = moduleFactories;
+        }
+
+        ResolvedSpecifier IModuleLoader.Resolve(string referencingModuleLocation, ModuleRequest moduleRequest)
+        {
+            return new ResolvedSpecifier(moduleRequest, moduleRequest.Specifier, Uri: null, SpecifierType.RelativeOrAbsolute);
+        }
+
+        Module IModuleLoader.LoadModule(Engine engine, ResolvedSpecifier resolved)
+        {
+            if (_moduleFactories.TryGetValue(resolved.ModuleRequest.Specifier, out var moduleFactory))
+            {
+                return moduleFactory(engine, resolved);
+            }
+
+            throw new ArgumentException(null, nameof(resolved));
+        }
+    }
 }

+ 0 - 1
Jint/Engine.Modules.cs

@@ -36,7 +36,6 @@ public partial class Engine
 
         internal Module Load(string? referencingModuleLocation, ModuleRequest request)
         {
-            var specifier = request.Specifier;
             var moduleResolution = ModuleLoader.Resolve(referencingModuleLocation, request);
 
             if (_modules.TryGetValue(moduleResolution.Key, out var module))

+ 1 - 1
Jint/Runtime/Host.cs

@@ -134,7 +134,7 @@ namespace Jint.Runtime
             try
             {
                 // This should instead return the PromiseInstance returned by ModuleRecord.Evaluate (currently done in Engine.EvaluateModule), but until we have await this will do.
-                Engine.Modules.Import(moduleRequest.Specifier, referrer?.Location);
+                Engine.Modules.Import(moduleRequest, referrer?.Location);
                 promise.Resolve(JsValue.Undefined);
             }
             catch (JavaScriptException ex)

+ 1 - 0
Jint/Runtime/Modules/SyntheticModule.cs

@@ -35,6 +35,7 @@ internal sealed class SyntheticModule : Module
 
     public override void Link()
     {
+        InnerModuleLinking(null!, 0);
     }
 
     public override JsValue Evaluate()