Skip to content

Commit

Permalink
Fix dynamic importing of JSON modules (#1837)
Browse files Browse the repository at this point in the history
  • Loading branch information
adams85 committed Apr 20, 2024
1 parent bc7662f commit d39eeee
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 2 deletions.
100 changes: 100 additions & 0 deletions Jint.Tests/Runtime/ModuleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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));
}
}
}
1 change: 0 additions & 1 deletion Jint/Engine.Modules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion Jint/Runtime/Host.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions Jint/Runtime/Modules/SyntheticModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit d39eeee

Please sign in to comment.