From d39eeee6196482b68c5540dfe02cac2fccdb96e8 Mon Sep 17 00:00:00 2001 From: adams85 <31276480+adams85@users.noreply.github.com> Date: Sat, 20 Apr 2024 07:56:31 +0200 Subject: [PATCH] Fix dynamic importing of JSON modules (#1837) --- Jint.Tests/Runtime/ModuleTests.cs | 100 ++++++++++++++++++++++++ Jint/Engine.Modules.cs | 1 - Jint/Runtime/Host.cs | 2 +- Jint/Runtime/Modules/SyntheticModule.cs | 1 + 4 files changed, 102 insertions(+), 2 deletions(-) diff --git a/Jint.Tests/Runtime/ModuleTests.cs b/Jint.Tests/Runtime/ModuleTests.cs index 056b60e95c..cc0a6f5136 100644 --- a/Jint.Tests/Runtime/ModuleTests.cs +++ b/Jint.Tests/Runtime/ModuleTests.cs @@ -663,4 +663,104 @@ protected override string LoadModuleContents(Engine engine, ResolvedSpecifier re 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>(); + 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(TaskCreationOptions.RunContinuationsAsynchronously); + + var loaderModules = new Dictionary>(); + var engine = new Engine(o => o.EnableModules(new TestModuleLoader(loaderModules))) + .SetValue("callback", new Action(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> _moduleFactories; + + public TestModuleLoader(Dictionary> 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)); + } + } } diff --git a/Jint/Engine.Modules.cs b/Jint/Engine.Modules.cs index 08a67ec3dc..c1a7b313d4 100644 --- a/Jint/Engine.Modules.cs +++ b/Jint/Engine.Modules.cs @@ -36,7 +36,6 @@ public ModuleOperations(Engine engine, IModuleLoader moduleLoader) 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)) diff --git a/Jint/Runtime/Host.cs b/Jint/Runtime/Host.cs index 9ebb9301ee..212026ccf2 100644 --- a/Jint/Runtime/Host.cs +++ b/Jint/Runtime/Host.cs @@ -134,7 +134,7 @@ internal virtual void LoadImportedModule(IScriptOrModule? referrer, ModuleReques 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) diff --git a/Jint/Runtime/Modules/SyntheticModule.cs b/Jint/Runtime/Modules/SyntheticModule.cs index 6ead707ee0..0eca02bad8 100644 --- a/Jint/Runtime/Modules/SyntheticModule.cs +++ b/Jint/Runtime/Modules/SyntheticModule.cs @@ -35,6 +35,7 @@ internal SyntheticModule(Engine engine, Realm realm, JsValue obj, string? locati public override void Link() { + InnerModuleLinking(null!, 0); } public override JsValue Evaluate()